webpack 編譯優化

呆裡呆氣1發表於2018-02-08

如果是多頁專案,修改入口 entry,區分開發和生產環境

用我正在開發的專案舉例,修改之前 entry 沒有區分開發和生產環境,入口物件裡包含 pages 目錄下所有頁面的入口 js

webpack.base.conf.js

entry: utils.getEntry()
複製程式碼

utils.js

exports.getEntry = function () {
    var entry = {}
    Object.keys(config.pages).forEach(function (name) {
        entry[name] = config.pages[name].entry
    })
    return entry
}
複製程式碼

config.pages 是前面封裝好的一個物件,儲存著所有頁面的資訊,在這裡交給 Object.keys() 方法去列舉它的屬性並返回一個陣列,再將該陣列交給 forEach 迴圈遍歷,迴圈裡的 name 就是 config.pages 物件的 keyconfig.pages 如下:

{
    home: {
        name: 'home',
        entry: 'F:/path.../src/pages/home/main.js',
        template: 'F:/path.../src/pages/home/index.html'
    },
    'product-details': {
        name: 'product-details',
        entry: 'F:/path.../src/pages/product-details/main.js',
        template: 'F:/path.../src/pages/product-details/index.html'
    }
    ...
}
複製程式碼

修改之後:

utils.js

exports.getEntry = function () {
    var entry = {}
    Object.keys(config.pages).forEach(function (name) {
        if (process.env.NODE_ENV === 'production') {
            entry[name] = config.pages[name].entry
        } else {
            var target_pages = !!JSON.parse(process.env.npm_config_argv).original[3] ? JSON.parse(process.env.npm_config_argv).original[3] : 'home'
            var target_pages_arr = target_pages.split('!')
            target_pages_arr.forEach(function (target_name) {
                if (name === target_name) entry[name] = config.pages[name].entry
            })
        }
    })
    return entry
}
複製程式碼

首先通過 process.env.NODE_ENV 判斷當前環境,如果是開發環境,就將所有頁面的入口 js 新增進 entry (這裡視你的情況而定,我開發的這個專案需要將所有的頁面打包上線,所以 entry 裡新增了所有頁面的入口 js),如果是生產環境,則從執行的命令(npm run dev -- pageName | npm run dev == pageNameA!pageNameB!pageNameC)中獲取當前正在開發的頁面,再將這些頁面的入口 js 新增進 entry,這樣可以大大減少開發時每次編譯所消耗的時間。

使用 HappyPack

webpack 構建時需要解析和處理大量的檔案,但執行在 Node.js 上的 webpack 是單執行緒模型的,這就導致了 webpack 構建的時間開銷比較大,專案越大問題越嚴重。HappyPack 可以幫我們分解任務並管理執行緒,它將任務分解給多個子程式併發執行,子程式處理完之後再將結果傳送給主程式,我們只需要按照它的用法和配置正確接入即可。

webpack.base.conf.js

const HappyPack = require('happypack')
const happyThreadPool = HappyPack.ThreadPool({ size: 5 })

module.exports = {
    module: {
        rules: [{
            test: /\.js$/,
            use: ['happypack/loader?id=babel'],
            include: [resolve('src')]
        }]
    },
    plugins: [
        new HappyPack({
            id: 'babel',
            loaders: ['babel-loader?cacheDirectory'],
            threadPool: happyThreadPool
        })
    ]
}
複製程式碼
  • 對於 Loader,將檔案的處理交給 happypack/loaderhappypack/loader後面的 querystring -- ?id=babel 會告訴它該選擇外掛中的哪個 HappyPack 例項來處理這些檔案。

  • 在外掛中 new 了一個 HappyPack 的例項,作用是告訴 happypack/loader 怎麼處理 js 字尾的檔案, id 的值和 Loader 中的 ?id=babel 對應, loaders 屬性的值即原 Loader 配置,注意 loaders 屬性需是陣列型別。

  • HappyPack 例項中的 threadPool 參數列示共享程式池,多個 HappyPack 例項都使用同一個共享程式池中的子程式去處理任務,可以防止資源佔用過多,該例中構造出來的共享程式池 (const happyThreadPool = HappyPack.ThreadPool({ size: 5 })) 包含5個子程式。

配置完之後安裝新依賴:

npm i -D happypack
複製程式碼

安裝完之後重新進行構建即可。

構建時控制檯會輸出類似於下面的日誌:

webpack 編譯優化

這是 happypack 輸出的,如果想關閉它,在例項化 HappyPack 外掛時增加 verbose 引數並將它設定為 false 即可。

使用 DllPlugin 和 DllReferencePlugin

DllPlugin 和 DllReferencePlugin 的作用原理類似於 windows 系統中的動態連結庫,即 .dll 字尾的檔案。動態連結庫中包含著供其它模組呼叫的函式和資料。

在 Web 專案中,可以將原始碼中依賴的所有基礎模組提取出來,統一打包到一個單獨的動態連結庫裡,當需要匯入的模組存在於這個提前生成的動態連結庫裡時,該模組就不能被再次打包,而是直接從動態連結庫裡面獲取。

如果動態連結庫裡包含較多複用率比較高的模組,便會大大減少專案構建所需要的時間,因為這些模組被編譯一次之後就不會再被重複編譯,在以後的構建過程中用到這些模組的話就會直接從動態連結庫中獲取。

動態連結庫中包含的大多是一些常用的第三方模組,如 vue、vuex 等,只要不升級這些模組,就不用重新編譯動態連結庫。

配置方法

步驟1 建立配置檔案 webpack.dll.config.js

首先新建一個配置檔案 webpack.dll.config.js,該檔案的位置視你的專案結構而定,最好和其它 webpack 配置檔案放在同一目錄下,該檔案內容如下:

const path = require("path")
const webpack = require("webpack")

module.exports = {
    entry: {
        vendor: [
            'iscroll',
            'mint-ui',
            'vue/dist/vue.common.js',
            'vue-iscroll-view',
            'vue-lazyload',
            'vue-scroller',
            'whatwg-fetch'
        ]
    },
    output: {
        path: path.join(__dirname, '../static/js'),
        filename: '[name].dll.js',
        library: '[name]_library'
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, '..', '[name]-manifest.json'),
            name: '[name]_library',
            context: __dirname
        })
    ]
};
複製程式碼

entry.vendor 是要放進動態連結庫的模組的陣列。

output.path 表示輸出檔案放置的目錄。

output.filename 表示輸出的動態連結庫檔名稱,[name] 代表當前動態連結庫的名稱,拿到的是 entry 配置項的 key,這裡就是 vendor。也可以生成多個動態連結庫,將模組按型別進行區分,比如可以將專案依賴的第三方模組 vuevuex 等放入一個庫中,將依賴的 polyfill 放入另外一個庫中,這時 entry 就是下面這個樣子:

entry: {
    vue: [
        'mint-ui',
        'vue/dist/vue.common.js',
        ...
    ],
    polyfill: [
        'whatwg-fetch',
        ...
    ]
}
複製程式碼

output.library 用於存放動態連結庫的全域性變數名稱,比如對於 vendor 這個庫來說名稱就是 vendor_library

例項化 webpack.DllPlugin 時傳遞的引數 path 表示描述動態連結庫的 *-manifest.json 檔案輸出的路徑。

引數 name 表示動態連結庫的全域性變數名稱,需和 output.library 的值保持一致。該欄位的值也是輸出的 *-manifest.json 中 name 欄位的值。

引數 context 是 manifest 檔案中請求的上下文,選填,預設是當前 webpack 檔案的上下文。

步驟2 生成動態連結庫

配置好 webpack.dll.config.js 之後執行命令 webpack --config build/webpack.dll.config.js 就可以構建動態連結庫檔案了,我們將這句命令加到 package.json 的 scripts 裡:

"scripts": {
    "build:dll": "webpack --config build/webpack.dll.config.js"
}
複製程式碼

執行 npm run build:dll 即可。

執行完之後就會生成 vendor.dll.js 和 vendor-manifest.json,vendor.dll.js 在 static 目錄下的 js 資料夾裡,它就是“動態連結庫檔案”,vendor-manifest.json 在專案的根目錄下。

步驟3 引用動態連結庫

配置 DllReferencePlugin 外掛,使用 vendor-manifest.json 來引用動態連結庫。

webpack.base.conf.js

plugins: [
    ...
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require('../vendor-manifest.json')
    })
]
複製程式碼

context 需與 Dllplugin 裡 context 引數所指向的上下文保持一致。 manifest 引數用來引入之前生成的 vendor-manifest.json。

步驟4 頁面模板檔案引入動態連結庫

在頁面的模板檔案裡手動引入動態連結庫檔案。

<script src="./static/js/vendor.dll.js"></script>
複製程式碼

到這一步所有配置就已完成,根據動態連結庫中包含的模組數量和複用率不同,構建所減少的時間也會有所不同。

PS1

如果動態連結庫所包含的模組有所變化,就需要重新構建生成一份動態連結庫檔案。

PS2

生成的 js 檔案是未經壓縮的,可以在 webpack.dll.config.js 中新增外掛將生成的動態連結庫檔案進行壓縮:

plugins: [
    ...
    new webpack.optimize.UglifyJsPlugin({
        compress: {
        warnings: false
        }
    })
]
複製程式碼

babel-loader 使用 cacheDirectory

'babel-loader?cacheDirectory'

用於快取 babel 的編譯結果,加快重新編譯的速度。

縮小檔案的查詢範圍

module: {
    rules: [{
        test: /\.js$/,
        loader: 'babel-loader?cacheDirectory',
        include: path.resolve(__dirname, '../src')
    }]
}
複製程式碼

include 也可以是陣列,路徑視具體專案結構而定。用於命中指定目錄下的檔案,加快 webpack 的搜尋速度。

相關文章