一、优化打包构建速度 - 开发体验和效率
(1)优化 babel-loader
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'], // 开启缓存
include: path.resolve(__dirname, 'src'), // 明确范围
// 排除范围, include 和 exclude 两者选一个即可
// exclude: path.resolve(__dirname, 'node_modules')
}
(2)happyPack - 多进程打包工具
JS 单线程,开启多进程打包;提高构建速度(特别是多核 CPU)。HappyPack 可以将 Loader 的同步执⾏转换为并⾏的。
build-optimization/webpack.prod.js
const HappyPack = require('happypack')
module.exports = {
mode: 'production',
......
module: {
rules: [
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: path.resolve(__dirname, 'src'),
// exclude: /node_modules/
}
...
]
},
plugins: [
...
// happyPack 开启多进程打包
new HappyPack({
id: 'babel', // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
loaders: [babel-loader?cacheDirectory], // 如何处理 .js 文件,用法和 loader 配置中一样
})
],
...
}
(3)自动刷新
整个网页全部刷新,速度较慢,状态会丢失
配置 devServer,默认开启自动刷新!!!
(4)热更新
新代码生效,网页不刷新,状态不丢失
build-optimization/webpack.dev.js
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin')
module.exports = {
mode: 'development',
entry: {
index: [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
]
},
......
plugins: [
new webpack.DefinePlugin({
ENV: JSON.stringify('development')
}),
new HotModuleReplacementPlugin()
],
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
hot: true, // 热更新开启
proxy: { ... } // 设置代理
}
}
(5)DllPlugin - 比较大的库
DllPlugin 可以将特定的类库提前打包,然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,也实现了将公共代码抽离成单独文件的优化方案。
webpack-dll-demo/build/webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react,之所以在前面加上 _dll_ 是为了放在全局变量冲突
library: '_dll_[name]'
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一直,该字段的值也就是输出的 manifest.json 文件中 name 字段的值
// 例如 react.manifest.json 中就有 'name':'_dll_react'
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json')
})
]
}
在 package.json 文件中,配置以下信息,且运行 npm run dll 命令。
{
"scripts": {
"dll": "webpack --config build/webpack.dll.js"
}
}
使⽤ DllReferencePlugin 将依赖⽂件引⼊项⽬中
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
......
plugins: [
new DllReferencePlugin({
manifest: require(path.join(distPath, 'react.manifest.json')) // manifest 就是之前打包出来的 json ⽂件
})
]
}
二、优化产出代码 - 产品性能
体积更小;合理分包,不重复加载;速度更快,内存使用更少
(1)小图片 base64 编码
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 5 * 1024, // 小于 5KB 的图片用 base64 格式产出;否则依然延用 file-loader 的形式,产出 url 格式
outputPath: '/img1/', // 打包到 img 目录下
publicPath: 'http://cdn.abc.com' // 设置图片的 CDN 地址
}
}
}
]
},
...
}
(2)bundle 加 hash
如果文件内容没有改变,会命中缓存,浏览器加载会更快些。
module.exports = {
mode: 'production',
output: {
filename: 'bundle.[contentHash:8].js', -- 重点
path: distPath,
},
}
(3)懒加载
通过 import 的语法,先把重要的加载出来,把大的文件异步加载出来
(4)提取公共代码
有些公共代码,我们不需要在多个入口中,重复地打包进去,做一个公共的包,让他们相互引用就可以了。这样可以使我们整个打包出来的体积更小些。
module.exports = {
......
plugins: [
...
// 抽离 CSS 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
optimization: {
// 压缩 CSS
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
// 分割代码块
splitChunks: {
<!-- initial 入口 chunk,对于异步导入的文件不处理
async 异步 chunk,只对异步的文件处理
all 全部 chunk -->
chunks: 'all',
// 缓存分组
cacheGroups: {
// 第三方模块
vendor: {
name: 'vendor', // chunk 名称
priority: 1, // 权限更高,优先抽离,重要!
test: /node_modules/,
minSize: 0, // 大小限制
minChunks: 1 // 最少复用次数
}
// 公共的模块
common: {
name: 'common', // chunk 名称
priority: 0, // 优先级
minSize: 0, // 大小限制
minChunks: 2f // 最少复用次数
}
}
}
}
}
(5)使用 CDN 加速
module.exports = {
mode: 'production',
output: {
filename: 'bundle.[contentHash:8].js',
path: distPath,
publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 CDN 域名)
},
......
}
(6)使用 production - 开启自动压缩、开启 Tree-Shaking
1.自动开启压缩代码 - 代码体积更小些,浏览更快些
2.Tree-Shaking 可以实现删除项目中未被引用的代码,会自动删掉调试代码(如开发环境的 warning)。
注意:必须用 ES6 Module 才能让 tree-shaking 生效,commonjs 就不行。
(7)Scope Hosting -多个函数合成一个函数,减少作用域,执行更快一些
Scope Hosting 会分析出模块之间的依赖关系,尽可能的把打包处理的模块合并到一个函数中去。
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
module.exports = {
resolve: {
// 针对 npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
// 开启 Scope Hosting
new ModuleConcatenationPlugin()
]
}