Webpack 4 项目常用的优化技巧 —— 附上demo

902 阅读6分钟

前言

这一篇文章主要是续着上篇的 ——「webpack4 从零搭建 vue 项目开发环境」。强烈推荐先看上一篇文章 。

文章主要说一下不同环境的区分和一些可以优化的点

文章 demo 已经提交到 github , 请自取 ———— 「webpack 4 demo」

区分「开发环境」和「生产环境」

  • 开发环境:热更新多线程sourceMap 等等
  • 生产环境:js压缩提取csscss压缩多线程sourceMap目录清理等等

由于「开发环境」和「生产环境」的需求不同,所以我们要区分不同环境来构建我们的项目。

配置 webpack.common.jswebpack.dev.jswebpack.prod.js

新建 build 目录,并新建下面三个文件

  • webpack.common.js 公用配置
  • webpack.dev.js 开发环境配置
  • webpack.prod.js 生成环境配置

安装 webpack-merge

用于合并 webpack 公用配置到指定环境

npm i webpack-merge@5.8.0 -D

配置 webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
	entry: path.join(__dirname, "../src/main.js"),
	output: {
		path: path.join(__dirname, "../dist"),
		filename: "bundle.js"
	},
	module: {
		rules: [
			{
				test: /\.(png|jpg|gif)$/i,
				use: [
					{
						loader: 'url-loader',
						options: {
							limit: 8192, //小于 8K ,用 url-loader 转成 base64 ,否则使用 file-loader 来处理文件
							fallback: {
								loader: 'file-loader',
								options: {
									name: '[name].[hash:8].[ext]',
									outputPath: '../dist/images/', //打包之后文件存放的路径, dist/images
								}

							},

						}
					}
				]
			},
			{
				test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
				use: [
					{
						loader: 'url-loader',
						options: {
							limit: 8192,
							fallback: {
								loader: 'file-loader',
								options: {
									name: '[name].[hash:8].[ext]',
									outputPath: '../dist/media/',
								}
							},

						}
					}
				]
			},
			{
				test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
				use: [
					{
						loader: 'url-loader',
						options: {
							limit: 1,
							fallback: {
								loader: 'file-loader',
								options: {
									name: '[name].[hash:8].[ext]',
									outputPath: '../dist/fonts/',
								}
							},

						}
					}
				]
			},
			{
				test: /\.(css)$/,
				use: [
						"style-loader",
						"css-loader",
						"postcss-loader"
				]
			},
			{
			test: /\.(less)$/,
					use: [
						"style-loader",
						"css-loader",
						"postcss-loader",
						"less-loader",
				]
			},
			{
				test: /\.vue$/,
				use: ["vue-loader"]
			}
		]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: path.join(__dirname, '../src/index.html')
		}),
		new VueLoaderPlugin()
	]
};

配置 webpack.dev.js

const webpack = require("webpack");
const { merge } = require('webpack-merge')
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
	mode: "development",
	devServer: {
		contentBase: path.join(__dirname, "../dist"),
		compress: true,
		open: true,
		quiet: false,
		hot: true, //开启热更新
		port: 3000,
		clientLogLevel: 'none',  //关闭浏览器控制台输出的热更新信息
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	]
});

配置 webpack.prod.js

const { merge } = require('webpack-merge')
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
	mode: "production"
})

修改 package.json npm 脚本

指定 webpack 不同环境执行不同的 配置文件

  "scripts": {
    "serve": "webpack-dev-server --config build/webpack.dev.js", //运行环境
    "build": "webpack --config build/webpack.prod.js",//开发环境
  },

其实也可以通过 --mode 来设置 process.env.NODE_ENV ,并判断是「开发环境」还是「生产环境」,执行不同的配置文件内容。这里就不演示了。网上也很多例子,感兴趣自己去搜一下啊

CleanWebpackPlugin 自动删除 dist 上次打包的内容

npm i clean-webpack-plugin -D
// webpack.prod.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

plugins: [
	new CleanWebpackPlugin()
],

DefinePlugin 设置环境全局变量

DefinePluginwebpack 内置的插件,不需要安装

配置 webpack.dev.jswebpack.prod.js

这里只演示 webpack.dev.jswebpack.prod.js 设置也是一样的。

plugins: [
	new webpack.DefinePlugin({
		'process.env': {
			//设置全局变量可以在整个项目中调用
			BASE_URL: JSON.stringify('http://localhost:3000/dev')
			}
		})
	]

调用全局变量

console.log('BASE_URL:', process.env.BASE_URL)

设置 sourceMap 便于调试

webpack 打包过的 bundle.js 都是压缩过的。没法正常调试。而sourceMap就是用来帮助我们调试。如果你之前用过 vue-cli ,打包的时候 ,会在 dist 目录下生存后缀为 .map 的文件,这个就是源码的映射文件。

我们可以通过设置devtool来生成不同类型的 sourceMap 。不同模式生成的代码体积、构建速度、安全性等等都有差异,可以参考webpack官网」

个人推荐,开发环境推荐使用带eval字样的生成模式,速度会比较快。至于是cheapmodule根据自己的需要选择就好。生产环境,为了安全性,建议使用把sourceMap放在本地或者内网,可使用SourceMapDevToolPlugin插件来配置。

  • 开发环境
    devtool: "cheap-module-eval-source-map",
    
  • 生产环境

个人测试了一下,发现SourceMapDevToolPlugindevtool是存在冲突的,使用SourceMapDevToolPlugin来配置时需要设置 devtool: false

webpack 默认情况下会在bundle.js 最后面追加一行 //# sourceMappingURL=源代码对应的 sourcemaps 文件的路径的注释,使用浏览器调试的时候(F12),就会自动去查找这个路径的 sourceMap 。我们可以在 append里自定义这个路径,支持 url本地地址

 // webpack.prod.js
  devtool: false,
  plugins: [
  	// 默认情况下生成的模式是 `source-map`
  	new webpack.SourceMapDevToolPlugin({
  		//sourcemap 内网地址
  		append: '\n//# sourceMappingURL=http://localhost:1888/sourcemaps/[name].[hash:8].map',
  		// 生成 sourcemap 的存放路径和名称
  		filename: 'sourcemaps/[name].[hash:8].map', 
  		// module:true // 相当于 devtool 的 module
  		// columns:false //相当于 devtool 的 cheap
  		// noSources:true //相当于 devtool 的 nosources
  	})
  ],

这里顺便提一下,vue-cli「生产环境」默认生成为:devtool:source-map 也就是生成的map文件包含了未压缩的源代码,个人觉得不太安全!如果项目比较打的时候构建速度也会特别慢

如果你是第一次接触 sourceMap相信看完也是懵逼的,可以看一下下面的文章,应该对你有帮助

sourceMap 推荐阅读文章:

优化 (前言)

一个项目是否需要优化,要根据自己的项目情况来决定的。比如某些优化只在大项目时候才会体现出它的优势,小项目使用反而会适得其反。如果你感觉自己的项目构建速度和项目体积都可以接受,那也没有必要去优化。

下面是一些可以优化的点,但不要都一股脑的加上。根据自己项目情况来尝试和选择,查看效果

优化前准备工作

speed-measure-webpack-plugin 用于查看 loader插件 的处理时间

webpack-bundle-analyzer 用于查看打包后包的体积大小。

减少文件查找范围

指定 loader 处理范围 ———— exclude/include

exclude 是排除不在某个目录下查找

include 是限定在哪个目录下查找

同时配置的话,exclude 的优先级高于 include,推荐只使用include

// webpack.commom.js
{
	test: /\.js$/,
	// exclude: /node_modules/, 
	include: path.join(__dirname, "../src"),
	use: ["babel-loader"]
},

resolve.alias 别名配置

用别名来代替,编写更简洁,也可以起到减少查找范围的作用

// webpack.commom.js
resolve: {
	alias: {
		"@": path.join(__dirname, "../src")
	}
},

resolve.extensions 指定文件后缀匹配规则

webpack 默认配置为 extensions: ['.js', '.json'],假设文件导入的时候没用写后缀名,会根据这个规则来匹配。

例如:import router from "@/router/index", 先找js,再找 json

extensions 可以修改匹配的顺序,可以跳转它的优先级,不常用的文件不建议写进入

// webpack.commom.js
  resolve: {
    extensions: [".js", ".vue",".json"]
	// extensions:[".ts",".js", ".vue",".json"] 如果你的项目中有使用到 `typescript` 推荐把 `.ts` 放在前面
  }

缓存

cache-loader

在一些开销比较大的loader前面使用cache-loader,开销比较小的 loader 不需要使用,用了反而会慢。它会把结果缓存在磁盘上,node_modueles/.cache/cache-loader 默认存放路径。第一次构建的时间时间会比较长,后面再次构建就会比较快了

npm install cache-loader -D
// webpack.commom.js

{
	test: /\.js$/,
	include: path.join(__dirname, "../src"),
	use: ["cache-loader", "babel-loader"]
}

babel-loader

其实 babel-loader 本身也具备缓存的功能。 我听说 cache-loader 会更快一点。我自己试了一下,差距不大,cache-loader 稍微快一点点。

// webpack.commom.js
{
	test: /\.js$/,
	include: path.join(__dirname, "../src"),
	use: [{
		loader: 'babel-loader',
		options: {
			cacheDirectory: true
		}
	}]
},

hard-source-webpack-plugin

HardSourceWebpackPlugin 为模块提供中间缓存, 第一次构建,花费正常时间。第二个构建速度快60% ~ 90%。不推荐 DllPluginDllReferencePlugin 一起使用

注意:开发环境,使用 HardSourceWebpackPlugin 时,不要使用 speed-measure-webpack-plugin 会报错

npm install hard-source-webpack-plugin -D
// webpack.commom.js

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

plugins: [
   	// 为模块提供中间缓存,不能和 SpeedMeasurePlugin 插件同时使用
	new HardSourceWebpackPlugin(),
	// 排除缓存 MiniCssExtractPlugin 插件 ,不然会报错
	new HardSourceWebpackPlugin.ExcludeModulePlugin([
		{
			test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
		},
	]),
]

多线程

大家知道 JavaScript 是单线程的,我们可以使用 web worker来现实多线程执行JavaScript 。如果你没听过 web worker 推荐看一下这篇文章——「Web Worker 使用教程」

而在 webpack 打包构建项目的时候,我们也可以使用 worker 来实现。有两个基于worker实现的插件 thread-loaderhappypackhappypack 貌似被作者抛弃了,推荐使用thread-loader 来现实。

请只在耗时比较长的 loader 上使用!

npm install thread-loader -D
// webpack.commom.js

{
	test: /\.js$/,
	include: path.join(__dirname, "../src"),
	use: ["thread-loader", "cache-loader", "babel-loader"]
	// 项目大,loader 花费时间长时用
}

压缩

JS压缩,删除注释和 console —— terser-webpack-plugin

webpack 4 需要安装,webpack 5 默认支持

 npm i terser-webpack-plugin@4 -D
// webpack.prod.js

const TerserPlugin = require('terser-webpack-plugin');

optimization: {
	minimizer: [
		// 压缩 JS
		new TerserPlugin({
			parallel: true, //开启多线程
			// include: path.join(__dirname, "../src"),
			sourceMap: true, // 如果使用了 SourceMapDevToolPlugin ,需要设置为 true
			extractComments: false, // 默认打包会生成 LICENSE.txt  文件,这里设置禁止生成
			terserOptions: {
				output: {
					comments: false, //删除注释
				},
				compress: {
					drop_console: true //删除 console
					// drop_debugger: false //默认为 true, 会删除 debugger
				},
			},

		}),
	],
},

css 提取

如果打包生成的JS太大,提取css有利于减少JS的体积以及页面的快速渲染(link 引入样式不会堵塞DOM渲染和JS渲染),

npm i mini-css-extract-plugin@1.6.2 -D
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
	rules: [
		{
			test: /\.(css)$/,
			include: path.join(__dirname, "../src"),
			use: [
				MiniCssExtractPlugin.loader,
				// "thread-loader", // 项目大,loader 花费时间长时用
				"css-loader",
				"postcss-loader"
			]

		},
		{
			test: /\.(less)$/,
			include: path.join(__dirname, "../src"),
			use: [
				MiniCssExtractPlugin.loader,
				// "thread-loader", // 项目大,loader 花费时间长时用
				"css-loader",
				"postcss-loader",
				"less-loader",
			]
		},
	]
},	

// 提取 css

plugins: [
	new MiniCssExtractPlugin({
		filename: 'css/[name].[hash:8].css'
	}),
]

css 压缩

目的就是减少项目体积

npm i optimize-css-assets-webpack-plugin -D
// webpack.prod.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');


plugins: [
	// 压缩 css
	new OptimizeCssAssetsPlugin()
]

减少打包文件

Tree Shaking

Tree Shaking 用于剔除一些引入了却没有使用的代码。

 //package.json 
 // 需要排除 「.css」和「.vue」文件,样式才能生效
 "sideEffects": [
    "*.css",
    "*.vue"
  ],

externals

打包的时候把这些 externals 配置的包剔除掉,减少体积。然后使用 CDN 的方式引入这些包。

  // webpack.prod.js
  externals: {
    "vue": "Vue",
	"vue-router":"VueRouter"	
  }
<!-- index.html -->
<!-- cdn 引入 externals 排除的依赖 -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.runtime.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.5.2/vue-router.min.js"></script>

依赖版本

由于最新版本是 webpack5 部分插件也会同步更新,插件的最新版本在webpack 4上不适用,可以参考下面依赖版本进行安装!

  "devDependencies": {
    "@babel/core": "^7.4.4",
    "@babel/preset-env": "^7.14.8",
    "autoprefixer": "^10.3.1",
    "babel-loader": "^8.2.2",
    "cache-loader": "^4.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^5.2.7",
    "file-loader": "^6.2.0",
    "hard-source-webpack-plugin": "^0.13.1",
    "html-webpack-plugin": "^4.5.2",
    "less": "^3.5.0",
    "less-loader": "^7.3.0",
    "mini-css-extract-plugin": "^1.6.2",
    "optimize-css-assets-webpack-plugin": "^6.0.1",
    "postcss-loader": "^4.3.0",
    "speed-measure-webpack-plugin": "^1.5.0",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^4.2.3",
    "thread-loader": "^3.0.4",
    "url-loader": "^4.1.1",
    "vue-loader": "^15.9.6",
    "vue-template-compiler": "^2.6.14",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.2",
    "webpack-merge": "^5.8.0"
  },

文章 demo 已经提交到 github , 请自取 ———— 「webpack 4 demo」

觉得还不错记得 「点个赞」!谢谢