>[success] # 搖樹
~~~
1.tree-shaking 搖掉代碼中未引用部分(dead-code),production模式下會自動使用tree-shaking
打包時除去未引用的代碼。作用是優化項目代碼
~~~
>[info] ## 搖樹具備條件
~~~
1.'Tree Shaking' 是在'編譯時'進行無用代碼消除的,因此它需要在'編譯時確定依賴關系',首先排除'cjs'
他是運行時執行,因此'Tree Shaking' 要配合'ESM',總結:
1.1.只有 ES6 類型的模塊才能進行Tree Shaking,因為 ES6 模塊的依賴關系是確定的,可以編譯時確定依賴
關系,做靜態分析
1.2.CommonJS 定義的模塊化規范,只有在執行代碼后,才能動態確定依賴模塊,因此不具備 Tree Shaking
的先天條件
2.在傳統編譯型語言中,一般由編譯器將無用代碼在 AST(抽象語法樹)中刪除,而前端 JavaScript 并沒有正統
'編譯器'這個概念,那么 Tree Shaking 就需要在工程鏈中由工程化工具完成。
3.引入方式導致搖樹是否生效,以 default 方式引入的模塊,無法被 Tree Shaking;而引入單個導出對象的方式,
無論是使用 import * as xxx 的語法,還是 import {xxx} 的語法,都可以進行 Tree Shaking
~~~
>[info] ## 通過webpack 搖樹案例
~~~
1.下面代碼中'components.js' 向外導出三個函數,但是使用的時候在'sec/index.js'只是用了'Button',
剩下兩個方法沒有使用,因此希望打包時候可以不將這未使用的方法一起打包,這個過程需要
需要通過webpack 提供的'搖樹'
2.webpack 本身在mode production 會開啟搖樹,可以理解是自帶行為,但是可以將mode 配置成none,
進行打包后如圖一所示未被使用的導出依舊被打包了
~~~
~~~
// src/components.js 導出了三個函數,每個函數模擬一個組件
export const Button = () => {
return document.createElement('button')
console.log('dead-code') // 未引用代碼
}
export const Link = () => {
return document.createElement('a')
}
export const Heading = level => {
return document.createElement('h' + level)
}
// sec/index.js
import { Button } from './components'
document.body.appendChild(Button())
~~~
* 圖一

>[danger] ##### 配置搖樹
~~~
1.手動配置webpack搖樹,這個案例依舊使用mode為none,但搖樹配置項手動配置需要配置'optimization',
先分析搖樹過程'先標記','在刪除',需要配置屬性
1.1.'usedExports' - 打包結果中只導出外部用到的成員(做標記)
1.2.'minimize' - 壓縮打包結果(用來刪除)
1.3.'concatenateModules'盡可能打包后將每一個模塊合并到一個函數中,這樣的好處
'既提升了運行效率,又減少了代碼的體積',如果看過之前章節可以先webpack導出是以每個模塊為單位
配置這個后會將模塊打包進一個導出函數中
2.整個過程簡單的說Webpack 負責對模塊進行分析和標記,而這些壓縮插件負責根據標記結果,進行代碼刪除
3.整個標記分為三類:
3.1.'harmony export',被使用過的 export 會被標記為 harmony export;
3.2.'unused harmony export',沒被使用過的 export 標記為 unused harmony export;
3.3.'harmony import',所有 import 標記為 harmony import。
4.整個具體過程
4.1.Webpack 在編譯分析階段,將每一個模塊放入 ModuleGraph 中維護;
4.2.依靠 'HarmonyExportSpecifierDependency' 和 'HarmonyImportSpecifierDependency'
分別識別和處理 import 以及 export;
4.3.依靠 'HarmonyExportSpecifierDependency' 識別 'used export' 和 'unused export'。
~~~
~~~
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
// 集中配置webpack的優化功能
optimization: {
// 模塊只導出被使用的成員
// useExports負責標記枯樹葉
usedExports: true,
// 盡可能合并每一個模塊到一個函數中
concatenateModules: true,
// 壓縮輸出結果
// minimize 負責搖掉枯樹枝
minimize: true
}
}
~~~
* 如圖只配置了 useExports 只會做標記并沒有刪除,當配置了minimize 這些沒被使用的將會被刪除

* 配置concatenateModules,這里沒有開啟搖樹,可以發現之前是按模塊導出現在已經將相同模塊放到了一起

>[info] ## 有的文章說 babel-loader 不能做搖樹
~~~
1.Tree Shaking 實現前提是使用 ES Modules 組織代碼,也就是交給 Webpack 處理的代碼必須使用
ESM 實現的模塊化
2.有個疑問'Babel' 會不會影響搖樹,很多時候我們會選用 babel-loader 去處理JS。而在 Babel 轉換代碼時,
可能處理掉代碼中的 ES Modules 轉換成 Common JS,可以通過配置來解決讓'Babel' 保留'esm'導入導出
3.在 Babel 7 之前的babel-preset-env中,modules 的默認選項為 'commonjs',因此在使用 babel 處理模塊時,
即使模塊本身是 ES6 風格的,也會在轉換過程中,因為被轉換而導致無法在后續優化階段應用 Tree Shaking。
而在 Babel 7 之后的 @babel/preset-env 中,modules 選項默認為 ‘auto’,它的含義是對 ES6 風格的模塊不做
轉換(等同于 modules: false),而將其他類型的模塊默認轉換為 CommonJS 風格。因此我們會看到,后者即
使經過 babel 處理,也能應用 Tree Shaking。
~~~
~~~
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
// ['@babel/preset-env'] // 最新版本的babel-loader中自動關閉了ESM轉換插件
// ['@babel/preset-env', { modules: 'commonjs' }] // 強制開啟該插件 則會導致 Tree Shaking 失效
// ['@babel/preset-env', { modules: false }] // 確保不會開啟ESM轉換插件
// 也可以使用默認配置,也就是 auto,這樣 babel-loader 會自動關閉 ESM 轉換
['@babel/preset-env', { modules: 'auto' }]
]
}
}
}
]
},
optimization: {
// 模塊只導出被使用的成員
usedExports: true,
// 盡可能合并每一個模塊到一個函數中
// concatenateModules: true,
// 壓縮輸出結果
// minimize: true
}
}
~~~
>[info] ## 副作用
~~~
export function add(a, b) {
return a + b
}
export const memoizedAdd = window.memoize(add)
或者
// 為 Number 的原型添加一個擴展方法
Number.prototype.pad = function (size) {
const leadingZeros = Array(size + 1).join(0)
return leadingZeros + this
}
上面兩種寫法在打包時候都會產生副作用,其中第一種寫法雖然后續可能你沒有使用'memoizedAdd '
但是在打包時候webpack 為了安全起見因為你在window.memoize使用了add 因此也打包進去了
~~~
>[success] # sideEffects -- 手動配置副作用
~~~
1.Webpack4 還新增了 sideEffects 新特性,它允許我們通過配置的方式去標識我們的代碼是
否有副作用從而為 Tree Shaking 提供更大的壓縮空間。
1.1.'副作用':模塊執行時除了導出成員之外所做的事情
2.Tree-shaking 只能移除沒有用到的代碼成員,而想要完整移除沒有用到的模塊,
那就需要開啟 sideEffects 特性了
~~~
>[danger] ##### 舉個例子
~~~
1.在我們寫一些項目的時候經常會遇到這種結構,index.js 是所有文件的最后導出文件例如:
export?{?defaultasButton?}?from'./button'
export?{?defaultasLink?}?from'./link
export?{?defaultasHeading?}?from'./heading'
但是在整個項目使用的時候我們導入文件可能最后僅僅只使用的了其中一個'Button'
import { Button } from './components'
document.body.appendChild(Button())
這里注意和上面的案例的區別,上面是同一個文件同時導出三個方法,這里是index.js做了一個橋導出三個了文件
使用的時候是在index.js這個橋里面取出對應的方法,這時候使用上面的'搖樹的配置'是不會清除掉沒有引用的
另外兩個文件
2.這里需要使用'sideEffects',來判斷引用文件是否存在副作用,也就是是否被使用,就是被導入但沒被使用的
就可以不再最后打包文件內容生成
~~~
* 如圖

~~~
1.使用在'optimization'配置'sideEffects',還需要在package.json中的sideEffects: false 標明項目代碼是否有副作用
2.查看 lodash-es 的 package.json 文件,可以看到其中包含了 "sideEffects":false 的描述
~~~
~~~
module.exports = {
...
optimization: {
sideEffects: true, // production 下也會自動開啟
}
}
~~~
>[danger] ##### 為什么要在package.json 中聲明
~~~
1.因為有時候會引入css 文件,或者是全局方法文件這里舉個例子,
1.1.下面的css文件引入后確實沒有地方調用使用它,看似沒有副作用,但是實際他不能再打包的時候被刪除
1.2.下面js文件中'extend' 在原型上面綁定了一個pad 方法,看似'extend' 也沒有地方調用,但是實際產生
了副作用因為他給Number 的原型添加一個擴展方法也是不能再打包的時候刪除
2.這種特殊的性質的文件 就不能再'package.json中的sideEffects: false'將所有的看似沒有副作用的文件都
刪除,需要告訴這些文件看似沒有副作用但實際產生了副作用,在package.json 配置如下
{
...
"sideEffects": [ // 添加有副作用的文件
"./src/extend.js",
"*.css" // 路徑通配符的方式
]
}
3.當然直接配置false,表示我們這個項目中的所有代碼都沒有副作用該刪除就刪除
"sideEffects": false
~~~
* css 文件
~~~
// 樣式文件屬于副作用模塊
import './global.css'
~~~
* js 文件
~~~
// src/extend.js
// 為 Number 的原型添加一個擴展方法
Number.prototype.pad = function (size) {
// 將數字轉為字符串 => '8'
let result = this + ''
// 在數字前補指定個數的 0 => '008'
while (result.length < size) {
result = '0' + result
}
return result
}
~~~
~~~
import './extend'
console.log((8).pad(3))
~~~
>[danger] ##### css
~~~
1.purgecss-webpack-plugin
~~~
>[success] # Tree Shaking 友好的導出模式
~~~
export default {
add(a, b) {
return a + b
}
subtract(a, b) {
return a - b
}
}
或者
export class Number {
constructor(num) {
this.num = num
}
add(otherNum) {
return this.num + otherNum
}
subtract(otherNum) {
return this.num - otherNum
}
}
~~~
~~~
1.對于上述情況,以 Webpack 為例,Webpack 將會趨向保留整個默認導出對象/class,
不能把沒用到的類或對象內部的方法消除掉,下面三種都將影響 Tree Shaking
1.1.導出一個包含多項屬性和方法的對象
1.2.導出一個包含多項屬性和方法的 class
1.3.使用export default導出
2.推薦:原子化和顆粒化導出
export function add(a, b) {
return a + b
}
export function subtract(a, b) {
return a - b
}
這種方式可以讓 Webpack 更好地在編譯時掌控和分析 Tree Shaking 信息,取得一個更優的 bundle size。
~~~
>[danger] ##### 關于組價庫的優化
https://juejin.cn/post/6844903544760336398
>[danger] ##### 參考文章
[Tree Shaking:移除 JavaScript 上下文中的未引用代碼](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=584#/detail/pc?id=5916)
[玩轉 Webpack 高級特性應對項目優化需求(上)](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=88#/detail/pc?id=2269)
[打包提效:如何為 Webpack 打包階段提速?](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=416#/detail/pc?id=4426)
- 工程化 -- Node
- vscode -- 插件
- vscode -- 代碼片段
- 前端學會調試
- 谷歌瀏覽器調試技巧
- 權限驗證
- 包管理工具 -- npm
- 常見的 npm ci 指令
- npm -- npm install安裝包
- npm -- package.json
- npm -- 查看包版本信息
- npm - package-lock.json
- npm -- node_modules 層級
- npm -- 依賴包規則
- npm -- install 安裝流程
- npx
- npm -- 發布自己的包
- 包管理工具 -- pnpm
- 模擬數據 -- Mock
- 頁面渲染
- 渲染分析
- core.js && babel
- core.js -- 到底是什么
- 編譯器那些術語
- 詞法解析 -- tokenize
- 語法解析 -- ast
- 遍歷節點 -- traverser
- 轉換階段、生成階段略
- babel
- babel -- 初步上手之了解
- babel -- 初步上手之各種配置(preset-env)
- babel -- 初步上手之各種配置@babel/helpers
- babel -- 初步上手之各種配置@babel/runtime
- babel -- 初步上手之各種配置@babel/plugin-transform-runtime
- babel -- 初步上手之各種配置(babel-polyfills )(未來)
- babel -- 初步上手之各種配置 polyfill-service
- babel -- 初步上手之各種配置(@babel/polyfill )(過去式)
- babel -- 總結
- 各種工具
- 前端 -- 工程化
- 了解 -- Yeoman
- 使用 -- Yeoman
- 了解 -- Plop
- node cli -- 開發自己的腳手架工具
- 自動化構建工具
- Gulp
- 模塊化打包工具為什么出現
- 模塊化打包工具(新) -- webpack
- 簡單使用 -- webpack
- 了解配置 -- webpack.config.js
- webpack -- loader 淺解
- loader -- 配置css模塊解析
- loader -- 圖片和字體(4.x)
- loader -- 圖片和字體(5.x)
- loader -- 圖片優化loader
- loader -- 配置解析js/ts
- webpack -- plugins 淺解
- eslit
- plugins -- CleanWebpackPlugin(4.x)
- plugins -- CleanWebpackPlugin(5.x)
- plugin -- HtmlWebpackPlugin
- plugin -- DefinePlugin 注入全局成員
- webapck -- 模塊解析配置
- webpack -- 文件指紋了解
- webpack -- 開發環境運行構建
- webpack -- 項目環境劃分
- 模塊化打包工具 -- webpack
- webpack -- 打包文件是個啥
- webpack -- 基礎配置項用法
- webpack4.x系列學習
- webpack -- 常見loader加載器
- webpack -- 移動端px轉rem處理
- 開發一個自己loader
- webpack -- plugin插件
- webpack -- 文件指紋
- webpack -- 壓縮css和html構建
- webpack -- 清里構建包
- webpack -- 復制靜態文件
- webpack -- 自定義插件
- wepack -- 關于靜態資源內聯
- webpack -- source map 對照包
- webpack -- 環境劃分構建
- webpack -- 項目構建控制臺輸出
- webpack -- 項目分析
- webpack -- 編譯提速優護體積
- 提速 -- 編譯階段
- webpack -- 項目優化
- webpack -- DefinePlugin 注入全局成員
- webpack -- 代碼分割
- webpack -- 頁面資源提取
- webpack -- import按需引入
- webpack -- 搖樹
- webpack -- 多頁面打包
- webpack -- eslint
- webpack -- srr打包后續看
- webpack -- 構建一個自己的配置后續看
- webpack -- 打包組件和基礎庫
- webpack -- 源碼
- webpack -- 啟動都做了什么
- webpack -- cli做了什么
- webpack - 5
- 模塊化打包工具 -- Rollup
- 工程化搭建代碼規范
- 規范化標準--Eslint
- eslint -- 擴展配置
- eslint -- 指令
- eslint -- vscode
- eslint -- 原理
- Prettier -- 格式化代碼工具
- EditorConfig -- 編輯器編碼風格
- 檢查提交代碼是否符合檢查配置
- 整體流程總結
- 微前端
- single-spa
- 簡單上手 -- single-spa
- 快速理解systemjs
- single-sap 不使用systemjs
- monorepo -- 工程
- Vue -- 響應式了解
- Vue2.x -- 源碼分析
- 發布訂閱和觀察者模式
- 簡單 -- 了解響應式模型(一)
- 簡單 -- 了解響應式模型(二)
- 簡單 --了解虛擬DOM(一)
- 簡單 --了解虛擬DOM(二)
- 簡單 --了解diff算法
- 簡單 --了解nextick
- Snabbdom -- 理解虛擬dom和diff算法
- Snabbdom -- h函數
- Snabbdom - Vnode 函數
- Snabbdom -- init 函數
- Snabbdom -- patch 函數
- 手寫 -- 虛擬dom渲染
- Vue -- minVue
- vue3.x -- 源碼分析
- 分析 -- reactivity
- 好文
- grpc -- 瀏覽器使用gRPC
- grcp-web -- 案例
- 待續