關於webpack優化,你需要知道的事(上篇)

leizore發表於2018-07-22

前言

webpack 是一個優秀的打包工具,其本身為我們做了大量優化,同時也為我們提供了大量的配置項讓我們可以自定義,從而有優化空間。

在講 webpack 優化篇之前,由於樓主主要以 vue 腳手架開始的,而且是已經升級為 webpack4 之後的優化,如果對 vue腳手架配置不太瞭解的同學。可以看我上一篇文章 如何優雅的升級到webpack4,或者直接看 webpack3 vue腳手架註解

下面我先講講vue腳手架為我們做的一些優化,不喜歡看的請跳過,然後會講如何在優化的基礎上升華一下,內容從淺到深,但是所有的方法都經過樓主考證,內容較長,請自帶板凳瓜子。

vue-cli 腳手架自帶優化

babel

Babel 是一個 JavaScript 編譯器,能將 ES6 程式碼轉為 ES5 程式碼,讓你使用最新的語言特性而不用擔心相容性問題,並且可以通過外掛機制根據需求靈活的擴充套件。這裡我不講babel ,而是講官方用的外掛 transform-runtime,對應的外掛全名叫做 babel-plugin-transform-runtime,其作用是減少冗餘程式碼,到底是怎麼減少的呢?

例如在轉換 class extent 語法時會在轉換後的 ES5 程式碼裡注入 _extent 輔助函式用於實現繼承:

function _extent(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
  return target;
}
複製程式碼

這會導致每個使用了 class extent 語法的檔案都被注入重複的_extent 輔助函式程式碼,babel-plugin-transform-runtime 的作用在於不把輔助函式內容注入到檔案裡,而是注入一條匯入語句:

var _extent = require('babel-runtime/helpers/_extent');
複製程式碼

這樣能減小 Babel 編譯出來的程式碼的檔案大小。 注意:babel-plugin-transform-runtime 必須和 babel-runtime 需要配套使用

說來慚愧,樓主試了一下,把這個外掛去掉,生成檔案的hash和大小並沒有變化(汗,別砸,翻資料webpack 標準入門前端工程化-webpack篇之babel-polyfill與babel-runtime(三)上有寫,而且腳手架上有)。後來發現,樓主的程式碼中並沒有es6。後來換了一個大專案,做了對比

babel對比
可以發現,圖右邊是去掉外掛的。體積明顯大了一點。使用此外掛可以減少重複程式碼,縮小專案體積。

縮小檔案搜尋範圍

loader

使用 Loader 時可以通過 test 、 include 、 exclude 三個配置項來命中,對於我們的專案大部分都是 js,下面看看官方腳手架 js 的 babel-loader:

module.exports = {
    // ...
    module: {
        rules: [
            // ...
           {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
           },
        ]
    }
}
複製程式碼

由於通過 npm 安裝的第三方的庫,都是經過 webpack 打包 es5 化了,所以這裡就可以只對 include 包括的檔案使用 babel-loader 解析

注意了。由於 css、less 的引入是要插入到js中的,所以並不適用於這個(把 node_modules 排除在外)方法。說到這裡,多說一句,也是曾經很困擾我的 css 的 loader 解析順序,use 的 loader 解析順序跟陣列的位置是反著的,以 less 為例,具體來講

module.exports = {
    // ...
    module: {
        rules: [
            // ...
           {
                test: /\.less$/,
                // less 檔案的處理順序為先 less-loader 再 css-loader 再 vue-style-loader
                use: [
                    // style-loader 會把 CSS 程式碼轉換成字串後,注入到 JavaScript 程式碼中去,
                    'vue-style-loader',
                    // css-loader 會找出 CSS 程式碼中的 @import 和 url() 這樣的匯入語句,告訴 Webpack 依賴這些資源。同時還支援 CSS Modules、壓縮 CSS 等功能。處理完後再把結果交給 vue-style-loader 去處理。
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: config.dev.cssSourceMap
                        }
                    },
                    //通過 less-loader 把 less 原始碼轉換為 CSS 程式碼,再把 CSS 程式碼交給 css-loader 去處理。
                    {
                        loader: 'less-loader'
                    }
                ] 
            },
        ]
    }
}

複製程式碼

關於縮小範圍增加命中這個思想,還可以做很多事情,這裡只講了vue腳手架優化做的事情,更多配置請往後看,看我如何自定義的

node 選項

webpack 的官方腳手架裡面的node選項可以防止node包,還有 setImmediate 的 profill注入到程式碼中

node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
}
複製程式碼

好不好,看療效。那麼具體的療效怎麼樣呢,樓主同樣的程式碼,做了對比,效果如下:

node選項對比

通過對比可以看到,兩次打包css的hash值全部變了,js部分hash發生改變(這個打包沒看出js變化,但是另一個專案的部分js的hash變了)。總體打出來的包的體積相差不大。去掉node選項打包時間差別不明顯,所以用不用,見仁見智吧。我看create-react-app中也使用了,所以還是建議使用吧,知道更多的,可以留言區討論。

js、css 壓縮

css 壓縮這個就不多說了,大家都懂,

值得一提的是由於 UglifyJsPlugin 外掛升級到1.0之後有了 parallel選項,開啟了多執行緒壓縮

new UglifyJsPlugin({
  uglifyOptions: {
    compress: {
      warnings: false
    }
  },
  sourceMap: config.build.productionSourceMap,
  parallel: true  // 開啟多執行緒壓縮
})
複製程式碼

這兩個外掛都有配置項,合理配置可以優化專案。後面會講。

程式碼分割

程式碼分割就是將動態引入的程式碼分割成一個一個的程式碼塊(chunk),根據需求載入到html上。注意:要使用程式碼分割功能,在專案中要配合使用元件、路由懶載入的方式(可以通過import實現)

webpack4 的 mode 為 production 時,預設會對程式碼進行分割。樓主看了 webpack3 的程式碼分割方式是使用 CommonsChunkPlugin 外掛,目的就是分割出幾類程式碼:

  1. vendor 也就是第三方庫打包這裡。
  2. manifest 當編譯器開始執行、解析和對映應用程式時,它會保留所有模組的詳細要點。這個資料集合稱為 "Manifest"
  3. app 這個是程式碼中的公共部分

HashedModuleIdsPlugin

嗯 webpack 生成 js 的 hash 是如何計算我並不清楚,但是如果不用這個外掛的話,所有生成 js 的 hash 是一樣的,而且只要有一點點改動,所有檔案的 hash 值都會變化。那造成什麼樣的結果呢?

比如你只改了 b 頁面的 js 裡的一行程式碼,如果不用此外掛的話,所有頁面的 js 的 hash 全部會變化,瀏覽器要重新請求全部的js。效能浪費到令人髮指。而使用了 HashedModuleIdsPlugin 這個外掛,只有你改動的那個 chunk 的 hash會發生變化,其他不變,由於瀏覽器的快取機制,瀏覽器只重新請求改動的js。是不是很棒。而且上一小節對程式碼分割那裡的分割方式,也是為了把不經常變動的檔案單獨打包,hash 可以保持不變。

使用方法也很簡單

new webpack.HashedModuleIdsPlugin(),
複製程式碼

什麼?為什麼就算去掉 HashedModuleIdsPlugin 外掛 用腳手架第一次打包專案生成的 js 的 hash 不全部一樣,而且改動之後,也不是全部發生變化啊。這個也是樓主遇到的問題。樓主不用腳手架搭建的專案,js 的 hash 是一樣的,知道為什麼出現初始打包的 js hash 值為什麼不全部一樣的同學,歡迎評論區討論。

作用域提升(scope hoisting)

過去 webpack 打包時的一個取捨是將 bundle 中各個模組單獨打包成閉包。這些打包函式使你的 JavaScript 在瀏覽器中處理的更慢。相比之下,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者預編譯所有模組到一個閉包中,提升你的程式碼在瀏覽器中的執行速度。 個外掛會在 webpack 中實現以上的預編譯功能。

new webpack.optimize.ModuleConcatenationPlugin()
複製程式碼

這種連結行為被稱為“作用域提升(scope hoisting)

記住,此外掛僅適用於由 webpack 直接處理的 ES6 模組。在使用轉譯器(transpiler)時,你需要禁用對模組的處理(例如 Babel 中的 modules 選項)。

css 優化

由於css載入不會阻塞dom的解析,所以把css抽取出來。不佔用js的大小是一個明智的選擇 OptimizeCSSPlugin 外掛做的就是這個,並且程式碼複用,會減小css體積

new OptimizeCSSPlugin({
  cssProcessorOptions: config.build.productionSourceMap
    ? { safe: true, map: { inline: false } }
    : { safe: true }
}),
複製程式碼

總結

總體來講 webpack 為我們做的優化有

  1. babel-plugin-transform-runtime 外掛去除重複墊片程式碼
  2. module.rules 的 js 解析,使用 include 提高命中
  3. node 選項,防止 node 的自帶包(dgram、fs、net、tls、child_process)注入到我們的程式碼中
  4. js、css 壓縮,程式碼分割,公共部分抽離
  5. 維持打包後不變chunk的hash值不變
  6. 作用域提升(scope hoisting)
  7. css 抽離。公共部分抽離

大致就這樣了,有沒有講到的也請評論區提出,那麼如何在此基礎上做優化呢,這個也許是大家都很關心的問題。接下來我會在 《關於webpack優化,你需要知道的事(下篇)》講到。

相關文章