webpack性能优化主要有两方面:构建速度优化,产出速度优化
对生产环境产出代码合理分包,不重复加载,使其体积更小,速度更快,内存使用更少。浏览器加载页面时用的时间越短越好所以构建出的文件越小越好,减少浏览器需要发起请求的数量,减少下载静态资源的时间。webpack在生产环境构建时会做一些额外工作如代码压缩等,可优化前端资源加载性能。
构建速度优化-开发体验和效率
优化babel-loader
开启babel-loader缓存。babel-loader后加?cacheDirectory
如果es6代码没有改,不会重新编译
控制babel编译范围。用include确定包含的范围,或者exclude确定不包括的范围。(排除node_modules)
项目中配置babel-loader
webpack.config.common.js文件 webpack生产环境中配置
module: {
rules: [
{
test: /.\ts$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
// 此处为了ts-loader第一次命中就把所有options被缓存到
appendTsSuffixTo: [/\.vue$/],
},
},
],
},
{
test: /.\js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
cacheDirectory:true,
},
},
]
}
IgnorePlugin避免引入无用模块
如引入moment,js,只需要中文和英文的模块
业务代码中手动引入中文和英文的语言包,webpack配置中
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
忽略掉moment里的locale语言包,可以减少无用模块引入
noParse避免重复打包
如社区中min.js这种一般是已经打包过的,无需再次打包。可在module.noParse中配置不需打包的文件。注意,react.min.js不能使用,没有采用模块化,需要打包
noParse和Ignore区别:Ignore直接用不引入,代码中没有。noParse会引入,但不打包。两个都能提高构建速度,Ignore还可以优化线上性能。
happyPack多进程打包 是plugin
js是单线程,webpack实际也是单线程打包。
比如要将babel的解析放在新进程中,那么module.rule中对js的解析需要改成:
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
},
然后需要在plugin中配置babel的happyPack实例:
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
ParallelUglifyPlugin多进程压缩JS
webpack本身内置了uglify压缩JS,压缩JS本身成本较高,可以多进程开启,直接plugin实例化。
根据需要开启多进程,不一定需要。如果项目较大,打包较慢,开启多进程可提高速度。但开启多进程本身就需要成本时间,小项目没必要,打包时间反而会增加
热更新HotModuleReplacementPlugin
自动刷新和热更新区别:
自动刷新速度较慢,状态会丢失
热更新不会刷新,状态不丢失,新代码可立刻生效
webpack.config.dev.js文件 webpack本地环境中配置
plugin中实例化,devSever中设置hot为true
devServer: {
host: '0.0.0.0',
port,
openPage: `http://${ip}:${port}/${publicPath}`,
hot: true,
inline: true,
liveReload: true,
historyApiFallback: {
rewrites: [
{
from: /.*/g,
to: `/${publicPath}`,
},
],
},
},
plugins: [
// Only update what has changed on hot reload
new webpack.HotModuleReplacementPlugin(),
new UrlLogPlugin(),
],
DLLPlugin动态链接库插件
webpack内置了DLLPlugin,主要包含两个插件:
DllPlugin:打包出dll文件,先进行一遍预打包,生成dll文件
DLLReferencePlugin:在开发环境使用dll文件
一般需单独定义一个dll.config.js
output: {
path: path.join(__dirname, '../build'), // 放在项目的/build目录下面
filename: '[name].dll.js', // 打包文件的名字
library: '[name]_library' // 可选 暴露出的全局变量名
// vendor.dll.js中暴露出的全局变量名。
// 主要是给DllPlugin中的name使用,
// 故这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugin
new webpack.DllPlugin({
path: path.join(__dirname, '../build', '[name]_manifest.json'), // 生成上文说到清单文件,放在当前build文件下面,这个看你自己想放哪里了。
name: '[name]_library',
context: __dirname,
}),
定义dll命令,进行dll打包
最后输出dll和manifest文件。dll文件就是所有打包的文件内容。manifest作用为索引,负责引导实际对于dll里模块的引用
实际应用中,需要在dev的html中进行script引用,可用DLLReferencePlugin插件引用manifest文件,AddAssetHTMLPlugin引用dll文件即可
产出速度优化
webpack优化产出代码-产品性能提高
核心:代码体积更小,合理分包,不重复加载,速度更快,内存使用更少
小图片用base64编码
url-loader 设置limit值控制图片大小,(小于该文件大小的图片转为base64格式)将处理后的图片引入项目,减少http请求
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 3*1024 // 3k
}
}
}
bundle需要加hash
根据文件内容算hash值,客户端可实现缓存管理使用。js代码如果没有变化,再次上线也可以复用没变的bundle块
bundle:webpack打包后的各个文件,一般和chunk(代码块)一对一关系,bundle就是对chunk进行编译压缩打包等处理后的产出。
懒加载
webpack支持异步加载模块的特性。
按需加载:如一个应用有3个页面,首页加载时只加载首页的逻辑,其他两个页面跳转到页面后在异步加载。
原理:动态向页面找那个插入script标签。
webpack支持实现方式有两种:
commonjs形式的require
es6的异步import()
// 普通加载
import xxx from './xxx'
懒加载(按需加载)
const xxx = () => import('./xxx')
提取公共代码
抽离一些通用代码和第三方的。
webpack 3.x前版本用CommonsChunkPlugin做代码分离,4.x把相关功能包在optimization里的splitChunks
参数:
chunks选项,决定要提取那些模块。
默认是async:只提取异步加载的模块出来打包到一个文件中。
异步加载的模块:通过import(‘xxx’)或require([‘xxx’],() =>{})加载的模块。
initial:提取同步加载和异步加载模块,如果xxx在项目中异步加载了,也同步加载了,那么xxx这个模块会被提取两次,分别打包到不同的文件中。
同步加载的模块:通过 import xxx或require(‘xxx’)加载的模块。
all:不管异步加载还是同步加载的模块都提取出来,打包到一个文件中。
minSize选项:规定被提取的模块在压缩前的大小最小值,单位为字节,默认为30000,只有超过了30000字节才会被提取。
maxSize选项:把提取出来的模块打包生成的文件大小不能超过maxSize值,如果超过了,要对其进行分割并打包生成新的文件。单位为字节,默认为0,表示不限制大小。
minChunks选项:表示要被提取的模块最小被引用次数,引用次数超过或等于minChunks值,才能被提取。
maxAsyncRequests选项:最大的按需(异步)加载次数,默认为 6。
maxInitialRequests选项:打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件),默认为4。
先说一下优先级 maxInitialRequests / maxAsyncRequests <maxSize<minSize。
automaticNameDelimiter选项:打包生成的js文件名的分割符,默认为~。
name选项:打包生成js文件的名称。
cacheGroups选项,核心重点,配置提取模块的方案。里面每一项代表一个提取模块的方案。下面是cacheGroups每项中特有的选项,其余选项和外面一致,若cacheGroups每项中有,就按配置的,没有就使用外面配置的。
test选项:用来匹配要提取的模块的资源路径或名称。值是正则或函数。
priority选项:方案的优先级,值越大表示提取模块时优先采用此方案。默认值为0。
reuseExistingChunk选项:true/false。为true时,如果当前要提取的模块,在已经在打包生成的js文件中存在,则将重用该模块,而不是把当前要提取的模块打包生成新的js文件。
enforce选项:true/false。为true时,忽略minSize,minChunks,maxAsyncRequests和maxInitialRequests外面选项
项目中配置
optimization: {
splitChunks: {
chunks: 'async', // 决定提取哪些模块
minSize: 0, // 超过该值才会被提取,单位字节,默认30000
maxSize: 30000, // 打包生成的文件最大值,如果超过则分隔打包生成新文件。单位字节,默认0表不限制大小
minChunks: 1, // 被提取的模块最小被引用次数,引用次数超过或等于该值才能被提取
maxAsyncRequests: 6, // 最大按需加载次数,默认6
maxInitialRequests: 4, // 打包后入口文件加载时同时加载的js文件数量(包括入口文件)默认4
automaticNameDelimiter: '~', // 打包生成的js文件名的分隔符,默认~
cacheGroups: { // 核心重点!配置提取模块的方案,每一项代表一个提取模块的方案
vendors: {
name: `chunk-vendors`, // 打包生成文件名
test: /[\\/]node_modules[\\/]/, // 匹配提取的模块资源路径或名称,值为正则或函数
priority: -10, // 优先级,值越大优先级越高。默认值0
chunks: 'initial',
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true, // true时,如果要提取的模块,打包文件已存在,则重用该模块,不重新打包
enforce: false, // true时,忽略minSize,minChunks,maxAsyncRequests,maxInitialRequests外面选项
},
},
},
},
IngorePlugin减少打包内容
cdn加速 内容分发网络
原理:优化物理链路层传输过程中的网速有限,丢包等问题提升网速。通过在各地部署服务器,形成cdn集群,提高访问速度,把资源部署到各地,用户访问是就近原则向离用户最近的服务器获取资源。
tree-shaking移除没用的js代码
打包过程中移除js上下文没引用的代码。依赖于es6的import和export语句,用来检测代码模块是否被导入,导出,被 js文件引用。
es6 Module的模块依赖解析在代码静态分析过程中进行,可在编译阶段就获取到整个依赖树(不是运行时)这一点webpack提供tree-shaking功能进行代码静态分析层面的优化。
webpack.config.js里Module为production自动开启tree-shaking。但只有用es6 Module才可生效,不能用commonjs。
原因:commonjs动态引入,执行时引入。es6 Module静态引入,编译时引入。
用Scope Hosting
该插件只适用于webpack直接处理es6模块。
针对NPM中第三方模块优先采用jsnext:main中指向es6模块化语法的文件
大型工程中模块引用层级一般较深,产生较长引用链,Scope Hosting可将纵深的引用链拍平,使得模块本身和其引用的其他模块作用域处同级,可去掉一部分webpack附加代码减小资源体积。
作用:代码体积更小,创建函数作用域更少,代码可读性更好。
使用方式:
引入webpack内部插件 ModuleConcatenationPlugin插件(默认在生产模式下已启用,若要在其他模式下启用concatenation,可手动添ModuleConcatenationPlugin或用optimization.concatenateModules选项)
module.exports = {
resolve: {
mainFields: ['jsnext:main', 'browser', 'main'] // 针对npm中的第三方模块优先采用jsnext:main中指向的ES6模块化语法的文件
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin() // 开启Scope Hoisting
]
}