# webpack性能优化
# HMR
HMR: hot module replacement 热模块替换
作用:一个模块发生变化,只会重新发包这有个模块(而不是打包所有模块),极大提升构建速度
# 开启HMR功能
devServer: {
contentBase: resolve(__dirname,'build'),
compress: true,
port: 3000,
// 开启HMR功能
open: true
}
css文件:可以使用HMR,因为style-loader内部实现了
js文件:默认不能使用HMR
注意:HMR功能对js的处理,只能处理非入口js文件的其他文件
if(module.hot) {
// 一旦 module.hot为true,说明开启了HMR功能. 让HMR功能代码生效
module.hot.accept('./print.js',function() {
// 方法会监听 print.js 文件的变化, 一旦发生变化,其他模块不会重新打包构建
// 会执行后面的回调函数
print()
})
}
html文件:默认不能使用HMR功能,同时会导致问题: html文件不能热更新了,解决办法 修改entry入口,将html文件引入 entry: ['./src/js/index.js','./src/index.html']
# SourceMap
source-map: 一种提供源代码到构建代码映射的技术(如果构建代码出错了,通过映射可以追踪代码错误)
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
内联 和 外联 的区别:1. 外部生产了文件,内联没有 2. 内联构建速度更快
- source-map: 外联
- 错误代码准确信息 和 源代码的错误位置
- inline-source-map: 内联 --> 只生成一个内联sourceMap
- 错误代码准确信息 和 源代码的错误位置
- hidden-source-map: 外联
- 不能追踪源代码错误,只能提示到构建后代码的错误位置
- eval-source-map: 内联 --> 每一个文件都生产sourceMap,都在eval函数中
- 错误代码准确信息 和 源代码的错误位置
- nosources-source-map: 外联
- 错误代码准确信息,但是没有任何源代码信息
- cheap-source-map: 外联
- 错误代码准确信息,但是没有任何源代码信息
- 只能精确到行,不能精确到列
- cheap-module-source-map: 外联
- 错误代码准确信息 和 源代码的错误位置
# 环境选择
# 开发环境
开发环境:速度快,调试更友好
速度快(eval > inline > cheap > ....)
eval-cheap-source-map
eval-source-map
调试更友好
source-map
cheap-module-source-map
cheap-source-map
eval-source-map || eval-cheap-module-source-map
vue react脚手架默认使用的是eval-source-map
# 生产环境
生产环境:源代码要不要隐藏? 调试要不要更友好
隐藏代码
nosources-source-map
hidden-source-map
调试
source-map
cheap-module-source-map
# cache
# babel缓存
cacheDirectory: true
{
test: /\.js$/,
exclude: /node_modules/, // 排除
loader: 'babel-loader',
options: {
// 预设:指示babel做怎样的兼容处理
presets: [
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
],
// 开启babel缓存
cacheDirectory: true
}
},
# 文件缓存
# hash:每次webpack构建时会生成的唯一的hash值
output: {
filename: 'js/built.[hash:10].js',
path: resolve(__dirname, 'build')
},
new MiniCssExtractPlugin({
filename: 'css/built.[hash:10].css'
}),
问题:因为js css 同时使用一个hash值,如果重新打包,会导致所有缓存失效
# chunkhash
根据chunk生成的hash值,如果打包来源于同一个chunk,那么它的hash值就一样
问题:js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk
# contenthash
根据文件的内容生成hash值,不同的文件hash一定会不一样
# 总结
babel缓存 让第二次打包构建速度更快,文件缓存 让代码上线运行缓存更好用
# tree shaking
tree-shaking: 去除无用代码
前提:1. 必须使用es6模块化 2. 开启production环境
作用: 减少代码体积
在package.json中配置 "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
问题: 可以会把css / @babel/polyfill (副作用)文件干掉
"sideEffects": ["*.css"]
# code split
# lazy loading
# 如何优化
我们将index.js中的异步代码打开,并且注释掉同步代码,然后使用打包命令npm run start
,成功打包后我们使用开发者工具中的Network,可以看出页面加载时已经同时加载了打包相关的代码:
我们不妨使用一个监听点击的函数来决定页面中DOM的挂载,所以我们index.js写成这样:
// 异步代码输出
function getComponent() {
return import( /* webpackChunkName: "lodash" */ 'lodash').then(({
default: _
}) => {
var dom = document.createElement('div');
dom.innerHTML = _.join(['Copy', 'Splitting'], ' ');
return dom;
})
}
// 使用点击事件触发挂载DOM节点
document.addEventListener('click', () => {
getComponent().then(dom => {
document.body.appendChild(dom);
})
})
此时我们再去运行npm run start
,成功打包后我们发现bundle打包文件夹中依然还是那三个文件,此时我们打开页面去看Network,发现我们未点击页面之前,只加载了一个打包文件和静态页面index.html,而第三方插件lodash并没有载入进来:
此时只有我们触发点击事件时,才会去调用 getComponent() 的方法,也才会去执行lodash的异步导入,此时页面才回去载入 vendors~lodash.js 这个打包文件,这样也就形成了一个Lazy Loading懒加载模式,这相比与同步代码时直接引入的方式更有优化的地方~
# 其他场景
如果使用过Vue/React这些框架的话,里面都是有路由跳转的概念,就好比一个SPA单页面应用,有首页模块、推荐页模块、购物车模块、个人中心模块等,此时我们把整个应用的代码打包上线而不去做分割时,那用户载入我们首页的时候,是不是都要加载我们这个应用逻辑压缩代码?这样肯定是不行的,所以就有了路由跳转的概念,只有我们切换到那个模块时才会去加载对应的逻辑代码,要实现这个肯定少不了在路由中进行异步加载的配置,然后配合webpack进行识别自动代码分割,这也算是Lazy Loading懒加载吧
那Lazy Loading懒加载是webpack里的一个概念吗?
其实并不是的,Lazy Loading懒加载是ES语法中提出的概念,而webpack只是能识别这种Import的语法进行相应的代码分割而已
# 结语
其实在webpack中关于Lazy Loading懒加载其实很常见也很实用,后面会不断实践,你会发现懒加载无处不在~
# 多进程打包
- 需要先下载
thread-loader
npm i thread-loader -D
- 一般在
babel-loader
下配置 - 代码
{
test: /\.js$/,
exclude: /node_modules/, // 排除
// loader: 'babel-loader',
use: [
/**
* 开启多进程打包
* 进程开启大概为600ms,进程通信也有开销
* 只有工作消耗时间比较长,才需要多进程打包
*
*/
'thread-loader',
{
loader: 'babel-loader',
options: {
// 预设:指示babel做怎样的兼容处理
presets: [
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
],
// 开启babel缓存
cacheDirectory: true
}
}
],
}
# externals
externals 排除一下包来进行打包
externals: {
// 忽略库名 --- 对应npm包名
jquery: 'jQuery'
}
# dll
使用dll技术,对某些库(第三方:jquery,vue,react)进行单独打包
- 创建
webpack.dll.js
/**
* 当你运行 webpack 时, 默认查找 webpack.config.js 配置文件
* 需要运行webpack.dll.js 文件的话 指令为 webpack --config webpack.dll.js
*/
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生产的name --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname,'dll'),
library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射关系
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname,'dll/manifest.json') // 输出文件路径
})
],
mode: 'production'
};
2. 在`webpack.config.js配置`
plugins: [
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname,'dll/manifest.json')
})
]
3. `cnpm i add-asset-html-webpack-plugin -D`
// 将某个文件打包输出出去,并在html中通过自动引入该文件
new AddAssetHtmlWebpackplugin({
filepath: resolve(__dirname,'dll/jquery.js')
})