内容导读:
序言:
webpack对于前端的同学总是熟悉又陌生,熟悉的是几乎每个框架都有 webpack 身影,而且你也可以根据自己的需求独自搭建一个适合自己的框架结构,说到最后,其实都为了一个目的,快和简单,为了每次编译快一点,为了每次打包快一点,以及能让设计的系统更适合自己的所期望的理想架构,记得之前看 react 文档是作者说的一句话:不要为了用而用,再恰当时机使用才能得到最好的效果。由于之前也接触过 webpack ,也总结了到一些关于 webpack 的如何加快构建速度的方法,所以想在已有项目上尝试一下。
用到的技术方法:
本次实践主要根据 webpack 文档构建性能章节来进行逐步实现的。如使用的到的主要包括:
-
Dllplugin: 使用DllPlugin可以将不频繁改动的代码单独编译,拆分了bundle同时降低构建的复杂度。要使用DllPlugin就需要在一个单独的webpack中 创建一个 dll-only-bundle,然后生成一个manifest.json的文件,此文件这个文件包含了从require和import中request到模块id的映射。 此插件与output.library的选项相结合可以暴露出(也称为放入全局作用域)dll函数。另一方面让DllReferencePlugin能够映射到相应的依赖上。为什么要使用
DllPlugin?在项目开发过程中,我们的代码包含第三方库代码和业务代码,而我们主要开发的是业务代码,第三方库代码除了升级的情况一般不改动,所以此时将第三方包通过DllPlugin打包成动态链接库来引用注:
webpack5新出一个联邦模块可以支持跨项目共享bundles,实现热插拔效果,感兴趣的同学可以了解一下,个人感觉是DllPlugin的plus版。对应的依赖关系图
-
Thread-loader: 是一个多进程打包,即它会将每一个在他之后loader的放进一个单独的woker池(一个单独的node进程)里面,由于每一个worker池都是一个单独的node进程,所以各进程间不能进行通信,每次打开一个进程也是有消耗的,所以建议在消耗大的loader上使用。
如何在项目中应用
DllPlugin 在项目中的应用
-
创建
vue.vendor.config.js文件const path = require('path') const webpack = require('webpack') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { mode: 'production', entry: { vendor: [ 'vue', 'vuex', 'vue-router', 'element-ui', 'axios', 'lodash', 'echarts', 'codemirror'] }, // 输出的配置 output: { path: path.join(__dirname, 'public/vendor'), filename: '[name].dll.js', library: '[name]_[hash]' }, plugins: [ new CleanWebpackPlugin(), // 再次打包需要清除之前已生成的文件 new webpack.DllPlugin({ // 生成 dll.js 文件和 manifest.json 文件 path: path.join(__dirname, 'public/vendor', '[name]-manifest.json'), // 定义文件的输出路径 name: '[name]_[hash]', context: process.cwd() }) ] } -
在本地的
vue.config.js文件中配置DllReferencePlugin用于在构建过程中寻找依赖。
DllReferencePlugin用于查找映射依赖AddAssetHtmlPlugin用于自动将生成dll.js文件作为script的引用插入到html中。
module.exports = {
configureWebpack: {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./public/vendor/vendor-manifest.json')
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, './public/vendor/*.js'),
publicPath: './vendor',
outputPath: './vendor'
})
]
}
}
- 在
package.json文件中添加生成dll.js的执行命令
{
"scripts": {
"dll": "webpack --progress --config ./vue.vendor.config.js"
}
}
-
执行命令
yarn dll后会在public/vendor下生成一个vendor-manifest.json文件和一个vendor.dll.js,此时就可以正常开始启动项目进行正常开发了。-
生成
manifest.json文件的部分内容如下:{ "name": "vendor_99b4edce0862b7b404e3", "content": { "./node_modules/zrender/lib/core/util.js": { "id": 0, "buildMeta": { "providedExports": true } }, "./node_modules/echarts/lib/echarts.js": { "id": 1, "buildMeta": { "providedExports": true } }, "./node_modules/echarts/lib/util/graphic.js": { "id": 2, "buildMeta": { "providedExports": true } } } } ```~
thread-loader 应用
如何防止
thread-loader启动worker带来的高延时?thread-laoder由于每启动一个node进程都会有一定的时间开销,所以其提供的有预热功能,可以防止启动worker带来的高延时。在本项目中开启使用thread-loader开启多进程的有babel-loader和ts-loade。-
在
vue.config.js中配置const threadLoader = require('thread-loader') const tsPoolOptions = { // thread-loader 参数 name: 'ts-pool', workers: 2, // 启动 worker 的数量 默认是cpu核心数减1 poolRespawn: process.env.NODE_ENV === 'production', workerParallelJobs: 50, poolParallelJobs: 50, poolTimeout: 2000 } threadLoader.warmup(tsPoolOptions, // 预热 ['babel-loader'] // 加载模块 ) module.exports = { chainWebpack(config) { config .when(process.env.NODE_ENV !== 'development', config => { config.module .rule('ts') .use('thread-loader') .before('babel-loader') .loader('thread-loader') .options(tsPoolOptions) .end() } ) } }
-
使用前后对比
build 时间对比
-
更改前
-
更改后
build 后包大小对比
-
更改前
-
更改后
使用后感受
DllPlugin
- 首先使用上可能有些麻烦,需要将不需要使用的第三方资源包先进行编译才可以在build时生效,每次打包只打包业务代码,但是在更改第三包资源时需要再次进行
dll编译。dll时会把整个第三方引用包都会被编译,所以如果只是使用某个的包的部分功能,可以使用按需引用,避免dll编译后的bundle过大。
thread-loader
- 只能处理耗时的
loader。 - 使用开销比较大,每开启一个
worker进程池需要600ms。
本次实践感想
本次操作实践在一定程度上让自己对 webpack 打包和编译有了更多了解,在其中学习到的很多内容,也对自己的知识进行了巩固,明白的知道和做到之间其实是有很大的区别的,在开发的整个过程可以了解到优化的真实效果,以及是否适合自己所用的项目。其实在大家的客观上目标是一致的,只不过主观上有所区别,但是殊途同归。