爲何 webpack4 默認支持 ES6 語法的壓縮?

在專欄課程裏,有位同窗提到過一個頗有意思的問題:「我沒裝 babel,js 入口裏寫了個箭頭函數,運行 webpack 構建命令後,也成功編譯了。這是爲何?」。今天就帶領你們一塊兒去探討下這個話題。前端

在使用 webpack 的時候,很常見的一個構建優化手段就是縮小構建目標。好比在構建階段只構建 src 裏面的模塊代碼,對於 node_modules 裏面所引入的三方包不進行構建操做。node

發現問題

若是使用的是 webpack 3.x 版本,編寫的構建腳本相似這樣的,咱們經過設置loader 裏面的 exclude 字段避免因爲解析 node_modules 裏面的模塊形成的構建耗時:webpack

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'happypack/loader',
                exclude: path.join(__dirname, 'node_modules')
            }
        ]
    }
    plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ]
};
複製代碼

咱們常常會遇到一個問題,假設引入的 npm 包質量不夠高,好比 node_modules 裏面有 ES6 的語法,那麼 webpack 在 uglify 階段會報錯!下面給出兩種常見的出錯場景:ios

ES6 的模板字符串

假設 node_modules 裏面存在 ES6 的模板字符串語法,那麼在生產環境打包的代碼壓縮階段,UglifyJs 會拋出錯誤。git

圖片

ES6 的箭頭函數

一樣的,你使用 ES6 的箭頭函數也是沒法正常的壓縮代碼的。github

圖片

細心的你必定會發現若是使用的是 webpack 4,這個場景描述的問題將再也不出現。webpack 4默認支持 ES6 代碼的壓縮,這個是什麼緣由呢?web

初步分析

若是你有對 webpack 4 的依賴包進行過相關分析,好比直接查閱 package.json 文件或者經過 npm.broofa.com/ 網站上進行 webpack 依賴圖分析。不難發現 webpack 4 裏面使用了 terser-webpack-plugin 插件替代了以前一直使用的 uglifyjs-webpack-plugin 做爲它的內置插件。npm

以 4.39.3 這個版本爲例,能夠看到它的 package.json 文件的依賴包括了terser-webpack-plugin。json

圖片

咱們進一步分析發現 webpack 的 4.26.0 這個版本有一次提交,它的提交內容是對 webpack 內置插件進行了一次切換。bash

圖片

通過這麼一次分析,咱們能夠知道 webpack 4 之因此具有默認壓縮 ES6 代碼的能力,離不開 terser-webpack-plugin所起的做用!

進一步分析

在探究 terser-webpack-plugin 插件的原理前,咱們先系統的回顧一下代碼壓縮插件的歷史:

  • 當 uglifyjs-webpack-plugin 版本小於 v1.0 時,它使用的是 uglify-js 依賴
  • 可是 uglify-js 並不支持 ES6, 所以在 uglify-js 倉庫的 harmony 分支 Fork 了一個 uglify-es
  • uglifyjs-webpack-plugin 的 v1.x 爲了支持 ES6 的壓縮語法,將 uglify-js 依賴切換到了 uglify-es
  • 可是 uglify-es 中止維護了: mishoo/UglifyJS2#3156 (comment)
  • uglify-es 的中止維護致使了 terser 被 fork 出來了,而且 terser 處理了沒有合入的 PRs,最終建立了一個獨立的倉庫: github.com/fabiosantos…
  • 隨後,terser-webpack-plugin 被建立出來, 它基於 terser,而且具有uglifyjs-webpack-plugin 的同等功能 : github.com/webpack-con…
  • 因爲 uglifyjs-webpack-plugin v2.x 回退到了 uglify-js, 再也不支持 ES6。 所以那些但願支持 ES6 語法壓縮的項目必須切換到 terser-webpack-plugin

備註:壓縮插件歷史的來源 github.com/webpack/web…

到這裏,咱們能夠得出一個基本的結論:terser-webpack-plugin 基於 terser 所以它具有 ES6 的壓縮能力,uglifyjs-webpack-plugin v2.x 版本基於 uglify-js,沒法支持 ES6 的壓縮。

插件 依賴 是否支持 ES6(Y/N)
terser-webpack-plugin terser Y
uglifyjs-webpack-plugin v1.x uglify-es Y
uglifyjs-webpack-plugin v2.x uglify-js N

原理探究

代碼壓縮原理其實挺簡單的,也是 AST 的一個經典的應用案例。它的壓縮過程一般是:

JS 源代碼 -> AST -> 美化、壓縮 -> 新的 AST -> 壓縮後的代碼 
複製代碼

瞭解了代碼壓縮的基本流程後,接下來咱們看看源碼包含了哪些內容,因爲 terser 是從 uglify-es Fork 出來進行修改的,所以它的代碼結構和 uglify-js 基本一致,只不過 terser 使用了 ES6 模塊的靜態分析功能。咱們以 terser 的源碼爲例分析下:

  • ast.js:JS 的抽象語法樹的描述信息
  • parse.js:Parser,用於從 JS 源代碼分析出 AST
  • minify.js:用於將 AST 優化成更簡短的結構
  • output.js:代碼生成器,從 AST 輸出 壓縮後的代碼,支持 sourcemap 的生成
  • propmangle.js:對變量的長度進行壓縮,一般是單個字符
  • scope.js:分析變量定義/引用位置的信息
  • transform.js:節點遍歷

而後,咱們來一探 terser 和 uglify-js 的差別。對比了以後,發現一個很大的差別是 AST 的支持上面不一樣。

分析AST的差別發現,下面是兩個文件 diff 對比只在 terser 中才有,而這些恰好對應 ES6 的語法。

AST_Arrow,
AST_Await,
AST_BigInt,
AST_Class,
AST_ClassExpression,
AST_ConciseMethod,
AST_Const,
AST_DefaultAssign,
AST_Destructuring,
AST_Expansion,
AST_Export,
AST_ForOf,
AST_Import,
AST_Let,
AST_NameMapping,
AST_NewTarget,
AST_PrefixedTemplateString,
AST_Super,
AST_SymbolMethod,
AST_TemplateSegment,
AST_TemplateString,
AST_Yield
複製代碼

至此,咱們發現 webpack4 默認支持 ES6 壓縮的關鍵是:terser 裏面實現了 ES6 語法的 AST解析


想學習更多 webpack 和前端工程相關知識能夠掃碼關注咱們團隊同窗公衆號:前期推送頻率每週一篇。

相關文章
相關標籤/搜索