用实验的思路优化webpack4项目编译速度

avatar
SugarTurboS Club @SugarTurboS

最近开发的时候遇到一个问题:当项目越来越大的时候,webpack构建和编译的速度变得很慢。尽管webpack4官方宣称速度提高了90%以上,但实际使用的时候感觉速度和webpack2也差不多。我实在受不了热加载的时候要等几秒才能编译好,于是就开始了优化之路。
最终优化的效果不错,提速达到了80%以上。一路上按着以前做实验的思维去优化,经历了各种问题,相比于分享解决问题的方法,我反而更想分享下解决问题的思路。

先量化目标

一般做实验之前,我们都要先定一个目标。
但是问题来了,虽然感觉很慢,但我不知道具体花了多少时间,慢是慢在哪里,所以也没办法知道我们可以优化到什么程度,优化也无从下手了。 于是首要的任务就是获得webpack编译过程的信息。
在webpack的官方文档中找了一大圈后,发现了配置中devServer.stats属性可以获得编译过程的完整信息

stats: {
    timings: true,
    modules: false,
    assets: false,
    entrypoints: false,
    assetsSort: 'field',
    builtAt: false,
    cached: false,
    cachedAssets: false,
    children: false,
    chunks: false,
    chunkGroups: false,
    chunkModules: false,
    chunkOrigins: false,
    performance: true,
    errors: true,
    warnings: true,
},

配成这样之后得到了构建和编译的时间:

可以看到构建时间是40s,改动代码后编译的时间是5.4s。
既然webpack4宣称可以提速90%,那目前的情况肯定是有某些地方配置不当,我们就先定个目标,将编译时间优化到1s以下。

猜测可能影响速度的因素

目前的项目是多入口的项目,每个入口之间没有太多的关联,但是却放在了一起打包和编译。我们平时开发的时候如果每次只编译一个入口的文件会不会快一点呢?
原本有三个入口,只加载一个入口的时候可以看到:

构建和编译的速度都加快了。变成了30s和3.4s。
既然有效,就写几个命令来方便开发不同的入口:

"scripts": {
    "dev": "webpack-dev-server  --config ./webpack.config/dev.js --hot --inline",
    "teacher": "app=teacher webpack-dev-server  --config ./webpack.config/dev.js --hot --inline",
    "student": "app=student webpack-dev-server  --config ./webpack.config/dev.js --hot --inline",
    "home": "app=home webpack-dev-server  --config ./webpack.config/dev.js --hot --inline",
    }

在package.json中dev命令前加了一个app=XXX的参数

const teacherEntry = {
    ueditor: [
        'babel-polyfill',
        './src/common/UEditor/ueditor.config.js',
        './src/common/UEditor/ueditor.all.js',
        './src/common/UEditor/kityformula-plugin/addKityFormulaDialog.js',
        './src/common/UEditor/kityformula-plugin/getKfContent.js',
        './src/common/UEditor/kityformula-plugin/defaultFilterFix.js'
    ],
    teacher: ['./src/app/teacher/index.js'],
    teacherLogin: './src/app/teacherLogin/js/teacherLogin.js'
};
const studentEntry = {
    student: ['babel-polyfill', './src/app/student/index.js'],
    studentLogin: './src/app/studentLogin/js/studentLogin.js'
};
const homeEntry = {
    home: './src/app/home/index.js'
};
const entryObj = {
    teacher: teacherEntry,
    student: studentEntry,
    home: homeEntry
};

const entry = process.env.app
    ? entryObj[process.env.app]
    : Object.assign({}, teacherEntry, studentEntry, homeEntry);

然后在webpack.base.config.js中加上了根据process.env.app的值来加载不同入口的代码,这样就实现开发时跑不同的命令加载不同入口的效果了。

检查webpack config中是否有影响性能的配置

接下来就用控制变量的方法去逐个去掉webpack的loader和plugin,然后观察减少的时间了。

new WriteFilePlugin({
    test: /^((?!hot-update).)*$/
}),

发现用到了一个写入文件的插件,将所有文件都写到出口了,但实际上只需要写入html文件就行了,于是改成

new WriteFilePlugin({
    test: /\.html$/,
    useHashIndex: true
}),

改后构建和编译时间变成了30s和3s,有了一点点优化。
然后发现babel编译js的时候没有忽略掉node_modules目录。

{
    test: /\.js$/,
    include: /(src|node_modules\/flv.js)/,
    exclude: /(node_modules)/,
    loader: 'babel-loader'
},

加上后发现时间变成了27s和2.2s,已经是一开始的一半了。

对比别人的项目看有没有可以借鉴的地方

配置看得差不多了,没有再发现有问题的地方了,于是开始google查找别人的优化方法。
后来找到了一个类似的项目用了happypack(多线程编译)和cache-loader(缓存),感觉优化效果还不错:

new HappyPack({
    id: 'babel', // 对于loaders id
    loaders: ['cache-loader', 'babel-loader?cacheDirectory'], // 是用babel-loader解析
    threadPool: happyThreadPool,
    verboseWhenProfiling: true // 显示信息
}),

加上了happypack和cache-loader之后可以看到第一次构建速度有了很大的提升,减少到了13.5s!
但是编译的速度基本没什么变化,还是2s。革命尚未成功,同志仍需努力啊。

观察编译过程的信息,寻找优化点

要进一步优化编译的速度,只好去分析编译的过程到底发生了什么问题了。
将stats中的assets设置为true,观察编译的文件:

发现每一个js文件都有一个相同大小的.map文件,这样岂不是会花费一倍的时间!?于是赶紧谷歌查一下.map文件是何方神圣。 后来发现它来自于webpack配置中的devtool,主要用于调试程序,官方配置说明如下:

原来项目中一直是用了最慢的source-map!于是赶紧修改配置:

devtool: 'cheap-module-eval-source-map'

改完后试了一下,构建和编译时间缩短为11s和0.8s。

终于将编译速度降到了1s一下,完成了最初定下的目标。

总结

经过这一路的优化之后,深感webpack的配置博大精深。每个项目可能都有不同的问题影响着webpack的速度,所以在此分享定位问题的思路,希望能帮助遇到类似问题的朋友一步步找到问题,提高速度。

Author: Brady