webpack性能优化--构建速度

webpack优化构建速度(是否可用于生产环境)

可用于生产环境

优化bable-loader
IgnorePlugin
noParse
happyPack
ParalleIUglifyPlugin

不可用于生产环境

自动刷新
热更新
DIIPlugin

优化 babel-loader(编译ES6到ES5)

1
2
3
4
5
6
7
{
test: /\.js$/,
use: [loader: 'babel-loader?cacheDirectory' ], #//开启缓存
include: path.resolve(_dirname, 'src'), # // 明确范围
# // exclude: /(node_modules|bower_components)/, //排除
#// 排除范围include和exclude两者选一个即可
},

IgnorePlugin (避免引入插件中的某些包), 直接不引入,代码中没有

例: moment 获取日期插件
引入moment插件,默认会引入所有的语言JS代码,代码过大,
如何只引入中文 ?
index.js

1
2
3
4
5
import moment form 'moment' #//引入moment插件
import 'moment/locale/zh-cn' #//手动引入中文语言包

moment.locale('zh-cn'); #设置语言为中文
console.log(moment().format('ll')); #2020年X月X日

webpack.common.js

1
2
3
4
5
6
module.exports = {
plugins: [
#// 忽略 moment 下的 /locale 目录,(忽略所有的语言包)
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
],
}

noParse ( 引入,避免打包),只引入,不打包

webpack.common.js

1
2
3
4
5
6
7
module.exports = {
module: {
#// 独完整的'react.min.js' 文件就没有采用模块化
#// 忽略对'react.min.js' 文件的递归解析处理
noParse: [/react\.min\.js$/],
}
}

happyPack ( 多进程打包工具,打包更快)

JS单线程,开启多进程打包
提高构建速度 (特别是多核CPU)
webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const HappyPack = require('happypack');
module.exports = {
module: {
rules: [
#// js
{
test: /\.js$/,
#// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
#// exclude: /node_modules/
},
]
},
plugins: [
#// happyPack 开启多进程打包
new HappyPack({
#// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
#// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
]
}

happypack 已经不再维护了, 可用thread-loader

ParallelUglifyPlugin ( 多进程代码压缩JS)

webpack 内置Uglify工具压缩JS
JS单线程, 开启多进程压缩更快
webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
module: {
rules: [
...
]
},
plugins: [
#// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
#// 传递给 UglifyJS 的参数
#// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
#// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
#// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
#// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]
}

关于开启多进程

  1. 项目较大,打包较慢,开启多进程能提高速度;
  2. 项目较小,打包很快,开启多进程会降低速度(进程开销).
  3. 按需使用.

自动刷新 ( 一保存代码,编译完之后浏览器就自动刷新页面)

watch监听

配置watch:true只是开启了webpack的监听模式,文件改变会触发事件,(一般情况不会用).
webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
watch: true, #//开启监听, 默认为false
# // 注意,开启监听之后webpack-dev-server会自动开启刷新浏览器!!!

# // 监听配置
watchOptions: {
ignored: /node_modules/, # //忽略哪些

# // 监听到变化发生后会等300ms再去执行动作, 防止文件更新太快导致重新编译频率太高
aggregateTimeout: 300, #//默认为300ms

#// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
poll: 1000 #//默认每隔1000毫秒询问一次
}

devServer 把webpack-dev-server会自动开启刷新浏览器带上,不用再去配watch

devServer只是类似监听了这些事件,注册回调函数(比如刷新浏览器)
webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = smart(webpackCommonConf, {
mode: 'development',
devServer: {
port: 8080,
progress: true, #// 显示打包的进度条
contentBase: distPath, #// 根目录
open: true, #// 自动打开浏览器
compress: true, #// 启动 gzip 压缩

#// 设置代理
proxy: {
#// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',

#// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
}
}

热更新 ( 一保存代码,编译完之后浏览器不用刷新页面,代码就已更新)

HotModuleReplacementPlugin

  1. index引入变化: dev-server,
  2. 引用HotModuleReplacementPlugin插件
  3. 设置hot,告诉devServer热更新已开启
  4. 设置热更新的范围(配置热更新哪些文件,及更新后的处理事件)
    如果网页自动刷新能够用,就没必要开启热更新,如果网页刷新比较慢,会引响开发,才会去用热更新

webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = smart(webpackCommonConf, {
mode: 'development',
entry: {
#// index: path.join(srcPath, 'index.js'), 之前的引入方法
# // 1. index引入变化: dev-server,
index: [
'webpack-dev-server/client?http://localhost:8080/', # // http://localhost:8080, 是本地local网址
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
other: path.join(srcPath, 'other.js')
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development')
}),
# // 2. 引用HotModuleReplacementPlugin插件
new HotModuleReplacementPlugin()
],
devServer: {
port: 8080,
progress: true, #// 显示打包的进度条
contentBase: distPath, #// 根目录
open: true, #// 自动打开浏览器
compress: true, #// 启动 gzip 压缩

# // 3. 设置hot,告诉devServer热更新已开启
hot: true,

proxy: {
...
}
},
}

index.js
`````` bash
# // 4. 设置热更新的范围math.js
import { sum } from './math'

# // 增加,开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}

DIIPlugin 动态链接库插件

把第三方插件事先打包好,做为DIL,之后打包时引用它,不需要每次打包都去打包第三方插件

使用背景:

  1. 前端框架如vue React, 体积大,构建慢
  2. 较稳定,不常升级版本
  3. 同一个版本只构建一次即可,不用每次都重新构建

使用

webpack已内置DIIPlugin支持
DIIPlugin - 打包出dII文件
DIIReferencePlugin - 使用 dII文件

webpack.dll.js (DIIPlugin - 打包出dII文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
mode: 'development',
#// JS 执行入口文件
entry: {
#// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
#// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
#// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
#// 输出的文件都放到 dist 目录下
path: distPath,
#// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
#// 之所以在前面加上 _dll_ 是为了防止全局变量冲突,(例: 打包出来生成的文件react. dII.js里面会有全局部变量名_dll_react)
library: '_dll_[name]',
},
plugins: [
#// 接入 DllPlugin
new DllPlugin({
#// 动态链接库的全局变量名称,需要和 output.library 中保持一致
#// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
#// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
#// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}

引入

引入react.dll.js

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>

<script src="./react.dll.js"></script>
</body>
</html>

配置地址react.manifest.json

webpack.dev.js (DIIReferencePlugin - 使用 dII文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const path = require('path')
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')

#// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

module.exports = merge(webpackCommonConf, {
mode: 'development',
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ #// 第二,不要再转换 node_modules 的代码
},
]
},
plugins: [
new webpack.DefinePlugin({
#// window.ENV = 'production'
ENV: JSON.stringify('development')
}),
#// 第三,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
#// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
],
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩

#// 设置代理
proxy: {
#// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',

#// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
}
})