webpack性能优化--产出代码

webpack性能优化–产出代码

体积更小
合理分包,不重复加载
速度更快, 内存使用更少

小图片base64编码

比较小的图片用base64 格式产,没必要做网络请求

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 = {
module: {
rules: [
#// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
#// 小于 5kb 的图片用 base64 格式产出
#// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,

#// 打包到 img 目录下
outputPath: '/img1/',

#// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
#// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
})

bundle加hash

内容变了hash就会变,缓存失效请求新的文件, 反之,文件名不变,用缓存

1
2
3
4
5
6
7
module.exports = {
output: {
filename: 'bundle.[contentHash:8].js', #// 打包代码时,加上 hash 戳
path: distPath,
#// publicPath: 'http://cdn.abc.com' #// 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
}

懒加载

import加载文件,和vue, react方式道理相同.
异步加载JS
index.js:

1
2
3
4
5
6
7
// 引入动态数据 - dynamic-data.js文件, webpack默认支持的方式
setTimeout(() =>{
// vue react 异步组件
import('./dynamic-data.js').then(res => {
console.log(res.default.message); #// 注意这里的default
})
},1500);

提取公共代码

webpack.prod.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
50
51
52
53
54
55
56
57
58
59
60
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')

module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
...
},
module: {
rules: [
#// 图片 - 考虑 base64 编码的情况
#// 抽离 css
]
},
plugins: [
...
],

optimization: {
#// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],

#// 分割代码块
splitChunks: {
chunks: 'all',
#/**
# * initial 入口 chunk,对于异步导入的文件不处理
# async 异步 chunk,只对异步导入的文件处理
# all 全部 chunk
# */

#// 缓存分组
cacheGroups: {
#// 第三方模块
vendor: {
name: 'vendor', #// chunk 名称
priority: 1, #// 权限更高,优先抽离,重要!!!
test: /node_modules/,
minSize: 0, #// 大小限制
minChunks: 1 #// 最少复用过几次
},

#// 公共的模块
common: {
name: 'common', #// chunk 名称
priority: 0, #// 优先级
minSize: 0, #// 公共模块的大小限制
minChunks: 2 #// 公共模块最少复用过几次
}
}
}
}
})

IgnorePlugin

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

例:
引入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/),
],
}

使用CDN加速

1.webpack配置CDN地址;(用于js, css, 图片路径)
webpack.prod.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
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
filename: '[name].[contentHash:8].js', #// name 即多入口时 entry 的 key
path: distPath,
#//1. publicPath 修改所有静态文件 url 的前缀(如 cdn 域名)http://cdn.xxx.com是CDN服务器域名
publicPath: 'http://cdn.xxx.com'
},
module: {
rules: [
#// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
#// 小于 5kb 的图片用 base64 格式产出
#/ 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,

#// 打包到 img 目录下
outputPath: '/img1/',
#// 1. 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
publicPath: 'http://cdn.xxx.com'
}
}
},
]
}
}

npm run build 生成后, 引入文件路径加了http://cdn.xxx.com/
index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
<!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>webpack demo</title>
<link href="http://cdn.xxx.com/css/main.28b49fd4.css" rel="stylesheet"></head>
<body>
<p>webpack demo</p>
<input type="text"/>
<script type="text/javascript" src="http://cdn.abc.com/common.87047fb9.js"></script><script type="text/javascript" src="http://cdn.xxx.com/index.9c930570.js"></script></body>
</html>

  1. 把dish文件夹里的东西放到CDN服务器上去
    http://cdn.abc.com/common.87047fb9.js,http://cdn.xxx.com/css/main.28b49fd4.css …..是可访问的

使用production

配置:

webpack.prod.js

1
2
3
4
5
6
7
8
module.exports = smart(webpackCommonConf, {
mode: 'production',
# 1. 不用做任何配置,就会自动开启代码压缩;(development环境不压缩代码)
# 2. Vue React等会自动删除调试代码(如开发环境的warning);
# 3. 启动Tree-Shaking (例:js文件里没有用到的函数,会自动删除,只编译有用到的)
#// 必须用ES6 Module 才能让 tree-shaking生效, commonjs 就不行.
...

Tree-Shaking作用

1. math.js:
1
2
3
4
5
6
7
8
9
10
export const sum = (a, b) => {
return a + b
}

export const mult = (a, b) => {
return a * b
}

#// ES6 Module 才能让 tree-shaking 生效
#// commonjs 就不行
ES6 Module和 commonjs区别
  1. ES6 Module 静态引入,编译时引入
  2. commonjs 动态引入,执行时引入
  3. 只有ES6 Module 才能静态分析,实现Tree-Shaking

commonjs动态引入

1
2
3
4
5
let apiList = require('../config/api.js');
if(isDev) {
# //可以动态引入执行时引下
apiList = require('../config/api_dev.js);
}

ES6 Module import();

1
2
3
4
5
6
7
# // "js 文件顶部同步的引用" ES6 的 import 是静态引入,打包时引入,即默认都打包在一起。这不是异步引入.
import apiList from('../config/api.js');

if(isDev) {
# //这里的import()是动态引入,做异步,语法不允许, 编译时报错,只能静态引入;
import apiList from('../config/api_dev.js);
}

2. index.js引入math.js,只用到sum,没有用到mult

mode: ‘production’, 启动Tree-Shaking,编译后的文件里没有mult,(而development, 编译后的文件里有mult).

1
2
3
4
5
6
7
8
9
#// 引入 css
import './style/style1.css'
import './style/style2.less'

#// 引入 math.js
import { sum } from './math'

const sumRes = sum(10, 20)
console.log('sumRes', sumRes)

3.css-tree-shaking

purifycss-webpack:移除没有使用到css样式
安装 npm install purifycss-webpack purifycss –save-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');
const path = require('path');
const glob = require('glob')

module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
new PurifyCSSPlugin({
paths: glob.sync(path.join(__dirname, './*.html'))
})
]
}

Scope Hosting

打包多个函数合并成1个函数

  1. 代码体积更小
  2. 创建函数作用域更少
  3. 代码可读性更好

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
resolve: {
# //针对 NPM 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
# // 开启 Scope Hoisting
new ModuleConcatenationPlugin(),
]
}

#webpack5,ModuleConcatenationPlugin 废弃,由 optimization.concatenateModules 替代,生产环境默认开启

效果

  1. module,2个js文件

    1
    2
    3
    4
    5
    6
    # hello.js
    export default 'Hello'

    # main.js
    import str from './hello.js'
    console.log(str);
  2. 默认打包结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [
    #第1个函数
    (function (module, _webpack_exports_, _webpack_require_) {
    .......
    console.log(......)
    }),
    #第2个函数
    (function (module, _webpack_exports_, _webpack_require_) {
    .......
    _webpack_exports_["a"] = ('Hello');
    })
    ]
  3. 开启 Scope Hoisting 打包结果

    1
    2
    3
    4
    5
    6
    [
    #多个函数合并成1个函数
    (function (module, _webpack_exports_, _webpack_require_) {
    var hello = ('Hello');
    console.log(hello);
    }),