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

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

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

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

仍是太慢了,快快使用Webpack4react

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

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

注意區分一下開發環境和線上環境,開發環境啓用熱更新替換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 參數就能夠了git

 

2、開發環境不作無心義的操做

不少配置,在開發階段是不須要去作的,咱們能夠區分出開發和線上的兩套配置,這樣在須要上線的時候再全量編譯便可es6

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

 

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

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

 

4、代碼壓縮用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
             }
           }
         }),

 

5、css-loader使用0.15.0如下的版本

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

 

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

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

 

7、babel-loader開啓緩存

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

能夠加上cacheDirectory參數或使用 transform-runtime 插件試試

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


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

 

8、不須要打包編譯的插件庫換成全局<script>標籤引入的方式

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

能夠直接用標籤引入,而後在webpack配置裏使用 expose-loader  或 externalsProvidePlugin  提供給模塊內部使用相應的變量

// @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'
        }),

 

9、使用 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')
        }),

 

10、提取公共代碼

使用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']
    },

 

11、使用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'
                }
            }]
        }),

 

12、優化構建時的搜索路徑

在webpack打包時,會有各類各樣的路徑要去查詢搜索,咱們能夠加上一些配置,讓它搜索地更快

好比說,方便改爲絕對路徑的模塊路徑就改一下,以純模塊名來引入的能夠加上一些目錄路徑

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

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

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

                 }
             }]
        }

 

 

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

檢查一下代碼,看看是否是有不須要引入的模塊出如今代碼裏

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

 

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

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

 new webpack.optimize.ModuleConcatenationPlugin({

        })

 

十5、使用noParse

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

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

 

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

這個算是能夠減少模塊的體積吧,在必定程度上也是爲用戶考慮的,使用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');
    });
});

 

十7、以模塊化來引入

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

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

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

 

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

固然還有不少方法沒整理出,這些方法是有使用場景的,並非每一個都須要用,須要在本身的項目中嘗試,結合配置它的複雜性和帶來的效應來權衡。

相關文章
相關標籤/搜索