1. oneOf
对于某种类型的文件,webpack 会从上至下匹配所有的 loader,也就是,所有的 loader 都会去处理这种文件。但是,webpack 这种匹配方式在有些场景反而影响了效率。比如说,一个 webpack 只有 babel-loader 处理了 js 文件,那么当它匹配完了以后,完全没有必要再匹配一些针对 css 文件的 loader。所以,这就是为什么,我们使用 oneOf,匹配到了,立刻跳出循环。因此 oneOf 可以优化构建速度
module: {
rules: [
{
oneOf: [
{
test: /\.(png|jpg|jpeg)$/,
use: ['file-loader'],
exclude: /(node_modules|bower_components|dist)/
}
]
}
]
}
2. babel 缓存
babel 缓存的意思是,浏览器第一次拿资源文件的时候,会从服务器上走 http 拉取,但是当刷新页面再次请求的时候,浏览器会直接从缓存(可以是内存,也可以是硬盘,自己配置)当中拿同名文件,因此省去了发送 http 请求拿资源的时间。
3. 多进程打包
业内有两种解决方案比较常用,一个是 thread-loader,一个是 happy-pack,但是 happy-pack 的维护者现在对这个库不再维护了,因此,推荐使用 thread-loader(多进程打包,不是线程)。
{
rules:
[
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components|dist)/,
use: [
/**
* 开启多进程打包,打开进程一般 600 ms,
* 通信也有开销。
*/
{
loader: "thread-loader",
options: {
workers: 3
}
},
{
loader: "babel-loader",
options: {
cacheDirectory: true
}
}
]
}
]
}
需要在 options 字段下配置 workers 也就是进程的个数。注意 thread-loader 并不是开的进程数越多就越好,假如你的 js 代码量很少,开多核反而会降低性能,这是因为打开进程有比较大的开销(600 ms 左右),进程间通信也有开销。
4. tree shaking
tree shaking 的意思是你的项目里面有些代码可能是从来没被引入的,比方说你定义了一个函数但是你从来没有引用到它。这个时候 tree shaking 可以在打包的时候帮你干掉这些代码。
前提
使用 ES6 module
production 默认开启
需要配合 package.json 里面 sideEffects: ["*.css"] 一同使用,否则可能会干掉打包好的 css 文件。
5. code split
code split 直接从字面上理解即可,就是代码分割技术,因为 html 里面的静态资源文件是并行加载的,即发送 http 请求并且把文件放到内存里这个过程是并行的。所以适当的代码分割技术可以让我们的项目运行性能更好。另外,代码分割也可以帮助我们在多路由的场景进行性能优化。
多入口
entry: {
main: './src/js/index.js', // 入口1
test: './src/js/test.js' // 入口2
},
output: {
// [name]是webpack命名规则,使用chunk的name作为输出的文件名。
// 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
filename: 'js/[name].[contenthash:10].js'
path: resolve(__dirname, 'build')
}
// optimization 代码分割配置
optimization: {
splitChunks:{
chunks: 'all' // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
// 修改配置
// cacheGroups: {
// 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
}
}
上面这块配置可以把你做两件事:
node_modules 中代码单独打包成一个 chunk
自动分析多入口 chunk 中,有没有公共文件,如果有会打包成一个单独 chunk
6. 懒加载和预加载
懒加载
使用 import 函数,同上。回调函数中使用 import 函数。
document.getElementById('btn').onclick = () => {
import('./test').then(({mul})=> {
console.log(mul(4,5))
})
)
预加载
// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "math":这是webpack动态导入模块命名的方式
// "math"将来就会作为[name]的值显示。
// webpackPrefetch 开启预加载
document.getElementById('btn').onclick = () => {
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({mul})=> {
console.log(mul(4,5))
})
)
7. pwa
适用情景,我们希望在用户离线时也可以访问我们的页面。对于 webpack 而言,我需要使用的是 workbox-webpack-plugin。配置成功以后,浏览器在离线后可以从 service-worker 里拉取静态资源。
// webpack
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 删除旧的 serviceWorker 且快速启动
clientsClaim: true,
skipWaiting: true
})
]
// js
if('serviceWorker' in navigator){
window.addEventListenr('load', ()=>{
navigator.serviceWorker.register.register('./service-worker.js')
.then(()=>{
console.log('sw 注册成功了')
})
.catch(()=>{
console.log('sw 注册失败')
})
})
}
8. CDN
CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写:CDN), 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器; 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户; 来提供高性能、可扩展性及低成本的网络内容传递给用户;
在开发中,我们使用CDN主要是两种方式:
方式一:打包的所有静态资源,放到CDN服务器, 用户所有资源都是通过CDN服务器加载的;
方式二:一些第三方资源放到CDN服务器上
9. JS代码压缩
Terser
optimization: {
minimize: true, // 是否要启用压缩,默认情况下,生产环境会自动开启
minimizer: [
// 压缩时使用的插件,可以有多个
new TerserPlugin(), // js压缩插件
new OptimizeCSSAssetsPlugin() // css压缩插件
],
}
10. gizp
浏览器发送请求时,会在请求头中设置Accept-Encoding:gzip,deflate,br。表明浏览器支持gzip。服务器收到浏览器发送的请求之后,判断浏览器是否支持gizp,如果支持gzip,则向浏览器传送压缩过的内容,不支持则向浏览器发送未经压缩的内容。一般情况下,浏览器和服务器都支持gzip,响应头返回包含content-encoding:gzip。浏览器接收到服务器的响应之后判断内容是否被压缩,如果被压缩则解压缩显示页面内容
使用compression-webpack-plugin插件对打包结果进行预压缩,可以移除服务器的压缩时间。
const CmpressionWebpackPlugin = require("compression-webpack-plugin")
module.exports = {
plugins: [
new CmpressionWebpackPlugin({
// filename: "[file].gzip"
test: /\.js/, //针对需要预压缩的文件
minRatio: 0.5 //压缩比率
})
]
};