tree-shaking是一個在前端領(lǐng)域比較熟知的東西了弧轧。在沒有深入了解前娱局,一直以為他在項目中發(fā)揮了很大的作用扣汪。但是在看了許多文章說tree-shaking并沒有什么卵用后,想自己深入了解一下科平,所以搜了許多博文褥紫,自己也在項目中試驗(yàn)了一下〉苫郏基本了解了大致的流程髓考。所以這篇博文主要是記錄一下學(xué)習(xí)的成果。
tree-shaking是干啥的:
// app.js
export function A(a, b) {
return a + b
}
export function B(a, b) {
return a + b
}
// index.js
import {A} from '/app.js'
A(1, 2)
當(dāng)index.js引用了app.js的A函數(shù)弃酌,如果tree-shaking起了作用氨菇,B函數(shù)是不會被打包進(jìn)最后的bundle的。
但是
世界上有很多但是妓湘,而且往往但是后面的內(nèi)容更加重要查蓉。
relies on the static structure of ES2015 module syntax, i.e. import and export.
在webpack官網(wǎng)當(dāng)中有這樣一句話,翻譯成人話就是tree-shaking依賴es6的模塊引入或輸出語法榜贴。如果你的模塊引入方式是require等等等亂七八糟的東西豌研。tree-shaking將不會起到任何作用。
babel, webpack打包, uglifyJs
這三項東西東西是在我們開發(fā)中幾乎繞不過去東西唬党。而tree-shaking的關(guān)鍵點(diǎn)就在第一步鹃共,babel
雖然我不太了解webpack內(nèi)部的運(yùn)行機(jī)制(看過運(yùn)行順序的相關(guān)文章,但一直是懵比狀態(tài))驶拱,但是看過這么多的文章后霜浴,上面三項的基本運(yùn)行順序還是理解的:
就是babel-loader先去處理js文件,處理過后蓝纲,webpack進(jìn)行打包處理阴孟,最后uglifyjs進(jìn)行代碼壓縮晌纫。而關(guān)鍵就是babel怎么去處理js文件
babel的配置文件中有一個preset配置項:
{
"presets": [
["env", {
"modules": false //關(guān)鍵點(diǎn)
}],
"stage-2",
"react"
]
}
其中presets里面的env的options中有一個 modules: false,這是指示babel如何去處理import和exports等關(guān)鍵子,默認(rèn)處理成require形式永丝。如果加上此option锹漱,那么babel就不會吧import形式,轉(zhuǎn)變成require形式类溢。為webpack進(jìn)行tree-shaking創(chuàng)造了條件凌蔬。
在看過這些篇博文后露懒,我本人對于tree-shaking有了一個基本的認(rèn)識闯冷,那就是
babel首先處理js文件,真正進(jìn)行tree-shaking識別和記錄的是webpack本身懈词。刪除多于代碼是在uglify中執(zhí)行的
注:webpack在認(rèn)定某塊代碼無用后蛇耀,會再處理過程中寫下一段注釋。uglifyjs會根據(jù)這點(diǎn)注釋去進(jìn)行刪除代碼坎弯。
注釋的大體內(nèi)容(博文很久了纺涤,還是在webpack2.0時代,具體內(nèi)容可能已經(jīng)變化抠忘,但原理應(yīng)該是不變的撩炊。)
function(module, exports, __webpack_require__) {
/* harmony export */ exports["foo"] = foo;
/* unused harmony export bar */;
function foo() {
return 'foo';
}
function bar() {
return 'bar';
}
}
tree-shaking,實(shí)戰(zhàn)代碼:
背景:在學(xué)習(xí)tree-shaking的過程中崎脉,如何支持class的tree-shaking是我一直關(guān)注的拧咳,而且大部分的文章還只停留在理論方面。所以最近自己寫了一個demo,支持class的tree-shaking
1.首先使用loader去處理囚灼,實(shí)驗(yàn)階段代碼骆膝,編譯成標(biāo)準(zhǔn)es代碼。這樣webpack內(nèi)部的編譯器才能正確識別代碼灶体。
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['babel-preset-stage-2', 'babel-preset-react']
}
},
'eslint-loader'
],
exclude: /node_modules/
}
]
}
2.然后通過webpack打包阅签,并對代碼進(jìn)行tree-shaking.在打包完最后的bundle之后,和輸出文件之前蝎抽,對最后的bundle進(jìn)行兼容性處理政钟。
plugins: [
new UglifyJSPlugin(), // uglify要在babelPugin的前面
new BabelPlugin({ //在這個插件內(nèi)部進(jìn)行最后bundle的兼容性處理
test: /\.js$/,
babelOptions: {
presets: [env]
}
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new HtmlWebpackPlugin({
template: 'index.html'
}),
]
最后總結(jié)步驟:
先編譯實(shí)驗(yàn)性質(zhì)代碼為標(biāo)準(zhǔn)代碼,會涉及到babel-preset-stage-x插件
webpack打包代碼并進(jìn)行tree-shaking識別樟结。
uglifyjs進(jìn)行代碼壓縮锥涕,并根據(jù)webpack標(biāo)識刪除多余代碼
4.對最后的代碼進(jìn)行兼容性處理涉及到babel-preset-env插件。
第三方類庫的tree-shaking
在研究了許多第三方類庫后狭吼,基本得出了一個結(jié)論:tree-shaking本質(zhì)上是不能對大部分的第三方類庫進(jìn)行tree-shaking的.上面的實(shí)戰(zhàn)代碼层坠,對于自己寫的代碼還有點(diǎn)用,但是只要涉及到第三方類庫刁笙,基本就是歇菜破花。
ramda的輸出文件:
大部分的react ui組件谦趣,以及函數(shù)工具類庫∽浚基本都是這樣來進(jìn)行模塊輸出前鹅,和引用的。
export { default as F } from './F';
export { default as T } from './T';
export { default as __ } from './__';
export { default as add } from './add';
export { default as addIndex } from './addIndex';
export { default as adjust } from './adjust';
export { default as all } from './all';
export { default as allPass } from './allPass';
export { default as always } from './always';
export { default as and } from './and';
export { default as any } from './any';
export { default as anyPass } from './anyPass';
export { default as ap } from './ap';
export { default as aperture } from './aperture';
export { default as append } from './append';
export { default as apply } from './apply';
export { default as applySpec } from './applySpec';
...
這樣的文件結(jié)構(gòu)是無法進(jìn)行tree-shaking的
// 只要是你在代碼中引用了一個方法峭梳,那么你肯定將所有的代碼都引入了進(jìn)來
import {path} from 'ramda'
唯一的解決方法就是直接到具體的文件夾去引用舰绘,而不是在根index.js里面去引用。
import path from 'ramda/src/path'
但是如果每一次引用都是這樣去寫葱椭,開發(fā)的效率就無法保證捂寿,所以基本上有點(diǎn)追求的技術(shù)團(tuán)隊,基本上會再類庫的基礎(chǔ)上孵运,開發(fā)一個babel的插件以支持代碼的tree-shaking秦陋。
像著名的antd,以及ramda等都開發(fā)了相應(yīng)的插件治笨。
babel-plugin-ramda:此插件會默認(rèn)將你寫的代碼轉(zhuǎn)化為tree-shaking的代碼
from:
import {path} from 'ramda'
to
import path from 'ramda/src/path'
而本人也在了解了以上東西后驳概,為本公司的ui組件開發(fā)了一個插件:
babel-plugin-b-rc