# 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 ,這時你打包後發現

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 不會被打包進去了

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 就會被打包了

值得注意的是有時候專案會引入 css 檔案,這時也要記得放到sideEffect 中,不然就會造成你打包後的內容沒有樣式XD,例如 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` 設定。