①. 主要是webpack做相关处理,主要从时间层面和体积层面优化.
②. ⏱ - 时间层面:
a. 目的是减少打包时间
b. 方案:
(1). 缩减范围、缓存副本、定向搜索、提前构建、并行构建、可视结构
③. - 体积层面:
a. 目的是减少打包体积
b. 方案:
(1). 分割代码、摇树优化、动态垫片、按需加载、作用提升、压缩资源
(1). 缩减范围:
①. 场景:
a. 配置include、exclude缩小Loader对文件的搜索范围.
②. 目的:
a. 避免不必要的转译.
b. node_modules目录的体积很大,减少检索所有文件时间成本.
export default {
// include/exclude通常在各大Loader里配置
// src目录通常作为源码目录
module: {
rules: [{
exclude: /node_modules/,
include: /src/,
test: /\.js$/,
use: "babel-loader"
}]
}
}
(2). 缓存副本:
①. 场景:
a. 配置cache缓存Loader对文件的编译副本
②. 目的:
a. 再次编译时只编译修改过的文件
b. 大部分Loader/Plugin都会提供一个可使用编译缓存的选项,通常包含cache字眼
以babel-loader和eslint-webpack-plugin为例:
import EslintPlugin from "eslint-webpack-plugin";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
},
plugins: [
new EslintPlugin({ cache: true })
]
};
(3). 定向搜索:
①. 场景:
a. 配置resolve提高文件的搜索速度.
②. 目的:
a. 定向指定必须文件路径.
b. 若某些第三方库以常规形式引入可能报错,希望程序自动索引特定类型文件都可通过该方式解决.
c. 配置项:
(1). alias映射模块路径
(2). extensions表明文件后缀
(3). noParse过滤无依赖文件
export default {
// ...
resolve: {
alias: {
"#": AbsPath(""), // 根目录快捷方式
"@": AbsPath("src"), // src目录快捷方式
swiper: "swiper/js/swiper.min.js"
}, // 模块导入快捷方式
extensions: [".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] // import路径时文件可省略后缀名
}
};
(4). 提前构建:
①. 场景:
a. 将第三方依赖提前打包,配置DllPlugin
②. 目的:
a. 将DLL与业务代码完全分离,且每次只构建业务代码
b. DLL(动态链接库),指一个包含可由多个程序同时使用的代码库:
(1). 把公共代码打包为DLL文件并存到硬盘,再次打包时,动态链接DLL文件就无需再次打包那些公共代码
c. webpack v4+已不推荐,因为其版本迭代带来的性能提升足以忽略DllPlugin所带来的效益
③. 配置:
a. 告知构建脚本哪些依赖做成DLL
b. 生成DLL文件和DLL映射表文件
import { DefinePlugin, DllPlugin } from "webpack";
export default {
// ...
entry: {
vendor: ["react", "react-dom", "react-router-dom"]
},
mode: "production",
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "all",
name: "vendor",
test: /node_modules/
}
}
}
},
output: {
filename: "[name].dll.js", // 输出路径和文件名称
library: "[name]", // 全局变量名称:其他模块会从此变量上获取里面模块
path: AbsPath("dist/static") // 输出目录路径
},
plugins: [
new DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development") // DLL模式下覆盖生产环境成开发环境(启动第三方依赖调试模式)
}),
new DllPlugin({
name: "[name]", // 全局变量名称:减小搜索范围,与output.library结合使用
path: AbsPath("dist/static/[name]-manifest.json") // 输出目录路径
})
]
};
在package.json中配置执行脚本且每次构建前首先执行该脚本打包出DLL文件:
{
"scripts": {
"dll": "webpack --config webpack.dll.js"
}
}
链接DLL文件并告知webpack可命中的DLL文件让其自行读取,使用html-webpack-tags-plugin在打包时自动插入DLL文件:
import { DllReferencePlugin } from "webpack";
import HtmlTagsPlugin from "html-webpack-tags-plugin";
export default {
// ...
plugins: [
// ...
new DllReferencePlugin({
manifest: AbsPath("dist/static/vendor-manifest.json") // manifest文件路径
}),
new HtmlTagsPlugin({
append: false, // 在生成资源后插入
publicPath: "/", // 使用公共路径
tags: ["static/vendor.dll.js"] // 资源路径
})
]
};
也可使用autodll-webpack-plugin代替手动配置.
(5). 并行构建:
①. 场景:
a. 配置Thread将Loader单进程转换为多进程
②. 目的:
a. 释放CPU多核并发的优势
b. webpack构建时会有大量文件需解析和处理,构建过程是计算密集型的操作,随着文件增多会使构建过程变得越慢
c. Node运行webpack是单线程模型,处理的任务需要一件件来处理,不能同一时刻处理多件任务
d. thread-loader可以根据CPU个数开启线程,能让webpack同一时刻处理多个任务
import Os from "os";
export default {
// ...
module: {
rules: [{
// ...
test: /\.js$/,
use: [{
loader: "thread-loader",
options: { workers: Os.cpus().length }
}, {
loader: "babel-loader",
options: { cacheDirectory: true }
}]
}]
}
};
(6). 可视结构:
①. 场景:
a. 配置BundleAnalyzer分析打包文件结构
②. 目的:
a. 找出导致体积过大的原因,分析原因得出优化方案减少构建时间
b. webpack官方插件,可直观分析打包文件的模块组成部分、模块体积占比、模块包含关系、模块依赖关系、文件是否重复、压缩体积对比等可视化数据
c. Node运行webpack是单线程模型,处理的任务需要一件件来处理,不能同一时刻处理多件任务
d. thread-loader可以根据CPU个数开启线程,能让webpack同一时刻处理多个任务
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
export default {
// ...
plugins: [
// ...
BundleAnalyzerPlugin()
]
}
(1). 分割代码:
①. 场景:
a. 分割各个模块代码,提取相同部分代码
②. 目的:
a. 减少重复代码的出现频率
b. webpack v4使用splitChunks替代CommonsChunksPlugin实现代码分割
export default {
// ...
optimization: {
runtimeChunk: { name: "manifest" }, // 抽离WebpackRuntime函数
splitChunks: {
cacheGroups: {
common: {
minChunks: 2,
name: "common",
priority: 5,
reuseExistingChunk: true, // 重用已存在代码块
test: AbsPath("src")
},
vendor: {
chunks: "initial", // 代码分割类型
name: "vendor", // 代码块名称
priority: 10, // 优先级
test: /node_modules/ // 校验文件正则表达式
}
}, // 缓存组
chunks: "all" // 代码分割类型:all全部模块,async异步模块,initial入口模块
} // 代码块分割
}
};
(2). 摇树优化:
①. 场景:
a. 删除项目中未被引用代码
②. 目的:
a. 好处是移除重复代码和未使用代码
b. 摇树优化首次出现于rollup(核心概念),后来在webpack v2里借鉴过来使用
c. 摇树优化只对ESM规范生效,对其它模块规范失效.
d. 摇树优化针对静态结构分析,只有import/export才能提供静态的导入/导出功能.
e. 因此在编写业务代码时,必须使用ESM规范才能让摇树优化移除重复代码和未使用代码.
在webpack里只需将打包环境设置成生产环境就能让摇树优化生效,同时业务代码使用ESM规范编写,使用import导入模块,使用export导出模块.
export default {
// ...
mode: "production"
};
(3). 动态垫片:
①. 场景:
a. 通过垫片服务根据UA返回当前浏览器代码垫片
②. 目的:
a. 无需将繁重的代码垫片打包进去
b. 每次构建都配置@babel/preset-env和core-js,根据某些需求将Polyfill打包进来
c. @babel/preset-env提供的useBuiltIns可按需导入Polyfill