从零开始的Webpack原理剖析(八)—— Webpack优化
前言
这篇文章我们稍微放松下,讲一讲webpack
的一些常用的配置和一些优化,对我们的项目有着极大的便利。
缩小查找范围/简写相关配置
先看下常用的配置项的示例:
// webpack.config.js
{
...
// 配置如何查找项目文件中中引入的模块
resolve: {
extensions: ['.js', '.json', '.scss', '.css'],
alias: {
'@': path.resolve(__dirname, 'src')
},
modules: [path.resolve(__dirname, 'my_modules'), 'node_modules'],
mainFields: ['style', 'main'],
mainFiles: ['style', 'index']
},
// 配置项和resolve是一模一样的,区别就是这个配置项是用来查找loader的
resolveLoader: {
......
}
...
}
- extensions: 默认配置为
['.js', '.json']
;在import
和require
的时候,文件名不需要加后缀名,webpack
会依次按照extensions
配置项中的后缀名进行查找尝试。 - alias: 别名配置项,这个在项目中非常常见,比如可以将我们的模块路径
src
文件夹,简化成一个别名@
,这样在某一个文件中,导入模块就可以写成import xxx from '@/xxxxx'
了,非常简便。 - modules: 默认配置为
['node_modules']
;即webpack
在打包的时候,默认寻找第三方依赖的文件目录,如果我们在加上自己的目录路径,如[path.resolve(__dirname, 'my_modules'), 'node_modules']
,那么webpack
就会优先在我们自己的目录去寻找依赖包,找不到再去node_modules
中寻找。一般这个配置项,只会在之前提到过的,自定义的loader
和plugin
如果不发布到npm
上,而是写在项目内部,则可能会用到这个配置。 - mainFields: 一般来说我们在引入外部依赖的时候,都会默认按照依赖包的
package.json
中的main
配置的路径,当然这个路径也是可以改的,比如我们在使用import bootstrap from 'bootstrap'
的时候,会默认寻找bootstrap
包里边package.json
文件中main
的路径,那么如果我们将配置改为mainFields: ['style', 'main']
,那么再import bootstrap from 'bootstrap'
的时候,引入的就是package.json
文件中style
的路径了,找不到style
的时候,再去找main
。 - mainFiles: 和上边的配置项类似,如果第三方包里边,没有
package.json
文件,那么就会按照配置的顺序来引入入口文件,比如我们有个第三方包叫my_package
,里边只有一个文件叫style.js
,那么当我们配置了mainFiles: ['style', 'index']
的时候,在项目中import myPackage from 'my_package'
的时候,就会默认引入style.js
的文件,找不到的话再去找index.js
文件。
module配置项优化
// webpack.config.js
{
...
module: {
// 不需要递归解析的模块
noParse: /jquery|lodash|chartjs/
......
}
...
}
- module.noParse: 加上此配置项的目的就是告诉
webpack
,在遇到这些模块的时候,不需要进行递归解析了,因为这些大型的库,没有第三方的依赖,配置完这个字段,如果项目中用到了这些库,可以提高webpack
整体的构建速度。
webpack
一些优化插件
IgnorePlugin: 可以让webpack
忽略某些特定的模块,在打包的时候,不把这些模块打包进最终文件。
一个很经典的例子就是在使用moment
库的时候,因为其语言包体积比较大,如果不做任何配置,则会把所有的语言包,都打包进最终的结果;而我们在开发的时候,又用不到那么多的语言包,所以我们不需要把所有的语言包打包进最终文件中。
import moment from 'moment'
import 'moment/locale/zh-cn'
console.log(moment().fromNow());
// webpack.config.js
const webpack = require('webpack')
{
...
plugins: [
new webpack.IgnorePlugin({
// 依赖包目录正则匹配
contextRegExp: /moment$/,
// 资源文件的正则匹配
resourceRegExp: /locale/
})
]
...
}
配置完后,执行npm run build
命令,发现打包结果中,只有我们import
的中文包被打包进来了,其他的语言包并没有被打包进来。
speed-measure-webpack-plugin: 耗时分析插件,我们在进行webpack
优化的时候,除了一些常见的配置优化,肯定需要一个量化的指标来分析,哪个地方耗时比较久,这样才能够对症下药,进行优化,那么就需要使用到这个插件了。使用起来也很简单
// webpack.config.js
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smw = new SpeedMeasureWebpackPlugin()
// 用smw.srap包裹我们的webpack配置即可
module.exports = smw.srap({
...
})
当我们再次执行npm run build
命令的时候,就会看到终端中显示了各个插件和loader
的耗时了。

webpack-bundle-analyzer: 生成代码报告分析
配置非常简单,之后执行npm run build
命令,就会自动启动一个本地服务器,如下图所示,展示打包结果的文件大小和关系。
// webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
plugins: [new BundleAnalyzerPlugin()]
}
用webpack
构建一个第三方库
在日常工作中,很可能我们需要写一个组件库,或者是一个工具库,发布到npm
上,供各个业务线来安装使用,那么我们也可以使用webpack
来对这些库进行打包。会用到output
中的几个属性,我们一一举例讲解:
libraryTarget
设置为var
// webpack.config.js
const path = require('path')
module.exports = {
entry: {
main: './src/index.js'
},
mode: 'development',
devtool: false,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
clean: true,
// 给这个库起一个导出的名字
library: 'calculator',
// 导出的方式,var代表生成一个全局变量
libraryTarget: 'var'
}
}
// src/index.js
// 比如我们自己写了一个如下的计算库,里边有两个方法,一个加法一个减法
module.exports = {
add(a, b) {
return a + b
},
minus(a, b) {
return a - b
}
}
我们执行npm run build
命令,进行打包,可以在打包结果中看到,在第一行定义了一个全局变量calculator

然后再最后一行,又将require
的结果赋值给了这个全局变量,所以可以直接通过<script>
标签引入的方式来使用

那么如何使用呢?因为是打包一个工具库,所以我们把他当成一个外部工具库来使用就好了,我们可以在打包结果同级新建一个index.html
文件,然后把打包结果引入进来,来验证一下,我们的库能不能用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入我们打包的库 -->
<script src="./main.js"></script></head>
<body>
<script>
// 直接使用calculator,因为库的导出方式是给window全局对象新增了一个属性calculator
console.log(calculator)
</script>
</body>
</html>
我们通过浏览器打开index.html
文件可以发现,控制台打印出来了两个方法add
和minus
,正是我们库里边提供的2个方法。
libraryTarget
设置为commonjs
别的配置项不变,我们把libraryTarget
的值改为commonjs
,然后再次打包,观察打包结果:

可以发现,和之前生成全局变量不同,这种方法,是将calculator
挂载到了exports
对象上,那么如何使用呢?很简单,我们在项目中只需要这样引入并使用:
// 通过require的方式进行引入
const main = require('./main.js')
console.log(main.calculator.add(1, 2))
libraryTarget
设置为commonjs2
别的配置项不变,我们把libraryTarget
的值改为commonjs2
,然后再次打包,观察打包结果,发现别的地方都没变,只是最后把calculator
赋值给了module.exports
仅此而已,和commonjs
可以说是一样的,使用方法也一样

libraryTarget
设置为umd
一说umd
,可能懂的都懂了,就是可以兼容各种模式,可以用amd、commonjs、script
的方式进行引入,那么打包结果我们也简单的看一下,因为兼容各种模式,所以在打包文件最上边要进行判断,用了那种方式,就用那种方式给calculator
进行赋值。

css
相关优化
我们可以单独把css
提取出来,因为html
文件非常大的时候,加载起来会比较慢,而css
和js
加载是可以并行的,所以,我们可以利用插件,将css
单独提取出来,同样,修改下配置文件。
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js'
},
mode: 'development',
devtool: false,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
clean: true
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
}
}
// src/index.js
import './index.css'
const div = document.createElement('div')
div.innerHTML = '我是一个测试的div'
div.className = 'bg-yellow'
document.body.appendChild(div)
/* src/index.css */
.bg-yellow {
background-color: orange;
}
div {
color: red;
}
我们执行npm run build
打包命令后可以发现,css
文件里边的内容,全部都被打包进了main.js
文件中,dist
文件夹中,也是只有index.html
和main.js
两个文件被打包出来。

但,当我们用了抽离css
的插件,就可以大大减少main.js
文件的体积,优化加载速度,首先要安装这个插件,之后再去增加如下配置。
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
main: './src/index.js'
},
mode: 'development',
devtool: false,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
clean: true
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].css'
})
],
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
]
}
}
再次执行npm run build
命令,可以看到,终端中,有3个文件被打包了,而且css
文件也被抽离了出来成为了单独的文件,main.js
的体积得到了大幅度的减少,从浏览器中打开index.html
文件查看结果也是一切正常。
同样,上边的插件,只是将css
抽离出来,但是并没有进行压缩,我们可以用另一个插件对抽离出来的css
进行压缩:
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js'
},
mode: 'development',
devtool: false,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
clean: true
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].css'
}),
new OptimizeCssAssetsWebpackPlugin()
],
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
]
}
}
再次执行npm run build
命令,查看打包结果可以发现,css
文件被成功压缩了。
结语
关于一些webpack
优化,项目中常用的有这些,之后也会对新的知识点及时的进行补充说明。
转载自:https://juejin.cn/post/7185149829108990011