已同步语雀:www.yuque.com/go/doc/5319…
github:www.yuque.com/go/doc/5319…
本系列为Webpack——入门篇,包括:
1、Webpack——【入门篇-上篇】juejin.cn/post/695439…
2、Webpack——【入门篇-中篇】juejin.cn/post/695438…
3、Webpack——【入门篇-下篇】【本篇】juejin.cn/post/695438…
17 性能优化配置
分为两种
-
开发环境性能优化
-
优化打包构建速度
-
HMR
-
优化调试功能
-
s****ource-map(devtool)
-
生产环境性能优化
-
优化打包构建速度
-
one-of
-
babel缓存
-
多进程打包
-
externals
-
dll
-
优化代码运行性能(体积小)
-
缓存(hash – chunkhash – contenthash)
-
tree-shaking
-
code-split
-
懒加载/预加载
-
PWA
具体:
HMR:hot
解释:hot module replacement 热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包这一个模块,而不是重新打包所有模块,极大的提升构建速度
-
样式文件:可以使用HMR功能,因为style-loader内部实现了
-
js文件:默认没有HMR功能,重新打包所有模块
-
注意:HMR功能对js处理,只能处理非入口文件,因为入口文件引入了全部内容,入口文件变化,其他文件变化
-
html文件:默认没有HMR功能,同时会导致问题:html文件不能热更新(不需要做HMR功能)
-
解决:修改entry入口,将html文件引入
使用:
webpack.config.js
module.exports = {
...
target: 'web', //自动更新
devServer: {
hot: true //热模块替换
}
}
复制代码
main.js
// import './print.js'
if(module.hot){
// 一旦module.hot为true,说明开启了hotHMR功能 --> 让HMR功能代码生效
module.hot.accept('./print.js', function(){
// 方法会监听print.js文件,一旦发生变化,其他模块不会重新打包构建
// 会执行后面的回调函数
print()
})
}
复制代码
source-map(devtool)
解释:一种提供源代码到构建后代码映射技术
作用:如果构建代码错了,通过映射关系可以追踪到原代码错误
使用:开发环境用devtool: ‘eval-source-map’;生产环境用devtool: ‘source-map’,
webpack.config.js
module.exports = {
...
devtool: 'source-map',
}
复制代码
执行webpack,可以看到打包的dist文件夹下面有main.js.map文件,如下图
具体配置:
[inline-|hidden-|eval-][nosources][cheap-[module-]]source-map
配置字段
解释
错误定位
source-map
外部,生成单独文件
错误代码准确信息,和源代码的错误位置,精确到行、列
inline-source-map
内联,不生成单独文件,整体在main.js文件里面下方扩展,只生成一个source-map,构建速度快
同source-map
hidden-source-map
外部,生成单独文件,main.js.map
错误代码准确信息,和构建后代码的错误位置,不能追踪到源代码错误;只隐藏源代码
eval-source-map
内联,不生成单独文件,在main.js文件每一个文件后面追加sourceMappgingURL字段,每个文件都生成一个source-map,都在eval中,构建速度快
同source-map
nosources-source-map
外部,生成单独文件
错误代码准确信息,不能追踪到源代码和构建后代码错误;为了隐藏源代码,全部隐藏
cheap-source-map
外部,生成单独文件
同source-map,但是仅能精确到行,整行,不能精确到列
cheap-module-source-map
外部,生成单独文件
同cheap-source-map,但module会将loader的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
-
生产环境
-
隐藏源代码
-
nosoures-source-map,hidden-source-map
-
体积小
-
内联会让体积变得很大,所以生产环境不能用内联
-
调试友好
-
同上
-
综上:source-map
one-of
解释:只匹配一个loader
作用:一个文件只会匹配一个loader,当匹配到了就不走下面的;提升构建速度
注意:不能有两个配置处理同一类型文件,比如js文件,eslint-loader,babel-loader;需要提取一个到oneof外面
webpack.config.js
{
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
enforce: 'pre', // 指定先后顺序,先执行
options: {
fix: true,
},
},
// 注意:不能有两个配置处理同一类型文件,比如js文件,eslint-loader,babel-loader;需要提取一个到oneOf外面
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-lodaer'],
},
// 正常来讲,一个文件只能被一个loader处理,那么一定要指定loader执行的先后顺序
// 先执行eslint,在执行babel
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBulitIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
],
],
},
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false, // html中用的是common, url-loader中用的是es6,需要关掉
},
},
{
test: /\.html/,
loader: 'html-loader', // 处理html中的img
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-lodaer',
options: {
outputPath: 'media',
},
},
],
]
}
复制代码
缓存 hash
- babel缓存:cacheDirectory –> 让第二次打包构建速度更快
生产环境下,文件变化时读取缓存
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBulitIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
],
],
cacheDirectory: true, //开启babel缓存,第二次构建时,读取缓存,加快速度
},
},
复制代码
-
**文件资源缓存:**hash –> 让代码上线运行缓存更好使用
-
hash:每次webpack构建时会生成一个唯一的hash值
-
问题:因为js和css同时使用一个hash值,如果重新打包就会导致所有的缓存失效(仅改变一个文件)
-
chunkhash:根据chunk生成hash值,如果打包来源于同一个chunk,那么hash值一样
-
问题:css和js的hash值还是一样
-
因为css是在js中引入的,所以同属一个chunk
-
contenthash:根据文件的内容生成hash值,不同文件hash值一定不一样,只有改变的文件会重新生成hash值,没变的不会
output: {
filename: ‘main.[contenthash:10].js’, //文件资源缓存处理 ‘main.[chunkhash:10].js’ || ‘main.[hash:10].js’
path: resolve(__dirname, ‘dist’),
publicPath: ‘/’,
},…
new MiniCssExtractPlugin({
filename: ‘css/index.[contenthash:10].css’, // 文件资源缓存处理 ‘css/index.[hash:10].css’ || ‘css/index.[hash]:10].css’
}),
tree-shaking
作用:去除无用代码,减少代码体积
前提:必须使用es6模块,mode = production环境默认开启tree-shaking,webpack默认的UglifyJsPlugin实现
最好在package.json配置”sideEffects”: [“*.css”,”*.less”],防止文件被摇掉
"sideEffects": false,
// 所有代码都没有副作用(都可以进行tree shaking)
// 问题:可能会把css文件摇掉
"sideEffects": ["*.css","*.less"], //.css,*.less不进行tree shaking
复制代码
code-split 代码分割
**作用:**将打包生成的一个chunk分割成多个代码块
- 并行加载,加快速度
- 按需加载,(spa,单文件需要按照路由分割)
方法:
- 多入口分割
问题:很难配置多入口,不灵活
//webpack.config.js
{ // 单入口
// entry: './src/main.js',
// 多入口,有一个入口,最终输出就有一个bundle
entry: {
main: "./src/main.js",
print: "./src/print.js"
},
output: {
filename: '[name].[contenthash:10].js',
}
}
复制代码
-
optimization配置
//webpack.config.js
{
optimization: {
/*
作用
1. 可以将node_modules中代码单独打包成一个chunk最终输出
2. 多入口还可以:自动分析多入口文件中有没有公共的依赖文件,如果有,回单独打包成一个chunk,而不是每个文件都去打包一次
*/
splitChunks: {
chunks: ‘all’
}
}
} -
import动态导入
//main.js
// import {mul, count} from ‘./print’// console.log(mul(5,2))
// console.log(count(5,2))/*
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
常用作路由
/
import(/ webpackChunkName: ‘print’ */’./print’)
.then(( {mul, count}) => {
console.log(mul(5,2))
console.log(count(5,2))
})
.catch( () => {
console.log(‘文件加载失败’)
})
执行webpack,可以看到结果如下图,print.js分割成单独文件
懒加载和预加载
利用代码分割-动态加载实现
//index.html
<button id="btn">按钮</button>
//main.js
document.getElementById('btn').onclick = function(){
/*
懒加载:当文件需要使用时才加载
问题:但当文件较大时,不合适,加载时间太长,导致用户等待,这时候考虑预加载
预加载:配置webpackPrefetch: true;prefetch:会在使用之前,提前加载js文件
问题:兼容性不好
正常加载 && 预加载
正常加载:并行加载,同一时间加载多个文件,浏览器一个域名下统一时间只能并行加载最多6个文件,超过需往后排队
预加载:等其他资源加载完毕后,浏览器空闲后再加载
*/
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test')
.then(() => {
console.log('记载test.js成功')
})
.catch(() => {
console.log('文件加载失败')
})
}
复制代码
PWA
解释:渐进式网络开发应用程序(离线也可以访问)
使用:workbox –> workbox-webpack-plugin
// webpack.config.js
const workboxWebpackPlugin = require('workbox-webpack-plugin')
{
...
plugins: [
// PWA
// npm i workbox-webpack-plugin -D
new workboxWebpackPlugin.GenerateSW({
/*
1. 帮助serviceworker快速启动
2. 删除旧的serviceworker
生成一个 serviceworker 配置文件,需要通过配置文件注册serviceworker
*/
clientsClaim: true,
skipWaiting: true
})
]
}
// main.js
// PWA 注册serviceworker
// 注意 sw代码必须运行在服务器上 npm i serve -g;serve -s dist 启动服务器,将dist目录暴露出去
// 处理兼容性问题
if('serviceWorker' in navigator){
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then( () => {
console.log('sw注册成功')
})
.catch( () => {
console.log('sw注册失败')
})
})
}
复制代码
执行webpack,可以看到
多进程打包
解释:js是单线程,排队构建速度慢
使用:npm i thread-loader -D
双刃剑:
-
缺点:进程启动大概为600ms,进程间通信也有开销,只有工作消耗时间比较长,才需要多进程打包,比如js babel-loader
// webpack.config.js
{
…
{
test: /.js$/,
exclude: /node_modules/,
use: {
‘thread-loader’,
‘babel-loader’
}
}
}
extranals
解释:外部的,拒绝某些包被打包进来,在index.html cdn引入
// webpack.config.js
{
...
externals: {
// 拒绝jquery被打包进来;需要在index.html cdn引入
jquery: 'jQuery' // 包名
}
}
// main.js
// externals
import $ from 'jquery'
console.log($)
// index.html
<script crossorigin="anonymous" integrity="sha512-6ORWJX/LrnSjBzwefdNUyLCMTIsGoNP6NftMy2UAm1JBm6PRZCO1d7OHBStWpVFZLO+RerTvqX/Z9mBFfCJZ4A==" src="https://lib.baomitu.com/jquery/3.6.0/jquery.slim.min.js"></script>
复制代码
dll
解释:动态连接库
新建webpack.dll.js,配置如下
// webpack.dll.js
/*
使用dll技术,对某些库(第三方库:jQuery、react、vue...)进行单独打包
webpack命令默认执行webpack.config.js文件
运行:webpack --config webpack.dll.js
*/
const path = require("path")
const webpack = require("webpack")
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jQuery']
},
output: {
filename: '[name].js',
path: path.join(__dirname, 'dll'),
library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 打包生成一个manifest.json --> 文件提供和jquery映射
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: path.join(__dirname, 'dll/manifest.json'), //输出文件路径
})
],
mode: "production"
}
复制代码
执行webpack –config webpack.dll.js,可以看到打包后的结果
webpack.config.js配置
// webpack.config.js
// npm i add-asset-html-webpack-plugin -D
{
const webpack = require("webpack")
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
...
pluigns: [
// dll
// 告诉webpack哪些库不参与打包。同时使用时的名称也得变
new webpack.DllReferencePlugin({
manifest: path.join(__dirname, 'dll/manifest.json')
}),
// npm i add-asset-html-webpack-plugin -D
// 将某个文件打包输出出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: path.join(__dirname, 'dll/jquery.js')
})
]
}
复制代码
执行webpack,可以看到单独生成了一个jquery,并且在index.html中引入了
externals && dll
externals:彻底不打包,需要手动引入cdn
dll:只需要打包一次,后面就只需执行webpack即可;第二次之后就不需要考虑第三方库了,提高速度
18 回顾:性能优化总结
分为两种
-
开发环境性能优化
-
优化打包构建速度
-
HMR
-
优化调试功能
-
source-map
-
生产环境性能优化
-
优化打包构建速度
-
one-of
-
babel缓存
-
多进程打包
-
externals
-
dll
-
优化代码运行性能(体积小)
-
缓存(hash – chunkhash – contenthash)
-
tree-shaking
-
code-split
-
懒加载/预加载
-
PWA
19 entry详细配置
entry:入口起点
-
string –> ‘./src/index.js’ 默认值
-
单入口
-
打包成一个chunk,输出一个bundle文件
-
此时chunk的名称默认是main
-
array –> [‘./src/index.js’, ‘./src/add.js’]
-
多入口
-
所有入口文件最终只会形成一个chunk,输出出去只有 一个bundle文件,add.js会打包到index.js中
-
此时chunk的名称默认是main
-
object –> 如下
-
多入口
-
有几个入口文件就形成几个chunk,输出几个bundle文件
-
此时chunk的名称是key
// string
entry: ‘./src/index.js’// array
entry: [‘./src/index.js’, ‘./src/add.js’]// object
entry: {
index: ‘./src/index.js’,
add: ‘./src/add.js
}// 特殊用法 如dll配置
entry: {
index: [‘./src/index.js’, ‘./src/count.js’],
add: ‘./src/add.js
}
20 output详细配置
output: {
// 文件名称(指定名称+目录)
filename: 'js/[name].js',
// 输出文件目录(将来所有资源输出的公共目录)
path: resolve(__dirname, 'build'),
// 所有资源引入公共路径前缀 --> imgs/a,jpg --> /imgs/a.jpg
publichPath: '/',
// 非入口chunk名称重命名,不定义的话会走filename,都叫main,webpack会用id命名 0.js 1.js 2.js ...
chunkFilename: 'js/[name]_chunk.js',
// 打包的js默认是一个function
// 整个库向外暴露的变量名
library: '[name].js', // var main = (function(modules){})
// 变脸名添加哪个上
libraryTarget: 'window' // browser window['main'] = (function(modules){})
// libraryTarget: 'global' // node
// libraryTarget: 'commonjs' // browser exports['main'] = (function(modules){})
}
复制代码
21 module详细配置
module: {
rules: {
// loader的配置
{
test: /\.css$/,
// 多loader用use
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
include: resolve(__dirname, 'src'),
enforce: 'pre', //优先执行
// enforce: 'post', //延后执行
// 单个loader用loader
loader: 'eslint-loader',
options: {
...
}
},
{
// 以下配置只会生效一个
oneOf: []
}
}
}
复制代码
22 resolve详细配置
// 解析模块规则
resolve: {
// 配置解析模块路径别名:简写路径
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配置省略文件路径的后缀名
extensions: ['.js', '.json', '.css'],
// 告诉webpack解析模块是去找哪个目录,默认去当前路径层级找,找不到找上一层级的
modules: [resolve(__dirname, '../../node_modules'), 'node_modules'] //先找第一个,再找第二个,防止第一个找不到
}
复制代码
23 devServer详细配置
// 开发环境
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视contentBase目录下的所有文件,一旦文件变化就会reload
watchContentBase: true,
watchOptions: {
// 忽略监视文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 50000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器日志信息
clientLogLevel: 'none',
// 除了一些基本的信息以外,其他内容都不要显示
quite: true,
// 如果出错了,不要全屏提示
overlay: false,
// 服务器代理 --> 解决开发环境跨域问题
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'^/api': ''
}
}
}
}
复制代码
24 optimization详细配置
const TerserWebpackPlugin = require('terser-webpack-plugin')
// 生产环境
optimization: {
splitChunks: {
chunks: "all",
// 以下是默认值,可以不写
miniSize: 30 * 1024, //分割的chunk最小为30kb,大于30kb才会被分割
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用一次
maxAsyncRequest: 5, // 按需加载时并行加载的文件最大数量
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 命名连接符
name: true, // 可以使用命名规则
cacheGroups: {
// 分割chunk的组
// node_modules文件会被打包到vendors组chunk中 => vendors~xxx.js
// 满足上面的公共规则,如:大小超过30kb,至少被引用一次
vendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
defalut: {
// 要提取的chunk至少被引用两次
minChunks: 2,
// 优先级
priority: -20,
// 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
reuseExistingChunk: true,
}
}
},
// 将当前模块的记录其他模块的hash单独打包为一个文件runtime
// 解决:修改a文件导致b文件的contenthash变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: {
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多线程打包
parallel: true,
// 启用source-map
sourceMap: true
}
}
}
复制代码
terser-webpack-plugin(webpack.docschina.org/plugins/ter…)
如果你使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin
。如果使用 webpack v4,则必须安装 terser-webpack-plugin
v4 的版本,不再用UglifyJsPlugin
已同步语雀:www.yuque.com/go/doc/5319…
github:www.yuque.com/go/doc/5319…
本系列为Webpack——入门篇,包括:
1、Webpack——【入门篇-上篇】juejin.cn/post/695439…
2、Webpack——【入门篇-中篇】juejin.cn/post/695438…
3、Webpack——【入门篇-下篇】【本篇】juejin.cn/post/695438…