之前面试被问到webpack打包优化,回答不怎么好,一直说总结了补充在【千人虐】面试总结---1中,后来总结下来发现内容太多,所以还是单独开篇记录下吧
我个人认为优化主要分为两个方面
- 提高webpack构建速度
- 缩小webpack打包后文件大小
1 提高webpack构建速度
提高webpack构建速度更多是对于开发环境的需求,减少启动等待时间,之前遇到过大项目,启动时间要六分多钟,着实难受。
1.1 webpack打包流程
整个打包流程我们可以将其理解为一个函数,配置文件则是其参数,传入合理的参数后,运行函数就能得到我们想要的结果。 大致流程如下:
- 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
- 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点, 做出相应的反应,执行对象的 run 方法开始执行编译。
- 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
- 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
- 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry配置生成代码块chunk。
- 输出完成:输出所有的chunk到文件系统。
1.2 如何提升构件速度
区分开发环境和生产环境区分开发环境和生产环境,上面提到我们需要区分开发环境和生产环境,在WebPack4.x中非常贴心的为我们引入了mode配置项,通过指定production 或 development来区分是开发环境还是生产环境。当我们把mode配置为development,那么WebPack会默认开启debug工具,当我们编译有错误的时候,提供良好的报错提示。打包的时候也会直接采用增量编译,而不是覆盖更新,这样我们可以节省大量编译时间。当我们把mode配置为production,则会默认开启运行时的性能优化以及构建结果优化。
减少不必要的编译,我们在使用loader处理文件的时候,应该尽量把文件范围缩小,对于一些不需要处理的文件直接忽略。这里我们以babel-loader为例,看一下如何处理:
module: { rules: [ { //处理后缀名为js的文件 test: /\.js$/, //exclude去掉不需要转译的第三方包 && 或者这里使用include去声明哪些文件需要被处理 exclude: /(node_modules|bower_components)/, //babel的常用配置项 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], //缓存设置为开启 cacheDirectory: true } } } ] } // 这里我们对于不需要处理的第三方包直接使用 exclude 属性排除在外, // 或者需要处理的文件使用 include 属性去包含,此外上面我们在 options 配置当中增加了 cacheDirectory: true, //这样对于转译结果就可以直接缓存到文件系统当中,在我们下次需要的时候直接到缓存当中读取即可。使用模块热替换(HMR),传统的如果我们没有配置模块热替换,则需要每次刷新整个页面,效率很低。而使用模块热替换之后,我们只需要重新编译发生变化的模块,不需要编译所有模块,速度上面大大提高。具体配置方法如下:
module.exports = { ...... plugins: [ new webpack.HotModuleReplacementPlugin(), // 引入模块热替换插件 ], devServer: { hot: true // 开启模块热替换模式 } }由于现在的项目都会引用大量的第三方包,这些包基本都是不会变的,我们完全把他们打包到单独的文件当中,这就涉及到了公共代码的提取,之前Webpack3.x中使用的是CommonsChunkPlugin插件,后来Webpack升级到4.x之后使用的则是optimization.splitChunks。下面我们来分别介绍一下这2个插件的使用:
- CommonsChunkPlugin配置
new webpack.optimize.CommonsChunkPlugin({ // 指定该代码块的名字 name: "framework", // 指定输出代码的文件名 filename: "framework.js", // 指定最小共享模块数 minChunks: 4, // 指定作用于哪些入口 chunks: ["pageA", "pageB","pageC"] })- optimization.splitChunks配置
optimization: { splitChunks: { //设置那些代码用于分割 chunks: "all", // 指定最小共享模块数(与CommonsChunkPlugin的minChunks类似) minChunks: 1, // 形成一个新代码块最小的体积 minSize: 0, cacheGroups: { framework: { test: /react|lodash/, name: "vendor", enforce: true } } } } //cacheGroups对象,定义了需要被抽离的模块,其中test属性是比较关键的一个值,他可以是一个字符串,也可以是正 //则表达式,还可以是函数。如果定义的是字符串,会匹配入口模块名称,会从其他模块中把包含这个模块的抽离出来。 //name属性是要缓存的分离出来的chunk名称。使用插件DLLPlugin
该插件它用来分离我们在程序调用中反复会用到的代码。其实我上面说到的CommonsChunkPlugin和optimization.spli tChunks和DLLPlugin是一类插件,但是DLLPlugin更加智能,配置过程也更复杂。- 配置动态链接库:首先需要为动态链接库单独创建一个 Webpack 配置文件,这里我们叫做 webpack.vendor.config.js。该配置对象需要引入 DLLPlugin,其中的 entry 指定了把哪些模块打包为 vendor。
const path = require('path'); const webpack = require('webpack'); module.exports = { entry: { //提取的公共文件 vendor: ['react','lodash',] }, output: { path: path.resolve('./dist'), filename: 'vendor.js', library: '[name]_library' }, plugins: [ new webpack.DllPlugin({ path: path.resolve('./dist', '[name]-manifest.json'), name: '[name]_library' }) ] }; - 打包动态链接库并生成 vendor 清单:使用该配置文件进行打包。会生成一个 vendor.js 以及一个资源的清单文件manifest.json,在内部每一个模块都会分配一个 ID。
- 将 vendor 连接到项目中:最后在工程的 webpack.config.js 中我们需要配置 DllReferencePlugin 来获取刚刚打包出来的模块清单。这相当于工程代码和 vendor 连接的过程。
module.exports = { plugins: [ new webpack.DllReferencePlugin({ // 指定需要用到的 manifest 文件 manifest: path.resolve(__dirname, 'dist/manifest.json'), }), ], } - 最后不要忘了需要在HTML文件当中正确引入我们打包好的vendor.js文件。
- 配置动态链接库:首先需要为动态链接库单独创建一个 Webpack 配置文件,这里我们叫做 webpack.vendor.config.js。该配置对象需要引入 DLLPlugin,其中的 entry 指定了把哪些模块打包为 vendor。
最后不要忘了需要在HTML文件当中正确引入我们打包好的vendor.js文件。
另一部分文件质量优化在另一篇文章中输出。。。。