Webpack5 常用优化总结

本文主要总结经常用到的一些代码性能优化、减小代码体积、提升webpack打包构建速度 等内容的方法。具体的实现可参考webpack官网查看相关示例。

注:如果读者还未接触过webpack,请先了解webpack的基本使用。

正文:

SourceMap ---- 提升开发体验

SourceMap源代码映射,是一个用来生成源代码与构建后代码一一映射的文件的方案。

使用webpack打包之后会生成一个与打包文件对应的.map文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后的代码报错时,其会通过.map文件将构建后代码中出错的位置映射到源代码出错的位置,从而让浏览器的报错提示的是源代码文件报错的位置信息,帮助开发人员快速定位。
解决方案: 可以通过设置devtool来控制如何生成source map。开发模式下我们一般使用cheap-module-source-map,优点:打包编译速度快,只包含行映射,缺点:没有列映射。生产模式下使用source-map,优点:包含行和列的映射,缺点:打包编译速度慢。

javascript 复制代码
// webpack.dev.js
module.exports = {
	mode: 'development',
	devtool: 'cheap-module-source-map'
}
// webpack.prod.js
module.exports = {
	mode: 'production',
	devtool: 'source-map'
}

提升构建打包速度

1. HotModuleReplacement(HMR)模块热替换

在webpack5中,热更新是webpack默认开启的。开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而实现局部更新,而不是刷新整个页面。

javascript 复制代码
// webpack.config.js
module.exports = {
	devServer: {
		hot: true
	}
}
2. OneOf 规则数组 只使用第一个匹配规则

当在webpack配置文件中写了很多处理不同资源文件的loader时,资源文件会遍历所有loader进行解析处理,当使用了oneOf规则之后,资源文件一旦被某个loader处理了,就不会继续往下遍历,从而使打包速度更快。

javascript 复制代码
// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				oneOf: [
					{
						test: /\.css$/,
						use: ['style-loader', 'css-loader']
					},
					{
						test: /\.less$/,
						use: ['style-loader', 'css-loader', 'less-loader']
					},
					{
						test: /\.s[sc]ss$/,
						use: ['style-loader', 'css-loader', 'sass-loader']
					},
					{
						test: /\.styl$/,
						use: ['style-loader', 'css-loader', 'stylus-loader']
					},
					{
						test: /\.(png|jpe?g|gif|webp|svg)$/,
						type: 'asset',
						parser: {
							dataUrlCondition: {
								// 小于10KB的图片转base64
								maxSize: 10 * 1024
							}
						},
						generator: {
							filename: 'static/img/[hash:10][ext][query]'
						}
					},
					{
						test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
						type: 'asset/resource',
						generator: {
						filename: 'static/media/[hash:10][ext][query]'
						}
					},
					{
						test: /.\.js$/,
						exclude: /node_modules/,
						loader: 'babel-loader'
					}
				]
			}
	
		]
	}
}
3. Include/Exclude

引入或排除某些文件,处理的文件更少,速度更快。例如开发时我们用到了第三方的库或者插件,那么这些文件是不需要编译就可以使用的,所以可以排除(exclude)对这些文件的处理。或者只处理(include)某个文件夹下面的源代码。二者选其一使用。

javascript 复制代码
// webpack.config.js
const path = require('path')
const ESlintPlugin= require('eslint-webpack-plugin')

module.exports = {
	module: {
		rules: [
			{
				test: /.\.js$/,
				// 排除node_modules下的文件 不作处理
				exclude: /node_modules/,
				loader: 'babel-loader'
			},
			// 或者
			{
				test: /.\.js$/,
				// 只处理src下的内容 其他文件不处理
				include: path.resolve(__dirname, './src'),
				loader: 'babel-loader'
			},
		]
	},
	plugins: [
		new ESlintPlugin([
			context: path.resolve(__dirname, './src'),
			exclude: /node_modules/, // 默认值
			//或者
			include: path.resolve(__dirname, './src'),
		])
	]
}
4. Cache 缓存

可以对eslint和babel处理的结果进行缓存,让后续打包速度更快。

javascript 复制代码
// webpack.config.js
const path = require('path')
const ESlintPlugin= require('eslint-webpack-plugin')

module.exports = {
	module: {
		rules: [
			{
				test: /.\.js$/,
				// 排除node_modules下的文件 不作处理
				exclude: /node_modules/,
				loader: 'babel-loader',
				options: {
					cacheDirectory: true, //  开启babel缓存
					cacheCompression: false // 关闭缓存文件压缩	
				}
			},
		]
	},
	plugins: [
		new ESlintPlugin([
			context: path.resolve(__dirname, './src'),
			exclude: /node_modules/, // 默认值
			cache: true, // 开启缓存
			cacheLocation: path.resolve(__dirname, './node_modules/.cache/eslintcache') // 定义缓存位置
		])
	]
}
5. Thead 多进程打包

当项目非常庞大的时候,打包速度越来越慢,主要是对js文件进行检查(eslint)、编译(babel)、压缩(terser),要提升运行速度可以开启多进程同时处理js文件。由于进程启动通信都是有开销的,所以只有在代码比较多的时候处理才有效果。

javascript 复制代码
npm i thread-loader --save-dev

使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。

在 worker 池中运行的 loader 是受到限制的。例如:

  • 这些 loader 不能生成新的文件。
  • 这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
  • 这些 loader 无法获取 webpack 的配置。
    每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。

请仅在耗时的操作中使用此 loader!

javascript 复制代码
// webpack.config.js
const os = require('os')
const ESlintPlugin= require('eslint-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin') // 内置插件

const threads = os.cups().length  // 获取CPU核数

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve('src'),
        use: [
          {
          	loader: "thread-loader",
          	options: {
          		works: threads	
          	}
          },
      	  {
			loader: "babel-loader"	
		  }
        ],
      },
    ],
  },
  plugins: [
	new ESlintPlugin([
		context: path.resolve(__dirname, './src'),
		exclude: /node_modules/, // 默认值
		cache: true, // 开启缓存
		cacheLocation: path.resolve(__dirname, './node_modules/.cache/eslintcache'), // 定义缓存位置
		threads, // 开启多进程和设置数量
	]),
	new TerserPlugin({ // 代码压缩
		parallel: threads //开启多进程和设置数量
	})
  ],
  // 压缩插件 第二种写法
  optimization: {
  	minimizer: [
  		new CssMinimizerPlugin(), // css文件压缩
  		new TerserPlugin({ // js代码压缩
			parallel: threads //开启多进程和设置数量
		})
  	]
  }
};

减小代码体积

1. Tree Shaking

当我们编写了很多工具函数或者引入了第三方库,可能在实际开发中只应用了其中一部分,那么在打包时这些未用到的代码就无须进行打包。Tree Shaking就帮我们做了这件事情。它可以移除JS上下文中的死代码,且其语法依赖于ESM,不支持CommonJS。

在Webpack中已经默认开启了此配置,所以开发者无需再进行配置。

2. @babel/plugin-transform-runtime

此插件可以对babel进行处理,让辅助代码单独生成到一个文件中,引入到编译后的文件中,而不是每个文件都生成辅助代码,从而减小打包后的体积。

javascript 复制代码
npm i @babel/plugin-transform-runtime -D
javascript 复制代码
// webpack.config.js

module.exports = {
	module: {
		rules: [
			{
				test: /.\.js$/,
				// 排除node_modules下的文件 不作处理
				exclude: /node_modules/,
				loader: 'babel-loader',
				options: {
					cacheDirectory: true, //  开启babel缓存
					cacheCompression: false, // 关闭缓存文件压缩
					plugins: ['@babel/plugin-transform-runtime'] // 减小代码体积
				}
			},
		]
	}
}
3. Image Minimizer 图片压缩

当项目中使用了很多本地图片,那么可以对图片进行压缩,减小图片体积,从而加快请求速度。如果项目中使用的是在线链接的图片,那么就不需要进行配置了。

javascript 复制代码
npm i image-minimizer-webpack-plugin imagemin -D

安装完上面两个依赖包之后还需要下载另外两种压缩方式的包,读者选择性下载。

一是无损压缩:

javascript 复制代码
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev

二是有损压缩:

javascript 复制代码
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
javascript 复制代码
// webpack.config.js
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const { extendDefaultPlugins } = require('svgo');

module.exports = {
	module: {
     rules: [
        {
          test: /\.(jpe?g|png|gif|svg)$/i,
          type: 'asset',
        },
      ],
    },
	plugins: [
		new ImageMinimizerPlugin({
	      minimizerOptions: {
	        plugins: [
	          ['gifsicle', { interlaced: true }],
	          ['jpegtran', { progressive: true }],
	          ['optipng', { optimizationLevel: 5 }],
	          [
	            'svgo',
	            {
	              plugins: extendDefaultPlugins([
	                {
	                  name: 'removeViewBox',
	                  active: false,
	                },
	                {
	                  name: 'addAttributesToSVGElement',
	                  params: {
	                    attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }],
	                  },
	                },
	              ]),
	            },
	          ],
	        ],
	      },
	   }),
	]
}

优化代码性能

1. Code Split 代码分割

在进行打包时,会将所有的js文件打包到一个文件中,导致体积太大,加载速度慢。当使用了代码分割之后,生成多个js文件,渲染哪个页面就加载哪个js文件,这样就会减少资源的加载,速度就更快,从而提升性能。

使用:

一、多入口、多输出

javascript 复制代码
module.exports = {
	entry: { // 多个入口文件,打包时就会产生多个输出文件
		main: './main.js',
		app: './src/app.js'
	},
	optimization: {
		splitChunks: { // 代码分割配置
			chunks: 'all',  // 对所有模块都进行分割
			minSize: 20000, // 生成chunk的最小体积(以bytes为单位)
	        minRemainingSize: 0, // 类似minSize,最后确保提取的文件大小不能为0
	        minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
	        maxAsyncRequests: 30, // 按需加载时并行加载文件的最大数量
	        maxInitialRequests: 30, // 入口js文件最大并行请求数量
	        enforceSizeThreshold: 50000, // 超过50KB一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
	        cacheGroups: {  // 组,指哪些模块要打包到一个组
	          defaultVendors: { // 组名
	            test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
	            priority: -10, // 权重,数值越大权重越高
	            reuseExistingChunk: true, // 如果当前chunk包含已从主bundle中拆分出的模块,则它将被重用,而不是生成新的模块
	          },
	          default: { // 默认属性,没有就会使用上面的配置,此配置会覆盖上面的配置
	            minChunks: 2,
	            priority: -20,
	            reuseExistingChunk: true,
	          },
	        },
		}
	}
}

二、单入口,多输出

javascript 复制代码
module.exports = {
	entry: './main.js',
	optimization: {
		splitChunks: {
			chunks: 'all'
		}
	}
}
2. Preload / Prefetch 预加载 预获取

preload:使浏览器立即加载资源;prefetch:等待浏览器空闲时开始加载资源。它们只会加载资源,并不执行且都有缓存。Preload加载优先级要高于Prefetch。

javascript 复制代码
npm install --save-dev @vue/preload-webpack-plugin
javascript 复制代码
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');

module.exports = {
	plugins: [
		new PreloadWebpackPlugin({
			rel: 'preload', // 预加载的rel
            as: 'script', // 预加载的资源类型
            include: 'allChunks' // 预加载的文件范围	
			// 或者使用 prefetch
			rel: 'prefetch'
		})
	]
}
3. Network Cache

对输出资源的文件更好的命名,可以做好缓存,提升性能。只对修改的文件打包之后改动文件名,其它文件不因引入改动的文件而改动文件名,可以更好的做到缓存。

javascript 复制代码
module.exports = {
	optimization: {
		runtimeChunk: {
			name: entrypoint => `runtime~${entrypoint.name}.js`
		}
	}	
}
4. Core-js 解决兼容性

例如一些ES6的新语法,babel无法处理,例如async函数、promise对象、数组的一些方法等等。所以可以使用core-js专门处理ES6及以上的语法。更好的适配老款浏览器的兼容性。

javascript 复制代码
npm i core-js

安装好之后在主入口引入即可

javascript 复制代码
import 'core-js'
5. PWA 渐进式网络应用程序(progressive web application - PWA)

当网络断开时,就无法访问Web应用了。为了提供离线访问效果,我们可以引入PWA,内部是通过Service Works技术实现的。可以将所有资源缓存到ServiceWork中,当离线时依旧可以访问。

javascript 复制代码
npm install workbox-webpack-plugin --save-dev
javascript 复制代码
const WorkboxPlugin = require('workbox-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js',
    },
    plugins: [
      new HtmlWebpackPlugin({
       title: 'Output Management',
       title: 'Progressive Web Application',
      }),
     new WorkboxPlugin.GenerateSW({
       // 这些选项帮助快速启用 ServiceWorkers
       // 不允许遗留任何"旧的" ServiceWorkers
       clientsClaim: true,
       skipWaiting: true,
     }),
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
  };

最后,需要在主入口文件中注册Service Worker才能生效:

main.js

javascript 复制代码
 if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
 }
相关推荐
~甲壳虫5 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
Beamon__6 小时前
element-plus按需引入报错AutoImport is not a function
webpack·element-plus
CodeToGym6 小时前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫6 小时前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫6 小时前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
lin-lins1 天前
模块化开发 & webpack
前端·webpack·node.js
柳鲲鹏2 天前
LINUX/CMAKE编译opencv_contrib
linux·opencv·webpack
前端李易安2 天前
webpack的常见配置
前端·webpack·node.js
魏大帅。3 天前
Webpack入门教程:从基本概念到优化技巧
前端·webpack·node.js
web_code3 天前
webpack源码快速分析
前端·webpack·源码阅读