tree-shaking實戰

tree-shaking是一個在前端領域比較熟知的東西了。在沒有深入了解前,一直以為他在項目中發揮了很大的作用。但是在看了許多文章說tree-shaking并沒有什么卵用后,想自己深入了解一下,所以搜了許多博文,自己也在項目中試驗了一下。基本了解了大致的流程。所以這篇博文主要是記錄一下學習的成果。

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)

當index.js引用了app.js的A函數,如果tree-shaking起了作用,B函數是不會被打包進最后的bundle的。

但是

世界上有很多但是,而且往往但是后面的內容更加重要。

relies on the static structure of ES2015 module syntax, i.e. import and export.

在webpack官網當中有這樣一句話,翻譯成人話就是tree-shaking依賴es6的模塊引入或輸出語法。如果你的模塊引入方式是require等等等亂七八糟的東西。tree-shaking將不會起到任何作用。


babel, webpack打包, uglifyJs

這三項東西東西是在我們開發中幾乎繞不過去東西。而tree-shaking的關鍵點就在第一步,babel

雖然我不太了解webpack內部的運行機制(看過運行順序的相關文章,但一直是懵比狀態),但是看過這么多的文章后,上面三項的基本運行順序還是理解的:

就是babel-loader先去處理js文件,處理過后,webpack進行打包處理,最后uglifyjs進行代碼壓縮。而關鍵就是babel怎么去處理js文件

babel的配置文件中有一個preset配置項:


{
  "presets": [
    ["env", {
      "modules": false  //關鍵點
    }],
    "stage-2",
    "react"
  ]
}


其中presets里面的env的options中有一個 modules: false,這是指示babel如何去處理import和exports等關鍵子,默認處理成require形式。如果加上此option,那么babel就不會吧import形式,轉變成require形式。為webpack進行tree-shaking創造了條件。

在看過這些篇博文后,我本人對于tree-shaking有了一個基本的認識,那就是

babel首先處理js文件,真正進行tree-shaking識別和記錄的是webpack本身。刪除多于代碼是在uglify中執行的

注:webpack在認定某塊代碼無用后,會再處理過程中寫下一段注釋。uglifyjs會根據這點注釋去進行刪除代碼。

直接復制這篇博文代碼

注釋的大體內容(博文很久了,還是在webpack2.0時代,具體內容可能已經變化,但原理應該是不變的。)


function(module, exports, __webpack_require__) {
    /* harmony export */ exports["foo"] = foo;
    /* unused harmony export bar */;

    function foo() {
        return 'foo';
    }
    function bar() {
        return 'bar';
    }
}

tree-shaking,實戰代碼:

背景:在學習tree-shaking的過程中,如何支持class的tree-shaking是我一直關注的,而且大部分的文章還只停留在理論方面。所以最近自己寫了一個demo,支持class的tree-shaking

1.首先使用loader去處理,實驗階段代碼,編譯成標準es代碼。這樣webpack內部的編譯器才能正確識別代碼。


module: {
rules: [
  {
    test: /\.js$/,
    use: [
      {
        loader: 'babel-loader',
        options: {
          presets: ['babel-preset-stage-2', 'babel-preset-react']
        }
      },
      'eslint-loader'
    ],
    exclude: /node_modules/
  }
]
}

2.然后通過webpack打包,并對代碼進行tree-shaking.在打包完最后的bundle之后,和輸出文件之前,對最后的bundle進行兼容性處理。

plugins: [
    new UglifyJSPlugin(),  //  uglify要在babelPugin的前面
    new BabelPlugin({  //在這個插件內部進行最后bundle的兼容性處理
      test: /\.js$/,
      babelOptions: {
        presets: [env]
      }
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new HtmlWebpackPlugin({
      template: 'index.html'
    }),
  ]


最后總結步驟:

  1. 先編譯實驗性質代碼為標準代碼,會涉及到babel-preset-stage-x插件

  2. webpack打包代碼并進行tree-shaking識別。

  3. uglifyjs進行代碼壓縮,并根據webpack標識刪除多余代碼

4.對最后的代碼進行兼容性處理涉及到babel-preset-env插件。

第三方類庫的tree-shaking

在研究了許多第三方類庫后,基本得出了一個結論:tree-shaking本質上是不能對大部分的第三方類庫進行tree-shaking的.上面的實戰代碼,對于自己寫的代碼還有點用,但是只要涉及到第三方類庫,基本就是歇菜。

ramda的輸出文件:
大部分的react ui組件,以及函數工具類庫。基本都是這樣來進行模塊輸出,和引用的。


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';

...

這樣的文件結構是無法進行tree-shaking的


// 只要是你在代碼中引用了一個方法,那么你肯定將所有的代碼都引入了進來
import {path} from 'ramda' 


唯一的解決方法就是直接到具體的文件夾去引用,而不是在根index.js里面去引用。


import path from 'ramda/src/path'


但是如果每一次引用都是這樣去寫,開發的效率就無法保證,所以基本上有點追求的技術團隊,基本上會再類庫的基礎上,開發一個babel的插件以支持代碼的tree-shaking。

像著名的antd,以及ramda等都開發了相應的插件。

babel-plugin-ramda:此插件會默認將你寫的代碼轉化為tree-shaking的代碼


from:

import {path} from 'ramda' 

to

import path from 'ramda/src/path'


而本人也在了解了以上東西后,為本公司的ui組件開發了一個插件:

babel-plugin-b-rc

在看過ramda的代碼,以及他的構建過程和對于tree-shaking的支持后,學到了很多。有的時候學習webpack以及如何優化網頁的時候,不知道如何下手。我的經驗就是找到一個技術點,進行深入。比如我得研究點就是tree-shaking,在一路研究過后,babel,babel-preset, babel-plugin,webpack,webpack-plugin,都有了一定的了解。這幾個功能點基本上花費了我將近一個多月的時間。從找材料,到動手完成自己的一個babel插件,收獲頗豐。學習babel以及webpack的過程,其本質是學習如何優化網頁的過程!!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。