webpack 性能优化
- 开发环境性能优化
- 生产环境性能优化
开发环境性能优化
优化打包构建速度
- HMR
优化代码调试
- source-map
生产环境性能优化
优化打包构建速度
- oneOf
- babel 缓存
- 多进程打包
- externals
- dll
优化代码运行的性能
- 缓存(hash,chunkhash,contenthash)
- tree shaking
- code split
- 懒加载和预加载
- pwa
优化 开发环境 打包构建速度
HMR hot module replacement 热模块替换/模块热替换
作用:一个模块发生变化,只会重新打包这一个模块 而不是重新打包所有,极大提升构建速度
- 样式文件: 可以使用HMR功能,因为style-loader内部实现了
- js文件: 默认不能使用HMR功能 -->解决:需要修改js代码,添加支持HMR功能的代码。注意,HMR功能对js的处理,只能处理非入口js文件的其他文件。
if(module.hot){
//一旦module.hot是true,说明开启HMR功能,让HMR功能代码生效
module.hot.accept('./xxx.js',function(){
//此方法会监听print.js文件的变化,一旦发生变化,其他默认不会重新打包构建
//会执行后面的回调函数
xxx();
})
}
- html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了
解决:改
entry:['入口js','html']
,但html文件只有一个,所以不用做HMR功能
devServer:{
//项目构建后的目录
contentBase: resolve(__dirname,'build'),
//启用gzip压缩
compress:true,
//端口号
port:3000,
//自动打开浏览器
open:true
}
优化 开发环境 代码调试
source-map 一种提供源代码到构建后代码映射的技术
如果构建后代码出错了,通过映射可以追踪到错误的代码
webpack.config.js
devtools:'source-map'
//其他 参数 [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
source-map : 外部
错误代码的准确信息和错误位置
inline-source-map : 内联
错误代码的准确信息和错误位置
hidden-source-map : 外部
错误代码的错误原因 但没有错误位置,不能追踪到源代码的错误,只能提示到构建后代码的位置
eval-source-map : 内联
每一个文件都生成对应的source-map,都在eval
错误代码的准确信息和错误位置
nosources-source-map : 外部
能找到错误代码的准确信息 但没有任何源代码信息
cheap-source-map : 外部
错误代码的准确信息和错误位置 只精确到行,不精确到列
cheap-module-source-map : 外部
错误代码的准确信息和错误位置
module 会将 loader 的 source-map加入
内联 和 外部的区别 :
1.外部生成了文件但内联没有生成
2.内联构建速度更快
开发环境:速度快,调试更友好
速度快 eval>inline>cheap>... 调试更友好 souce-map>cheap-module-souce-map>cheap-souce-map 所以 一般用eval-source-map
生产环境:源代码要不要隐藏,调试要不要友好?内联会让体积编码,所以一般不用内联
nosources-source-map 隐藏源代码 hidden-source-map 只隐藏源代码,会提示构建购代码错误信息 --> source-map /cheap-module-souce-map
优化生产环境
oneOf
rules里中有许多个loader,这样会导致每个文件都会被所有的loader过一遍,有些能处理,有些处理不了。所以可以利用oneOf达到以下loader只会匹配到第一个。但需要注意,不能有两个loader同时处理同一个文件
webpack.config
module.exports={
//....
module:{
rule:[
//正常来讲,一个文件只能被一个loader处理
//当一个文件要被多个loader处理,那么一定要指定loader的执行顺序
// 先执行eslint 再执行babel
{
test:/\.js$/,
exclude:/node_modules/,
//优先执行
enforce:'pre',
loader:'eslint-loader',
options:{
fix:true
}
},
{
oneOf:[
{
test: /\.css$/,
use:[
...commonCssLoader
]
},
{
test:/\.less$/,
use:[
...commonCssLoader,'less-loader'
]
},
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
// 预设 :指示babel做怎样的兼容性处理
presets:[
[
'@babel/preset-env',
{
//按需加载
useBuiltIns:'usage',
//指定core-js版本
corejs:{
version:3
},
//指定兼容性做到哪个版本的浏览器
targerts:{
chrome: '40',
fixfox: '50',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
},
{
test:/\.(png|jpg|gif)/,
loader:'url-loader',
enModule:true,
options:{
limit:8*1024,
name: '[hash:10].[ext]',
outputpath:''
}
},
{
test:/\.html$/,
loader:'html-loader'
},
{
exclude:/\.(js|less|css|png|jpg|gif)/,
loader:'file-loader',
options:{
name:'[hash:10].[ext]'
}
}
]
}
]
},
}
缓存
1.babel缓存-->第二次打包更快
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
// 预设 :指示babel做怎样的兼容性处理
presets:[
[
'@babel/preset-env',
{
//按需加载
useBuiltIns:'usage',
//指定core-js版本
corejs:{
version:3
},
//指定兼容性做到哪个版本的浏览器
targerts:{
chrome: '40',
fixfox: '50',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
2.文件资源缓存-->上线缓存优化
hash:每次webpack构建会生成一个唯一hash值
问题: 因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效,可能我却只改了一个文件,
chunkhash:根据chunk生成hash值,如果打包来源于同一个chunk,那么hash值也一样
问题:js和css的hash值还是一样的。
原因:css是由js引入的,所以属于同一个chunk
contenthash: 根据文件内容生成hash值,
webpack.config.js
tree shaking
去除应用程序中没有使用的代码
前提:
1.必须使用es6模块化
2.开启production模式
在package.json中配置
"sideEffects":false 所有代码都没有副作用,都可以镜像tree sharking
问题 可能会把css/@babel/polyfille 干掉
"sideEffects": ["*.css","*.less"] 哪些文件不 tree sharking
code split
1.入口配置
单入口 //单页面应用
entry:'./src/js/index.js'
多入口 //多页面应用
entry:{
index:'./src/js/index.js',
test:'./src/js/test.js'
}
2.optimization
module.exports={
//...
// 可以将nodemudules中的代码单独打包成一个chunk最终输出
// 还可以自动分析多入口chunk中,有没有公共的文件,如果有会打包成一个单独的chunk
optimization:{
splitChunks:{
chunks:'all'
}
}
}
3.import 动态导入语法,能将某个文件单独打包
通过js代码,让某个文件被单独打包成一个chunk,通过注释可以固定此文件的名称
import(/*webpackChunkName: 'xxxName' */'./xx/xxx.js')
.then(res =>{
//加载成功
})
.catch(()=>{
//加载失败
})
懒加载和预加载
1.懒加载 当文件需要用时才加载
import 动态导入语法
document.getElementById('btn').onclick = function(){
import(/*webpackChunkName: 'xxxName' */'./xx/ss.js')
.then(res=>{
//干啥干啥
})
}
2.预加载 webpackPrefetch:true
./xx/ss.js 已经被加载了,点击的时候再从缓存中加载,
document.getElementById('btn').onclick = function(){
import(/*webpackChunkName: 'xxxName',webpackPrefetch:true */'./xx/ss.js')
.then(res=>{
//干啥干啥
})
}
- 正常加载可以认为是并行加载(同一时间加载多个文件)
- 预加载prefectch 等其他资源加载完毕,浏览器空闲了,再偷偷加载资源 兼容性比较差 慎用
PWA 渐进式网络开发应用程序
网络离线可访问
webbox-->webbox-webpack-plugin
const WebboxWebpackPlugin = require('webbox-webpack-plugin')
module.exports={
plugins:[
new WebboxWebpackPlugin.GenerateSW({
/*
1.帮助 serviceWorker 快速启动
2.删除旧的 serviceWorker
生成一个 serviceWorker 配置文件
*/
clientsClaim:true,
skipWaiting:true
})
]
}
index.js 中注册serviceworker
//处理兼容性
if('serviceWorker' in navigator){
window.addEventListener('load',()=>{
navigator.serviceWorker
.register('./service-work.js')
.then(()=>{
//成功
})
.catch(()=>{
//失败
})
})
}
1.可能会出现问题 eslint不认识window和navigator
解决 package.json中eslintConfig中配置
"env":{
"browser":true //支持浏览器端的变量
}
2. sw代码必须运行在服务器上
-->node.js
--> npm i serve -g
serve -s build 启动一个服务器将build下的资源作为静态资源暴露出去
多进程打包
thread-loader 一般给babel-loader用
但需要注意
进程启动大约需500ms,进程间通信也有开销。只有工作消耗时间比较长,才需要多进程打包
{
test:/\.js$/,
exclude:/node_modules/,
use:[
//'thread-loader',
{
loader:'thread-loader',
options:{
workers: 2 //进程2个
}
}
{
loader:'babel-loader',
options:{
// 预设 :指示babel做怎样的兼容性处理
presets:[
[
'@babel/preset-env',
{
//按需加载
useBuiltIns:'usage',
//指定core-js版本
corejs:{
version:3
},
//指定兼容性做到哪个版本的浏览器
targerts:{
chrome: '40',
fixfox: '50',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//第二次构建时,会读取之前的缓存
cacheDirectory: true
}
}
]
},
externals
module.exports={
externals:{
//忽略/拒绝 库名 -- npm 包名
//可以在index.html中引入cdn
}
}
dll 动态连接
使用dll技术 对某些库(第三方库) 进行单独打包
指令 webpack --config webpack.dll.js
webpack.dll.js
const {resolve} = require('path')
const webpack = require('webpack')
module.exports = {
entry:{
//最终打包生成的[name]-->jquery
//['jquery']-->要打包的库是jquery
jquery:['jquery']
},
ouput:{
filename:'[name].js',
path:resolve(__dirname,'dll'),
library:'[name]_[hash]' //打包的库里面向外暴露出去的内容叫什么名字
},
plugin:[
new webpacl.DllPlugin({
//打包生成一个manifest.json --> 提供和jquery映射
name: '[name]_[hash]',//映射库的暴露内容名称
path:resolve(__dirname,'dll/manifest.json')
})
],
mode:'production'
}
webpack.config.js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports={
plugins:[
//告诉webpack哪些库不参与打包,同时名称也得变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname,'dll/manifest.json')
}),
//将某个文件打包输出出去,并在html中引入该资源
new AddAssetHtmlWebpackPlugin({
filepath:resolve(__dirname,'dll/jquery.js')
})
]
}