webpack5 优化产出代码
webpack
产出代码做性能优化的效果无非是下面三种效果:
- 体积更小
- 合理分包,不重复加载
- 速度更快、内存使用更少
下面将通过介绍以下方案来达到以上目的:
- 小图片使用base64编码:
url-loader
-
bundle
加hash
:应用缓存 - 使用
import
懒加载模块 - 使用
splitChunks
提取公共组件 - 使用
IgnorePlugin
忽略无用的模块 - 使用
CDN
加速:添加publicPath
- 使用
production
模式:- 自动开启代码压缩,使得打包体积更小
-
Vue,React
等会自动删除调试代码(比如开发环境的warning
警告),体积会比开发时更小 - 启动
tree-shaking
,删除无用的代码
- 使用作用域提升:
Scope Hosting
其实里面很多方案,我们在基础配置和高级配置时已经提及过:
小图片base64编码
通过设置url-loader
的阀值来控制,小于阀值的图片使用base64
编码,以此来减少http
请求
module: {
noParse: /jquery|lodash/,
rules: [
{
test: /\.(jpg|png|jpeg|gif)$/,
use: {
loader:'url-loader',
options: {
limit: 8 * 1024, //限制 8kb 以下使用base64
esMoudle: false,
name: '[name]-[hash:10].[ext]',
// 打包到/images目录下
outputPath: 'images'
}
}
}
]
},
bundle 加 hash 值
给产出的文件增加hash
值,当内容没有变化时,生成的文件名将不会变化,浏览器就会应用缓存,从而提升加载速度
output: {
filename: '[name].[contenthash:8].js',
path: distPath // 输出目录
},
懒加载
使用import
方式懒加载组件
// index.js
setTimeout(() => {
// 直接使用import导入即可,这样加载的模块,相当于一个独立的chunk存在
import('./common/dynamicData.js').then(res => {
console.log(res.default.msg);
})
}, 1000)
提取公共代码
在optimization
中配置splitChunks
属性,设置cacheGroups
分组,一般分为两个组:verdors
(用于提取第三方库)和common
(用于提取自定义的公共模块)
针对第三方库,几乎不会改变,提取出来后,改动业务代码不会动到第三方库的打包文件,这样,就能命中缓存,加快加载速度
针对公共模块,只用加载一次,就可以在多个模块中使用
// webpack.prod.js
optimization: {
splitChunks: {
chunks: 'all', // 表示要分割的chunk类型:initial只处理同步的; async只处理异步的;all都处理
// 缓存分组
cacheGroups: {
// 第三方模块
verdors: {
name: 'verdor', // chunk名称
test: /node_modules/, // 设置命中目录规则
priority: 1, // 优先级,数值越大,优先级越高
minSize: 0, // 小于这个大小的文件,不分割
minChunks: 1 // 最少复用几次,这里意思是只要用过一次就分割出来
},
// 公共模块
common: {
name: 'common',
minChunks: 2,
priority: 0,
minSize: 0,
minChunks: 2 // 只要引用过2次,就分割成公共代码
}
}
}
}
IgnorePlugin
IgnorePlugin
可以帮助我们忽略某些库不需要的模块,从而实现按需加载,减少打包体积
// webpack.prod.js或webpack.common.js都可以
const webpack = require('webpack')
const prodConfig = {
plugins: [
// webpack4写法
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
// webpack5写法
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/, // 忽略content设置的库中的某个文件夹
contextRegExp: /moment$/, // 要被忽略某部分内容的库
})
]
}
使用 CDN 加速
在输出文件配置中将CDN
路径添加至publicPath
中(针对js和css文件);
针对图片,也可以在loader
的配置中添加publicPath
output: {
// filename: 'bundle.[chunkhash].js', // 输出文件名,一般要加上hash
filename: '[name].[contenthash:8].js',
path: distPath, // 输出目录
publicPath: 'http://cdn.xxx.com'
},
// 或者是将图片放到cdn中
module: {
rules: [
{
test: /\.(jpg|png|jpeg|gif)$/,
use: {
loader:'url-loader',
options: {
limit: 8 * 1024, //限制 8kb 以下使用base64
esMoudle: false,
name: '[name]-[hash:10].[ext]',
// 打包到/images目录下
// outputPath: 'images', // 有了publicPath,会自动忽略outputPath,这是针对放在静态资源服务器上的目录
publicPath: 'http://cdn.xxx.com'
}
}
}
]
}
这样打包出来的index.html
中的 静态资源,会自动添加CND
地址前缀
<script defer="defer" src="http://cdn.xxx.com/verdor.03535a89.js"></script>
<script defer="defer" src="http://cdn.xxx.com/index.c6d170da.js"></script>
图片放至cdn服务器
这一步还需要我们将对应的资源文件给放至对应的CND
服务器地址中
启用production模式
mode: 'production', // 生产环境
启用production模式,会使打包的体积更小,webpack4
以后,只要开启生产模式,就会自己帮我们实现以下功能:
- 自动开启代码压缩,比如删除注释、空格等,当然可以视项目情况,来判断要不要使用
webpack-parallel-uglify-plugin
开启多进程压缩 -
Vue,React
等会自动删除调试代码(比如开发环境的warning
警告),体积会比开发时更小 - 启用
tree-shaking
,删除无用的代码,当注意,只有在ES Module
才可以使用- ·
ES6 Module
是静态引入,编译时时就引入模块的,所以才可以做静态分析,实现tree-shaking
-
Commonjs
是动态引入的,是执行时才引入,需要执行代码时,才知道引不引入,所以没法做静态分析,也就无法实现tree-shaking
- ·
Scope Hosting
webpack
分析依赖打包出来的文件,一般是一个模块生成一个函数,比如:
// test.js
export default 'module test'
// index.js
import test from './module-test'
console.log(test)
打包后大概是这样的:
[
function (module, exports, require) {
var module_test = require(1)
console.log(module_test['default'])
},
function (module, exports, require) {
exports['default'] = 'module test'
}
]
这样,当我们引用的模块很多时,我们打包时就会产生很多个函数,一个函数会生成一个函数作用域,当多个模块时,就会产生多个函数作用域,这样步骤创建函数作用域和执行函数、销毁函数,对js
代码的执行和内存是很不友好的
但如果我们按引用顺序,把它们合成一个函数来执行,所有操作都在一个函数执行,那么性能就会好很多,比如
// bundle.js
[
function (module, exports, require) {
// ./module-a.js
var module_test_defaultExport = 'module test'
// ./index.js
console.log(module_test_defaultExport)
}
]
它带来的好处有:
- 减小代码体积
- 创建的函数作用域更少
- 代码可读性会更好
但是,Scope Hosting
的实现也是有限制的,它必须是在ES Module
环境下使用
知道了原理,我们来看下Scope Hosting
如何配置,它是webpack
内置的一个模块,可以直接使用:
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
resolve: {
// 针对npm中的第三方库模块,优先采用jsnext:main中指向ES6模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
// 开启scope hoisting,作用域提升
new ModuleConcatenationPlugin()
]