webpack4 webpack之性能优化

在讲解性能优化的方案之前 , 我们需要了解一下webpack的整个工作流程 , 

webpack4 webpack之性能优化

文章插图
方案一:减少模块解析也就是省略了构建chunk依赖模块的这几个步骤
【webpack4 webpack之性能优化】
webpack4 webpack之性能优化

文章插图
如果没有loader对该模块进行处理 , 该模块的源码就是最终打包结果的代码 。不对某个模块进行解析 , 可以缩短构建时间
哪些模块不需要解析?模块中无其他依赖
webpack配置
配置module.noParse , 它是一个正则 , 被正则匹配到的模块不会解析
module.exports = {mode: "development",module: {noParse: /test/}}方案二:优化loader1.对于某些库 , 不使用loader例如:babel-loader可以转换ES6或更高版本的语法 , 可是有些库本身就是用ES5语法书写的 , 不需要转换 , 使用babel-loader反而会浪费构建时间
通过module.rule.excludemodule.rule.include , 排除或仅包含需要应用loader的场景 , 可以直接排除掉node_modules的所有包 , 也可以仅排除单独的包
module.exports = {module: {rules: [{test: /\.js$/,exclude: /node_modules/,// exclude: /lodash///或// include: /src/,use: "babel-loader"}]}} 2.利用cache-loader对模块进行缓存如果某个文件内容不变 , 经过相同的loader解析后 , 解析后的结果也不变 , 所以可以将loader的解析结果保存下来 , 让后续的解析直接使用保存的结果
module.exports = {module: {rules: [{test: /\.js$/,use: ['cache-loader', 'babel-loader']},],},};大家可能会感到疑惑 , 明明loader是从后往前执行的 , 那么cache-loader是怎么拿到babel-loader的结果的呢?
其实 , 每个loader上 , 还有一个pitch的静态方法
function loader(source){return `new source`}loader.pitch = function(filePath){// 可返回可不返回// 如果返回 , 返回源代码}module.exports = loader;loader真正执行的顺序是这样的:
loader1.pitch  => loader2.pitch => loader3.pitch  => loader3  => loader2 => loader1
因此 , 以['cache-loader', 'babel-loader']为例 , 
第一次打包:
  • 先调用cache-loader.pitch , 发现无缓存 , 往后执行 , 
  • 调用babel-loader.pitch , 也发现无缓存 , 往后执行 , 
  • 读取当前需要处理的模块的代码
  • 调用babel-loader , 返回修改成es5的代码
  • 调用cache-loader , 返回babel-loader处理的结果代码并缓存
第二次打包:
  • 先调用cache-loader.pitch , 发现有缓存 , 则返回源代码
  • 直接返回上次处理好的源代码 , 不会继续往后走了
当然对于babel-loader , 使用它本身的配置也是可以缓存的
module.exports = {module: {rules: [{test: /\.js$/,use:'babel-loader?cacheDirectory'},],},};3.开启thread-loader它会把后续的loader放到线程池的线程中运行 , 以提高构建效率
由于后续的loader会放到新的线程中 , 所以 , 后续的loader不能:
  • 使用 webpack api 生成文件 (loader上下文中的emitFile、emitError等api)
  • 无法使用自定义的 plugin api (某些插件提供了自身的plugin和loader , plugin会向webpack注入新的api , loader中会使用)
  • 无法访问 webpack的配置
注意 , 开启和管理线程需要消耗时间 , 在小型项目中使用thread-loader反而会增加构建时间
方案三:热替换热替换并不能降低构建时间(可能还会稍微增加) , 但可以减少代码改动到效果呈现的时间
// webpack配置module.exports = {devServer:{hot:true // 开启HMR}}// index.jsif(module.hot){ // 是否开启了热更新module.hot.accept() // 接受热更新}方案四:动态链接库什么情况下使用?
当打包出来的多个bundle.js文件都有重复的第三方代码 , 会增加文件的体积 , 不利于传输
打包的过程:1.使用output.library配置公共模块的全局变量名// webpack.dll.config.jsmodule.exports = {mode: "production",entry: {jquery: ["jquery"],lodash: ["lodash"]},output: {filename: "dll/[name].js",library: "[name]"// 每个buldle暴露的全局变量名}};打包结果
// dist/dll/lodashvar lodash=function(n){xxx}// dist/dll/jqueryvar jquery=function(n){xxx}2.用DllPlugin创建资源清单(包含信息:全局变量名、node_modules对应包的路径)// webpack.dll.config.jsmodule.exports = {plugins: [new webpack.DllPlugin({path: path.resolve(__dirname, "dll", "[name].manifest.json"), //资源清单的保存位置name: "[name]"//资源清单中 , 暴露的变量名})]};打包生成的资源清单
// dll/lodash.manifest.json{"name": "lodash","content": {"./node_modules/lodash/lodash.js": {xxx}}}// dll/jquery.manifest.json{"name": "jquery","content": {"./node_modules/jquery/dist/jquery.js": {xxx}}}3.用DllReferencePlugin使用资源清单在页面中手动引入公共模块
<script src="https://tazarkount.com/read/dll/jquery.js"></script><script src="https://tazarkount.com/read/dll/lodash.js"></script>重新设置clean-webpack-plugin
如果使用了插件clean-webpack-plugin , 为了避免它把公共模块清除 , 需要做出以下配置 , webpack.config.js配置(注意不是和output.library、DllPlugin在同一个配置文件中哦):
new CleanWebpackPlugin({// 要清除的文件或目录// 排除掉dll目录本身和它里面的文件cleanOnceBeforeBuildPatterns: ["**/*", '!dll', '!dll/*']})使用DllReferencePlugin
找到对应的资源清单 , 根据暴露的变量名(output.library)匹配第三方库在node_modules中的路径 , 不需要将代码打包到bundle.js中 , webpack.config.js:new webpack.DllReferencePlugin({manifest: require("./dll/jquery.manifest.json")}),new webpack.DllReferencePlugin({manifest: require("./dll/lodash.manifest.json")})打包过程:首先要根据webpack.dll.config.js配置文件打包一次 , 之后再根据webpack.config.js打包
最终打包结果的格式:
(function(modules){//...})({// index.js文件的打包结果并没有变化"./src/index.js":function(module, exports, __webpack_require__){var $ = __webpack_require__("./node_modules/jquery/index.js")var _ = __webpack_require__("./node_modules/lodash/index.js")_.isArray($(".red"));},// 由于资源清单中存在 , jquery的代码并不会出现在这里"./node_modules/jquery/index.js":function(module, exports, __webpack_require__){module.exports = jquery;},// 由于资源清单中存在 , lodash的代码并不会出现在这里"./node_modules/lodash/index.js":function(module, exports, __webpack_require__){module.exports = lodash;}})优点:
  • 极大提升自身模块的打包速度
  • 极大的缩小了自身文件体积
  • 有利于浏览器缓存第三方库的公共代码
缺点:
  • 使用非常繁琐
  • 如果第三方库中包含重复代码 , 则效果不太理想
方案五:抽离公共代码 有多个模块都引用了公共模块 , 当一个模块加载时 , 访问了公共模块 , 并缓存下来 , 另一个模块加载就可以直接使用缓存的结果 。
module.exports = {optimization: {splitChunks: {// 分包策略chunks: "all",cacheGroups: {// 公共模块common: {mixSize: 0,minChunks: 2, // 至少被几个文件引用},vendors: {test: /[\\/]node_modules[\\/]/, // 当匹配到相应模块时 , 将这些模块进行单独打包priority: 1 // 缓存组优先级 , 优先级越高 , 该策略越先进行处理 , 默认值为0},}}}}还可以抽离公共样式 , 使用MiniCssExtractPlugin
module.exports = {optimization: {splitChunks: {chunks: "all",cacheGroups: {styles: {test: /\.css$/, // 匹配样式模块minSize: 0, // 覆盖默认的最小尺寸 , 这里仅仅是作为测试minChunks: 2 // 覆盖默认的最小chunk引用数}}}},module: {rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }]},plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: "[name].[hash:5].css",// chunkFilename是配置来自于分割chunk的文件名chunkFilename: "common.[hash:5].css"})]} 通过cdn方式引入js、css文件 , 将不怎么需要更新的第三方库脱离webpack打包 , 不被打入bundle中 , 从而减少打包时间 , 但又不影响运用第三方库的方式 , 例如import方式等 。
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');module.exports = {plugins: [new HtmlWebpackExternalsPlugin({externals: [{module: 'vue',entry: '/uploads/allimg/220601/101HC1b-4.jpg',global: 'Vue'}]})],}最后看到在dist/index.html中动态添加了如下代码:
<script type="text/javascript" src="http://img.qxzm.cc/220601/101HC1b-4.jpg"></script>方案六:代码压缩压缩js和css代码:
const TerserPlugin = require('terser-webpack-plugin');const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');module.exports = {optimization: {// 是否要启用压缩 , 默认情况下 , 生产环境会自动开启minimize: true,minimizer: [ // 压缩时使用的插件// 压缩js文件new TerserPlugin({
parallel: true // 开启多线程压缩
}),// 压缩css文件new OptimizeCSSAssetsPlugin()],},}; 使用compression-webpack-plugin插件对打包结果进行预压缩 , 可以移除服务器的压缩时间
new CmpressionWebpackPlugin({test: /\.js/,minRatio: 0.5})