1. 程式人生 > >Webpack打包構建太慢了?試試幾個方法

Webpack打包構建太慢了?試試幾個方法

hash vid put es5 階段 ade new contex 版本

Webpack是個很流行的打包工具,但其打包速度卻一直被吐槽著

如果不用上一些打包的優化建議,單單打包兩三個文件就能花上好幾秒,放上幾十個入口文件依賴幾百上千個包的話,幾分鐘十幾分鐘妥妥的

本文整理了常見的一些方法,部分使用之後就看到了很大改善,部分沒什麽明顯的變化,也可能是項目規模還不夠大,先記錄一下方法也好

一、使用監聽模式或熱更新熱替換

webpack支持監聽模式,此時需要重新編譯時就可以進行增量構建,增量構建是很快的,基本不到一秒或幾秒之內就能重新編譯好

註意區分一下開發環境和線上環境,開發環境啟用熱更新替換

// 開發環境設置本地服務器,實現熱更新
    devServer: {
        contentBase: path.resolve(__dirname, 
‘static‘), // 提供給外部訪問 host: ‘0.0.0.0‘, port: 8388, // 允許開發服務器訪問本地服務器的包JSON文件,防止跨域 headers: { ‘Access-Control-Allow-Origin‘: ‘*‘ }, // 設置熱替換 hot: true, // 設置頁面引入 inline: true }, // 文件輸出配置 output: {
// 設置路徑,防止訪問本地服務器相關資源時,被開發服務器認為是相對其的路徑 publicPath: ‘http://localhost:8188/dist/js/‘, }, // 插件配置 plugins: [ // 熱更新替換 new webpack.HotModuleReplacementPlugin() ]

線上環境的編譯,加個 --watch 參數就可以了

二、開發環境不做無意義的操作

很多配置,在開發階段是不需要去做的,我們可以區分出開發和線上的兩套配置,這樣在需要上線的時候再全量編譯即可

比如說 代碼壓縮、目錄內容清理、計算文件hash、提取CSS文件等

三、選擇一個合適的devtool屬性值

配置devtool可以支持使用sourceMap,但有些是耗時嚴重的,這個得多試試

四、代碼壓縮用ParallelUglifyPlugin代替自帶的 UglifyJsPlugin插件

自帶的JS壓縮插件是單線程執行的,而webpack-parallel-uglify-plugin可以並行的執行,在我的小demo中使用後,速度直接從25s變成了14s

      new webpack.optimize.UglifyJsPlugin({
            sourceMap: true,
            compress: {
                warnings: false
            }
        }),



ParallelUglifyPlugin = require(‘webpack-parallel-uglify-plugin‘)

new ParallelUglifyPlugin({
           cacheDir: ‘.cache/‘,
           uglifyJS:{
             output: {
               comments: false
             },
             compress: {
               warnings: false
             }
           }
         }),

五、css-loader使用0.15.0以下的版本

聽聞這個版本以上的速度會慢許多,不過在我的小demo中還沒看到明顯變化

六、使用fast-sass-loader代替sass-loader

fast-sass-loader可以並行地處理sass,在提交構建之前會先組織好代碼,速度也會快一些

七、babel-loader開啟緩存

babel-loader在執行的時候,可能會產生一些運行期間重復的公共文件,造成代碼體積大冗余,同時也會減慢編譯效率

可以加上cacheDirectory參數或使用 transform-runtime 插件試試

// webpack.config.js
use: [{
                loader: ‘babel-loader‘,
                options: {
                    cacheDirectory: true
                }]


// .bablerc
{
    "presets": [
        "env",
        "react"
    ],
    "plugins": ["transform-runtime"]
}

八、不需要打包編譯的插件庫換成全局<script>標簽引入的方式

比如jQuery插件,react, react-dom等,代碼量是很多的,打包起來可能會很耗時

可以直接用標簽引入,然後在webpack配置裏使用 expose-loaderexternalsProvidePlugin 提供給模塊內部使用相應的變量

// @1
use: [{
                loader: ‘expose-loader‘,
                options: ‘$‘
            }, {
                loader: ‘expose-loader‘,
                options: ‘jQuery‘
            }]


// @2
externals: {
        jquery: ‘jQuery‘
    },


// @3
        new webpack.ProvidePlugin({
            $: ‘jquery‘,
            jQuery: ‘jquery‘,
            ‘window.jQuery‘: ‘jquery‘
        }),

九、使用 DllPlugin 和 DllReferencePlugin

這種方式其實和externals是類似的,主要用於有些模塊沒有可以在<script>標簽中引入的資源(純npm包)

Dll是動態鏈接庫的意思,實際上就是將這些npm打包生成一個JSON文件,這個文件裏包含了npm包的路徑對應信息

這兩個插件要一起用

首先,新建一個dll.config.js配置文件,先用webpack來打包這個文件

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

module.exports = {
    output: {
        // 將會生成./ddl/lib.js文件
        path: path.resolve(__dirname, ‘ddl‘),
        filename: ‘[name].js‘,
        library: ‘[name]‘,
    },
    entry: {
        "lib": [
            ‘react‘,
            ‘react-dom‘,
            ‘jquery‘
            // ...其它庫
        ],
    },
    plugins: [
        new webpack.DllPlugin({
            // 生成的映射關系文件
            path: ‘manifest.json‘,
            name: ‘[name]‘,
            context: __dirname,
        }),
    ],
};

技術分享

manifest.json文件中就是相應的包對應的信息

然後在我們的項目配置文件中配置DllReferencePlugin 使用這個清單文件

    // 插件配置
    plugins: [
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require(‘./manifest.json‘)
        }),

十、提取公共代碼

使用CommonsChunkPlugin提取公共的模塊,可以減少文件體積,也有助於瀏覽器層的文件緩存,還是比較推薦的

 // 提取公共模塊文件
        new webpack.optimize.CommonsChunkPlugin({
            chunks: [‘home‘, ‘detail‘],
            // 開發環境下需要使用熱更新替換,而此時common用chunkhash會出錯,可以直接不用hash
            filename: ‘[name].js‘ + (isProduction ? ‘?[chunkhash:8]‘ : ‘‘),
            name: ‘common‘
        }),




// 切合公共模塊的提取規則,有時後你需要明確指定默認放到公共文件的模塊
// 文件入口配置
    entry: {
        home: ‘./src/js/home‘,
        detail: ‘./src/js/detail‘,
        // 提取jquery入公共文件
        common: [‘jquery‘, ‘react‘, ‘react-dom‘]
    },

十一、使用HappyPack來加速構建

HappyPack會采用多進程去打包構建,使用方式還是蠻簡單的,但並不是支持所有的loader

首先引入,定義一下這個插件所開啟的線程,推薦是四個,其實也可以直接使用默認的就行了

HappyPack = require(‘happypack‘),
    os = require(‘os‘),
    happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

然後在module的規則裏改動一下,引入它,其中 id是一個標識符

{
            test: /\.jsx?$/,
            // 編譯js或jsx文件,使用babel-loader轉換es6為es5
            exclude: /node_modules/,
            loader: ‘HappyPack/loader?id=js‘
            // use: [{
            //     loader: ‘babel-loader‘,
            //     options: {

            //     }
            // }]
        }

然後我們調用插件,設置匹配的id,然後相關的配置可以直接把use:的規則部分套在loaders上

new HappyPack({
            id: ‘js‘,
            loaders: [{
                loader: ‘babel-loader‘,
                options: {
                    // cacheDirectory: true
                }
            }]
        }),

要註意的第一點是,它對file-loaderurl-loader支持不好,所以這兩個loader就不需要換成happypack了,其他loader可以類似地換一下

要註意的第二點是,使用ExtractTextWebpackPlugin提取css文件也不是完全就能轉換過來,所以需要小小的改動一下,比如

module: {
        rules: [{
            test: /\.css$/,
            // loader: ‘HappyPack/loader?id=css‘
            // 提取CSS文件
            use: cssExtractor.extract({
                // 如果配置成不提取,則此類文件使用style-loader插入到<head>標簽中
                fallback: ‘style-loader‘,
                use: ‘HappyPack/loader?id=css‘
                // use: [{
                //         loader: ‘css-loader‘,
                //         options: {
                //             // url: false,
                //             minimize: true
                //         }
                //     },
                //     // ‘postcss-loader‘
                // ]
            })
        }, {
            test: /\.scss$/,
            // loader: ‘HappyPack/loader?id=scss‘
            // 編譯Sass文件 提取CSS文件
            use: sassExtractor.extract({
                // 如果配置成不提取,則此類文件使用style-loader插入到<head>標簽中
                fallback: ‘style-loader‘,
                use: ‘HappyPack/loader?id=scss‘
                // use: [
                //     ‘css-loader‘,
                //     // ‘postcss-loader‘,
                //     {
                //         loader: ‘sass-loader‘,
                //         options: {
                //             sourceMap: true,
                //             outputStyle: ‘compressed‘
                //         }
                //     }
                // ]
            })
        }

因為它是直接函數調用的,我們就放到裏層的use規則就行了,然後配置插件即可

plugins: [
        new HappyPack({
            id: ‘css‘,
            loaders: [{
                loader: ‘css-loader‘,
                options: {
                    // url: false,
                    minimize: true
                }
            }]
        }),
        new HappyPack({
            id: ‘scss‘,
            loaders: [{
                ‘loader‘: ‘css-loader‘
            }, {
                loader: ‘fast-sass-loader‘,
                options: {
                    sourceMap: true,
                    outputStyle: ‘compressed‘
                }
            }]
        }),

十二、優化構建時的搜索路徑

在webpack打包時,會有各種各樣的路徑要去查詢搜索,我們可以加上一些配置,讓它搜索地更快

比如說,方便改成絕對路徑的模塊路徑就改一下,以純模塊名來引入的可以加上一些目錄路徑

還可以善於用下resolve alias別名 這個字段來配置

還有exclude等的配置,避免多余查找的文件,比如使用babel別忘了剔除不需要遍歷的

{
            test: /\.jsx?$/,
            // 編譯js或jsx文件,使用babel-loader轉換es6為es5
            exclude: /node_modules/,
             use: [{
                 loader: ‘babel-loader‘,
                 options: {

                 }
             }]
        }

十三、(導出編譯JSON文件)理一下打包構建涉及的模塊,分析看有哪些包是不需要打包的,只打包需要的模塊

檢查一下代碼,看看是不是有不需要引入的模塊出現在代碼裏

webpack編譯時加上參數 --json > stat.json 後,可以上傳到 webpack-analyse webpack-visualizer 等分析站點上,看看打包的模塊信息

十四、使用ModuleConcatenationPlugin插件來加快JS執行速度

這是webpack3的新特性(Scope Hoisting),其實是借鑒了Rollup打包工具來的,它將一些有聯系的模塊,放到一個閉包函數裏面去,通過減少閉包函數數量從而加快JS的執行速度

 new webpack.optimize.ModuleConcatenationPlugin({

        })

十五、使用noParse

webpack打包的時候,有時不需要解析某些模塊的依賴(這些模塊並沒有依賴,或者並根本就沒有模塊化),我們可以直接加上這個參數,直接跳過這種解析

module: {
    noParse: /node_modules\/(jquey\.js)/
  }

十六、使用異步的模塊加載

這個算是可以減小模塊的體積吧,在一定程度上也是為用戶考慮的,使用require.ensure來設置哪些模塊需要異步加載,webpack會將它打包到一個獨立的chunk中,

在某個時刻(比如用戶點擊了查看)才異步地加載這個模塊來執行

$(‘.bg-input‘).click(() => {
    console.log(‘clicked, loading async.js‘)

    require.ensure([], require => {

        require(‘./components/async2‘).log();
        require(‘./components/async1‘).log();
        console.log(‘loading async.js done‘);
    });
});

十七、以模塊化來引入

有些模塊是可以以模塊化來引入的,就是說可以只引入其中的一部分,比如說lodash

// 原來的引入方式
 import {debounce} from ‘lodash‘;

//按模塊化的引入方式
import debounce from ‘lodash/debounce‘;

主要是整理過來的,試用了幾個方法,首次編譯的速度可以從之前半分多鐘減小到十秒左右了,當然,開啟了熱更新替換後簡直美不可言

當然還有很多方法沒整理出,這些方法是有使用場景的,並不是每個都需要用,需要在自己的項目中嘗試,結合配置它的復雜性和帶來的效應來權衡。

Webpack打包構建太慢了?試試幾個方法