博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
webpack调优总结
阅读量:6291 次
发布时间:2019-06-22

本文共 6414 字,大约阅读时间需要 21 分钟。

欢迎关注我的公众号睿Talk,获取我最新的文章:

clipboard.png

一、前言

webpack 的出现为前端开发带来翻天覆地的变化,无论你是用 React,Vue 还是 Angular,webpack 都是主流的构建工具。我们每天都跟它打交道,但却很少主动去了解它,就像写字楼里的礼仪小姐姐,既熟悉又陌生。随着项目复杂度的上升,打包构建的时间会越来越长。终于有一天,你发现npm run dev后,去泡个茶,上了个厕所,跟同事 bb 一轮后回到座位,项目还没构建完的时候,你就会下定决心好好了解下这个熟悉的陌生人。

这次优化的目标主要有两个:

  • 加快编译构建速度
  • 减少页面加载的时间

现状是每次开发模式构建,大概要花 120 秒;生产模式构建,大概要花 300 秒。项目总共有将近 150 个 chunk。

如果你对 webpack 的工作原理感兴趣,可以看看我写的另一篇文章

二、加快编译构建速度

有 2 种方式可以加快编译的速度,分别是减少每次打包的文件数目,和并行的去执行打包任务。这里用到了 2 个 webpack 插件:

  • DllPlugin(减少每次打包的文件数目)
  • HappyPack(并行的去执行打包任务)

下面对这两个插件作详细的介绍。

  • DllPlugin

dll 是 Dynamic Link Library(动态链接库)的缩写,是 Windows 系统共享函数库的一种方式。将一些比较少改变的库和工具,比如 React、React-DOM,事先独立打包成一个 chunk,以后每次构建的时候再直接导入,就不用每次都对这些文件打包了。这里有 2 个分解动作:

  • 独立打包 dll
  • 导入 dll

使用 DllPlugin 可以独立打包 dll,具体的配置如下:

const path = require('path');const webpack = require('webpack');const UglifyJSPlugin = require('uglifyjs-webpack-plugin');const env = process.env.NODE_ENV;module.exports = {    entry: {        vendor: ['react', 'react-dom', 'react-router', 'redux', 'react-redux', 'redux-thunk'],    },    output: {        filename: '[name]_dll_[chunkhash].js',        path: path.resolve(__dirname, 'dll'),        library: '_dll_[name]',    },    resolve: {        mainFields: ['jsnext:main', 'browser', 'main'],    },    plugins: [        new webpack.DllPlugin({            name: '_dll_[name]',            path: path.join(__dirname, 'dll', '[name].manifest.json'),        }),        new webpack.DefinePlugin({            'process.env': {                NODE_ENV: JSON.stringify(env),            },        }),        new UglifyJSPlugin({            cache: true,            parallel: true,            exclude: [/node_modules/],            uglifyOptions: {                compress: {                    warnings: false,                    drop_console: true,                    collapse_vars: true,                    reduce_vars: true,                },                output: {                    beautify: false,                    comments: false,                },            },        }),    ],};

DllPlugin 网上有一些例子,但都不完美,体现在以下 2 点:

  • 没有压缩代码
  • 没有 hash,当依赖更新时无法通知浏览器更新缓存

第 1 点比较好处理,加上 DefinePlugin 和 UglifyJSPlugin 就可以了。处理第 2 点的时候,除了在 output 加上 chunkhash,在引入 dll 的时候需要做一些额外的操作,下文会讲解。

这时在 package.json 加上一个命令,npm run dll一下就会生成一个类似这样的文件:vendor_dll_be1f5270e490dcb25f.js

{    ...    "scripts": {        "dll": "cross-env NODE_ENV=production webpack --config webpack.dll.js --progress"    }    ...}

dll 生成后,就要在构建的配置文件里将其引入,这时候就用到 DllReferencePlugin 和 AddAssetHtmlPlugin,配置如下

const fs = require('fs');const path = require('path');const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');const files = fs.readdirSync(path.resolve(__dirname, 'dll'));const vendorFiles = files.filter(file => file.match(/vendor_dll_\w+.js/));const vendorFile = vendorFiles[0];module.exports = {    ...    plugins: [        ...        new webpack.DllReferencePlugin({            manifest: require('./dll/vendor.manifest.json'),        }),        new AddAssetHtmlPlugin({            filepath: path.resolve(__dirname, `dll/${vendorFile}`),            includeSourcemap: false        }),        ...    ],};

DllReferencePlugin 的作用是将打包好的dll文件传入构建的代码里面,而 AddAssetHtmlPlugin 的作用是在生成的 html 文件中加入 dll 文件的 script 引用。网上的例子一般是将 dll 的文件名直接写死的,但由于在上一步构建 dll 的时候加入了 hash,所以要通过 fs 读取真实的文件名,再注入到 html 中。

  • HappyPack

大家都知道 webpack 是运行在 node 环境中,而 node 是单线程的。webpack 的打包过程是 io 密集和计算密集型的操作,如果能同时 fork 多个进程并行处理各个任务,将会有效的缩短构建时间,HappyPack 就能做到这点。下面是它的相关配置:

const HappyPack = require('happypack');const os = require('os');const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });module.exports = {    ...    module: {        rules: [            {                test: /\.js$/,                include: [                    path.resolve(__dirname, 'src')                ],                use: [{                    loader: 'happypack/loader?id=happyBabel',                }],            },            {                test: /\.css$/,                include: [                    path.resolve(__dirname, 'src')                ],                use: ExtractTextPlugin.extract({                    fallback: 'style-loader',                    use: ['happypack/loader?id=happyCss'],                }),            }        ],        ...        plugins: [            ...            new HappyPack({                id: 'happyBabel',                loaders: [{                    loader: 'babel-loader',                    options: {                        cacheDirectory: true,                        presets: ['react', 'es2015', 'stage-0'],                        plugins: ['add-module-exports', 'transform-decorators-legacy'],                    },                }],                threadPool: happyThreadPool,                verbose: true,            }),            new HappyPack({                id: 'happyCss',                loaders: ['css-loader', 'postcss-loader'],                threadPool: happyThreadPool,                verbose: true,            }),        ],

其中happyThreadPool是根据cpu数量生成的共享进程池,防止过多的占用系统资源。

三、减少页面加载时间

对于 web 应用来说,减少页面加载时间一般有 2 种方法。一是充分利用浏览器缓存,减少网络传输的时间。另外就是减少 JS 运行的时间,通过 SSR 等方式实现。利用 webpack 能有效的抽取出共享的资源,提高缓存的命中率。这里用到的插件除了上文提到的 DllPlugin 外,还有 CommonsChunkPlugin,相关配置如下:

module.exports = {    entry: {        vendor: ['zent','lodash']        app: ['babel-polyfill', 'react-hot-loader/patch', './src/main.js']    },    ...    plugins: [        ...        new webpack.optimize.CommonsChunkPlugin({            names: ['vendor'],            minChunks: Infinity,        }),        new webpack.optimize.CommonsChunkPlugin({            name: 'app',            minChunks: 3,            children: true,            async: 'chunk-vendor',        }),        new webpack.optimize.CommonsChunkPlugin({            names: ['manifest'],            minChunks: Infinity,        }),        new webpack.HashedModuleIdsPlugin(),        new InlineManifestWebpackPlugin({            name: 'webpackManifest',        }),        ...    ],};

插件的第一部分是将 vendor 构建一个独立包;第二部分是抽取 app 入口文件 code split 之后所有子模块的公共模块,进一步减少子模块的大小;第三部分将 webpack 的启动代码独立打成一个 manifest 包,配合 HashedModuleIdsPlugin 可以保证每次构建的时候只要 vendor 内容不变,它的 hash 就不变。InlineManifestWebpackPlugin 的作用是将 manifest 文件内联到 html 模板中,减少一次网络请求。

四、总结

经过上述的优化之后,开发模式构建只需要 60 秒左右;生产模式构建只需要 150 秒左右,时间减少一半!缓存命中方面,可以做到基础模块(React等)和比较少变动的模块(组件库)分离出来,当组件库更新的时候依然可以使用基础模块的缓存(通过 dll 实现)。

通过这次的优化,对 webpack 的理解加深了不少,取得了比较不错的优化效果。另外也学习了 loader 和 plugin 的工作原理,有机会另写一篇文章分享。

如果你对 webpack 的工作原理感兴趣,可以看看我写的另一篇文章

转载地址:http://jrkta.baihongyu.com/

你可能感兴趣的文章