---
# System prepended metadata

title: webpack

---

# webpack

## Tree Shaking

他是一個打包的一種方式，幫你把沒有使用的程式碼，或是相關的引入去排除，通常會搭配 siedeEffect 去使用。

備註： 他依賴ES module

### 範例

```javascript
//Math.js

const add = (a, b) => a + b;

export default { add };

// string.js

const composeString = (a, b) => `${a} ${b}`;
const addString = (a) => `label: ${a}`;

export default { addString };

// index.js

import { add } from './math';
import { addString } from './string';

console.log(add(1, 2))
```

你可以看到 index.js 引入 Math.js string.js 這兩隻檔案，但卻只使用 add ，這時你打包後發現

![](https://hackmd.io/_uploads/B1xCDZxP2.png)

composeString 這個函數也被打包進去了，這就是 Tree Shaking 在 es module import 時的sideEffect。

解決方式

在package.json 中設置 sideEffects：false，告訴 webpack 所有要打包的模塊不要執行任何的 side effect

```javascript
// package.json
{
  "name": "tree-shaking",
  "sideEffects": false,
  "version": "1.0.0",
  ...
}
```

sideEffects: false 的運作原理就是把 import {a} from xx 转换为 import {a} from 'xx/a' 從而改善不必要的自動 import ，如同[babel-plugin-import](https://github.com/umijs/babel-plugin-import) 功能。



使用後你就會發現 composeString 不會被打包進去了

![](https://hackmd.io/_uploads/BJ7sObePh.png)

 
siedeEffect 的原因是因為 module import 會把所有的檔案都打包進去儘管 composeString 他沒有被export 


**補充:** 有時候我們使用 UI 庫，例如MUI 他們會要求我們更改 import 方式

```typescript
//not use
import {Button} from '@mui/material';

// use
import Button from '@mui/material/Button';
```
這是一種手動 Tree Shaking 的一種方式

### 但為什麼不直接用 sideEffect: fasle？

但這時候你會想那為什麼不全部用sideEffect: fasle就好了？

相信大家一定用過 polyfill ，這時我們import 進去


```javascript
//index.js

import './polyfill';
import { add } from './math';
import { addString } from './string';

console.log([].customMethod());

// polyfill.js 
Array.prototype.customMethod = () => {
  console.log('customMethods');
};
```

因為你開啟 sideEffect false 只要你引入的代碼沒有被使用就不會被打包，這樣就會導致你的build就無法包含 polyfill 的內容了，所以你可以修改一下 sideEffect 設置

```javascript
{
  "name": "tree-shaking",
  "sideEffects": ["./src/polyfill.js"],
  "version": "1.0.0",
  ...
}
```

這樣 polyfill.js 就會被打包了

![](https://hackmd.io/_uploads/BJ5HnWlP3.png)


值得注意的是有時候專案會引入 css 檔案，這時也要記得放到sideEffect 中，不然就會造成你打包後的內容沒有樣式ＸＤ，例如 sideEffects: ["*.css"]


### 進階

有時候我們會需要測試函數， 你可以透過/*#__PURE__*/ 將你調用的函數不被打包進去，這時對於你專案的調適或是打包後的體積都有幫助。

```
/*#__PURE__*/ double(55);
```
## hash

這邊我想補充一下為什麼會有舊資料問題，原因是瀏覽器的 cache 策略：
1. cache control 
也就是狐狸大大說的 cache control header設定，在 cache control 的一段時間內，如果靜態資源的檔名不變，瀏覽器將不會重新下載同一個黨名的資源。

優點：
避免重新下載重複資源，並加速整個 page 的 loading 時間與不必要的 request

2. 問題闡述
所以很多時候假如你的照片有修改但你檔名不變，就會發生 prod 的 image 內容需要等 cache control 下一次重新 update 才會重新抓取資源，這樣的更新是不及時的 。

3. 解決方式
在 builder 可以針對靜態資源的檔名做 hash 增加唯一性。
`[ext]` : 副黨名假如你是 Image.png 那這邊就是 png
`[name]`: 原本的黨名  Image.png 那這邊就是 Image
`[hash]`: 每次 build 後會有特定的 hash 值
`Timestamp` : 自己定義變數
```typescript
 build: {
    rollupOptions: {
      output: {
        chunkFileNames: `static/js/[name].[hash]${Timestamp}.js`,
        entryFileNames: `static/js/[name].[hash]${Timestamp}.js`,
        assetFileNames: `static/[ext]/[name].[hash]${Timestamp}.[ext]`,
      },
    }
  }
  ```
但這邊你一定會有疑慮為什麼要加 `Timestamp` 原因是每個檔案 build 最後的結果他的 `hash` 都會是一樣的，假設我 build image.png 這個檔案那他每次的解果都會是 `static/png/image.abcdef1234567890_1671234567890.png` ，
所以才會需要加  Timestamp 增加唯一性，這樣才能讓瀏覽器每次重新抓取新資料，但筆者這邊補充一個小知識除了 `Timestamp` 方式外 `hash` 還有其他用法：
`hash` : 使資源內容的 hash 有唯一性。
`chunkhash` : 根據 chunk 資源內容有唯一性，可以用於代碼分割。
`contenthash` : 根據檔案內容產生唯一性，同常用於 `css` 的編碼。


## 在 react 中為什麼不能使用 relative path 放 img source
原因是你需要將 `img` 的 `source` `import` 近來給 `webpack` 打包。

### 所以你不能這樣寫
這樣會導致 `webpack` 編譯時找不到 `img` 的 `source`

```typescript
<img src="./somePath">
```
在 `webpack` 中讀取 `assert` 的設定用 `webpack` 解釋的話看一下 `demo`

```typescript
module.exports = {
  entry: "./src/main.js",
  output: {
      // 打包後的路徑
    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
      // js module 的打包位置，沒做 code spilt 的 js
    filename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js",
     // 有做 code spilt 的 js 的打包路徑
    chunkFilename: isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js",
      // 所有 assert 打包後的路徑
    assetModuleFilename: "static/media/[hash:10][ext][query]",
      // 每次打包都重新刪除 dist 資料夾確保每次的打包都是最新的內容
    clean: true,
      // 哪個資料夾底下的內容需要給 webpack 打包
    publicPath: '/src',
  },
}
```
那 `webpack` 是怎麼知道讀取 `assert`  的則是透過 `rules` 設定，這邊不廢話直接簡單總結。

`test` : 哪些 `assert` 需要被打包。
`type` : `asset/resource`  代表是圖片資源。

* 補充一下在 `webpack 4 `打包圖片資源是透過 `file-loader` 跟 `url-loader` ，但在 `webpack 5` 把這兩個 `loader`整合再一起，所以只需要設定 ` type: 'asset/resource` `webpack` 就會自動幫你打包圖片資源了。

這代表當圖片大小大於 `4kb` 就把圖片資源轉乘 `base64` 到 `img` 的 `src` 上，所以在打包後的 `dist` 資料夾中會看不到大於 `4kb` 的圖片喔～
```typescript
 parser: {
         dataUrlCondition: {
           maxSize: 4 * 1024 // 4kb
         }
       }
```
```typescript
module.exports = {
    
// ..
  module: {
    rules: [
      {
        test: /\.(png|jpg)/,
        type: 'asset/resource',
        parser: {
         dataUrlCondition: {
           maxSize: 4 * 1024 // 4kb
         }
       }
     },
    ]
  },
    // ..
}
```

所以你在 `react` 中需要先 `import img` 的`source` 才能放到 `src` 中 ， `webpack` 才能幫你打包圖片～
```typescript
import path from './someImg.png'
<img src={path}>
```

那問題來了為什麼我們可以透過 `absoult path` 方式引入 `public` 的 `path`

```typescript
some-project-root
├── public
│   └── image.png
└── src
    └── App.css
<img src='/image.png'>
```

原因是 `public` 是 `react` 自己處理的， `webpack` 並不會去打包，因為有指定 `publicPath` 是 `src`所以 `public` 的內容 `webpack` 才可以不用去理會。


```typescript
module.exports = {
  entry: "./src/main.js",
  output: {
   // ..
      // 哪個資料夾底下的內容需要給 webpack 打包
    publicPath: '/src',
  },
}
```

`react` 會透過 `process.env.PUBLIC_URL` 這個環境變數原封不動地把 `public` 裡面的內容 `copy` 到 `dist`資料夾中。

```typescript

  return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;

```

最後提醒一下，建議放在 `public` 的內容是靜態資源為主，如果是長期變更的資源可能會跟`browser` 的`cache`週期撞到導致內容無法立即更新，那放在 `src/` 裡面的 `image` 因為有將打包後的 `source` 做 `hash`，所以不會因為 `cache` 的關係內容是舊的。

```typescript
assetModuleFilename: "static/media/[hash:10][ext][query]",
```

## 結論
`public` : 建議放靜態資源。
`src/*` : 適合放需要長期變更的資料或是 `image` ，但要急得加上 `hash` 設定。
