-
webpack4.X 实战(四):企业SPA 24点总结(下)
本文纲要:项目打包配置、优化
13. 配置 打包输出路径
-
webpack 配置文件输出路径时,常用三种配置
-
配置一:出口
entry.path
,配置所有文件的输出路径// webpack 配置 const path = require('path'); const rootPath = path.resolve(__dirname, ''); module.exports = { output: { path: path.resolve(rootPath, './dist') } } // 打包后的文件,所有资源 统一输出到 项目根目录下的 dist 文件夹下
-
配置二:
loader
的options.outputPath
,配置 当前loader
匹配到文件 的输出路径这项配置的文件输出 以
entry.path
路径为标准// webpack 配置 module.exports = { output: { path: path.resolve(rootPath, './dist') }, module: { rules: [{ test: /\.(png|jpg|gif|ico)$/, use: [{ loader: 'url-loader', options: { limit: 8192, name: '[name].[ext]', outputPath: 'static/assets/' } }] }] } }; // 打包后的文件,图片资源 统一输出到 项目根目录下的 `dist/static/assets` 文件夹下
-
配置三:
name
选项的值,可改变当前包的输出路径出口
entry.chunkFilename
,改变所有chunk
包的输出位置(runtimeChunk
除外)// webpack 配置 module.exports = { output: { path: path.resolve(rootPath, './dist'), chunkFilename: 'static/js/[name].js' } }; // 打包后的 chunk 包,统一输出到 dist/static/js 文件夹下
optimization.splitChunks
的name
值,以output.chunkFilename
路径为标准// webpack 配置 module.exports = { output: { path: path.resolve(rootPath, './dist'), chunkFilename: 'static/js/[name].js' }, optimization: { splitChunks: { cacheGroups: { libs: { test: /[\\/]node_modules[\\/]/, priority: 20, name: '../libs/index', chunks: 'all' } } } } }; // 打包后的 splitChunks 包,统一输出到 dist/static/js 文件夹下
【特殊】
optimization.runtimeChunk
的name
值,以output.path
路径为标准// webpack 配置 module.exports = { output: { path: path.resolve(rootPath, './dist'), chunkFilename: 'static/js/[name].js' }, optimization: { runtimeChunk: { name: 'static/runtime/index' } } }; // 打包后的 runtime 包,统一输出到 dist/static/runtime 文件夹下
-
14. 配置 静态资源发布路径(方便CDN请求资源)
-
作用:配置 发布路径,让页面请求 CDN 上的静态资源,速度更快
-
配置 公共发布路径:
output.publicPath
// webpack 配置 module.exports = { output : { publicPath : 'http://aaa.com/' } };
-
单独配置静态资源:
loader options
module.exports = { module: { rules: [{ test: /\.(png|jpg|gif|ico)$/, use: [{ loader: 'url-loader', options: { limit: 8192, name: '[name].[hash:8].[ext]', outputPath: 'static/assets/', publicPath: 'http://aaa.com/assets/' } }] }] } };
15. 配置 打包文件缓存 hash
-
webpack 打包后的模块 默认命名规则:
-
webpack 默认为给各个模块分配一个 id,作为模块的名称
默认 id 是根据模块引入的顺序,赋予一个整数(0、1、2、3……)
默认 id 用来处理模块之间的依赖关系
-
通过配置 不同的
hash
,缓存文件
-
-
webpack 内置了多种可使用 hash,官网解释分别如下:
-
hash
: the hash of the module identifier -
chunkHash
: the hash of the chunk content -
contentHash
: the hash of extracted content
-
-
设置 哈希 的长度
// 全局设置 hash 长度 output.hashDigestLength
// 局部设置 hash 长度 [hash:16] 等方式
-
配置 何时生成
hash
// output.hashDigest
-
如何选择正确的
hash
?-
JS、分离后的CSS、资源文件(图片、字体图标)都使用
hash
-
每次打包 JS、分离后的CSS 文件 哈希值 都一样
-
项目代码没有变化,再次打包;所有文件哈希值不变
-
只要JS、CSS 有一处变化,所有 JS、分离后的CSS 文件 哈希值 都变化
-
资源文件(图片、字体图标)没更新,哈希值不变;使用
hash
,是个不错的选择
-
-
JS、分离后的CSS、资源文件(图片、字体图标)都使用
chunkHash
-
每次打包 同一个页面的 JS、分离后的CSS 哈希值一样;不同页面 JS、CSS文件 哈希值不一样
-
项目代码没有变化,再次打包;所有文件哈希值不变
-
项目代码有变化,只是有更改的JS、对应的CSS文件 哈希值变化;其他的文件 哈希值不变
-
资源文件(图片、字体图标)不能使用
chunkHash
,会报错
-
-
最佳实践:
-
JS 文件:
[name].[chunkHash:8].js
-
分离后的CSS 文件:
[name].[contentHash:8].css
不会 随着JS的改变,而更改
hash
-
资源文件(图片、字体图标):
[name].[hash:8].[ext]
-
-
16. 打包前 自动清除文件 clean-webpack-plugin
-
使用
clean-webpack-plugin
插件,在打包时先清空上一次打包的文件如果不清空,打包后的文件体积会越来越大
-
安装
npm i clean-webpack-plugin@1.0.0 -D
-
配置
// webpack 中配置 const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { plugins: [ new CleanWebpackPlugin(['dist']) // 值为根目录下 清空的文件夹 ] }
17. 根据查找规则 精简打包
-
webpack 提供内置插件:
ContextReplacementPlugin
// 如下:只打包 moment 的中文包 ... plugins: [ new webpack.ContextReplacementPlugin( /moment[/\\]locale$/, /zh-cn/, ), ] ...
18. 打包时 代码分割
-
新旧版本 API更新
-
webpack3 使用 内置插件
optimize.CommonsChunkPlugin
来分割代码 -
webpack4 移除内置插件,新增
optimization.splitChunks
、optimization.runtimeChunk
来分割代码Webpack 4 的 Code Splitting 最大的特点就是配置简单(0配置起步),和基于内置规则自动拆分
-
-
为什么要分割代码?
-
编译:减少编译的整体大小,以提高构建性能
-
运行:减小文件体积,提高加载速度
这是相对的,在代码分割减小文件体积的同时,也应该考虑 合理的HTTP请求次数
-
-
通常分割哪些代码?
-
提取公有代码
-
提取常用库代码
-
提取 webpack 的 runtime (运行时) 代码
-
-
webpack4默认的分割代码机制,满足如下条件的默认都会被分割
-
新 bundle 被两个及以上模块引用,或者来自 node_modules
-
新 bundle 大于 30kb (压缩之前)
-
异步加载并发加载的 bundle 数不能大于 5 个
-
初始加载的 bundle 数不能大于 3 个
-
-
默认配置如下:
// 默认配置如下: module.exports = { // ... optimization: { splitChunks: { chunks: 'async', //默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效 minSize: 30000, //合并前模块文件的体积 maxSize: 0, minChunks: 1, //最少被引用次数 maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', //自动命名连接符 name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 // 同时满足条件的,优先级更高的生效 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } };
-
自定义代码分割
optimization.splitChunks.cacheGroups
optimization.splitChunks.cacheGroups
下每一个chunk
的配置 选项 同optimization.splitChunks
的配置选项如果设置了将 覆盖
optimization.splitChunks
下的配置选项;如果没设置 就继承// 将代码库 单独打包 module.exports = { optimization: { splitChunks: { cacheGroups: { libs: { test: /[\\/]node_modules[\\/]/, priority: 20, name: '../libs/index', chunks: 'all' } } } } };
-
将 webpack
runtime
代码单独打包// 将代码库 单独打包 module.exports = { optimization: { runtimeChunk: { name: 'static/runtime/index' } } };
-
特殊场景下,需要我们 手动代码分割,可参考如下案例
19. 使用 HappyPack 多线程打包(提升编译速度)
-
默认情况下,webpack 单线程处理任务
由于运行在 Node.js 之上的 Webpack 是单线程模型的
所以Webpack 需要处理的事情需要一件一件的做,不能多件事一起做
-
HappyPack 可以让 webpack 同时处理多个任务
它将任务分解给多个子进程去并发执行,子进程处理完成后再将结果发送给主进程中
从而减少总的构建时间,提升构建效率
-
HappyPack 处理 JS,配置如下
// 安装 npm i happypack@5.0.1 -D
// webpack 配置如下 const os = require('os'); const HappyPack = require('happypack'); const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); module.exports = { module: { rules: [{ test: /\.js$/, // 把对.js 的文件处理交给id为 happyBabel 的 HappyPack 的实例执行 loader: 'happypack/loader?id=happyBabel' }] }, plugins: [ new HappyPack({ id: 'happyBabel', // 用id来标识 happypack处理那里类文件 // 如何处理 用法和loader 的配置一样 loaders: [{ loader: 'babel-loader?cacheDirectory=true' }], threadPool: happyThreadPool, // 共享进程池 verbose: true // 允许 HappyPack 输出日志 }) ] };
-
说明
module.rules.loader
-
在 Loader 配置中,所有文件的处理都交给了
happypack/loader
去处理 -
使用紧跟其后的
?id=happyBabel
去告诉happypack/loader
去选择哪个 HappyPack 实例去处理文件
-
-
说明 HappyPack 参数
-
id
: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件 -
loaders
: Array 用法和 webpack Loader 配置中一样 -
threads
: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数 -
verbose
: Boolean 是否允许 HappyPack 输出日志,默认是 true -
threadPool
: HappyThreadPool 代表共享进程池(即多个 HappyPack 实例使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多) -
verboseWhenProfiling
: Boolean 开启 webpack --profile ,仍然希望HappyPack产生输出 -
debug
: Boolean 启用debug 用于故障排查;默认 false
-
-
注意:
-
HappyPack 在处理 CSS / SCSS 时,需要单独创建一个
postcss.config.js
文件;否则会报错 -
HappyPack 对 url-loader 和 file-loader 的支持度有限
-
-
存疑
-
问题:项目中只配置了子线程编译 JS,编译速度却更慢了(项目没有分离CSS;项目代码不多,生产环境打包后体积 1.13M)
-
原因猜测:HappyPack 多线程的原理是,先开启子线程处理,完成后再讲结果传递给主线程,这个时间 超过了 多线程打包节省的时间(由于需要编译的JS不多)
-
20. DLL动态链接库(提升编译速度)
-
认识 DLL
可以包含给其他模块调用的函数和数据
用过 Windows 系统的人应该会经常看到以 .dll 为后缀的文件
-
web 项目构建接入动态链接库,需要完成以下事情:
-
把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中(一个动态链接库中可以包含多个模块)
-
当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取
-
页面依赖的所有动态链接库需要被加载
-
-
web 项目构建接入动态链接库,好处:提升构建速度(每次构建 不用再重复打包)
-
webpack 已经内置了对动态链接库的支持
-
DllPlugin 插件:打包出一个个单独的动态链接库文件
-
DllReferencePlugin 插件:在主要配置文件中去引入 DllPlugin 插件打包好的动态链接库文件
-
-
实践 DLL
// webpack/dll.config.js 配置文件 const path = require("path"); const webpack = require("webpack"); const libs = require('../configs/dll.config'); const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { mode: 'production', entry: { // 把 vue 相关模块的放到一个单独的动态链接库 vue: ['vue', 'vue-router'], // 把项目需要所有的 lib 放到一个单独的动态链接库 lib: ['jquery', 'moment'], }, output: { path: path.resolve(__dirname, './dll'), filename: "[name].js", library: "_dll_[name]" }, plugins: [ new webpack.DllPlugin({ name: "_dll_[name]", path: path.join(__dirname, 'dll', 'manifest.json'), }), // 清空 dll 文件夹 new CleanWebpackPlugin(['dll']), ] }
配置 npm script { "scripts": { "dll": "webpack --config webpack/dll.config.js", } }
安装 依赖包 npm i clean-webpack-plugin@1.0.0 -D
// webpack 配置文件 const webpack = require('webpack'); module.exports = { plugins: [ // 启用 dll new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./dll/manifest.json') }) ] };
// 新dowload下来的项目,需要先打 DLL 包,再运行项目
21. 全局引入 第三方库
-
webpack 内置插件,支持全局引入第三方库
// webapck.config.js 文件中配置如下: const webpack = require('webpack'); plugins:[ new webpack.ProvidePlugin({ $:"jquery" }) ],
-
对比:
-
全局引入:
全局引入后,项目代码中直接使用即可
如果没有地方用,第三方库将不会被打包
-
局部引入:
每个文件中需要的话,都单独引入
只要引入了,即使没用,第三方库也会打包
-
22. 可视化 分析打包体积
-
插件:
webpack-bundle-analyzer
- 效果
-
生成的报告中有三种尺寸大小:
-
stat
: 压缩等转换之前的输入文件大小(从webpack的stat对象里得到的 -
parsed
: webpack 打包压缩后 输出的JS文件大小(不含资源文件、分离后的CSS等) -
gzip
: 经过 gzip 压缩后的大小
-
-
使用
// 安装 npm i webpack-bundle-analyzer@3.0.3 -D
// webpack 配置 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }; // 打包完成后 自动在浏览器中显示分析结果
-
插件:
webpack-chart
- 在线分析 地址
-
插件:
webpack-analyse
- 在线分析 地址
23. 精简 终端输出
-
部署打包 配置
// webpack 配置文件 module.exports = { // 精简 终端输出(打包部署) stats: { modules: false, children: false, chunks: false, chunkModules: false } };
-
本地运行 配置
webpack-dev-server
// webpack 配置文件 module.exports = { devServer: { // 精简 终端输出(本地运行) stats: { modules: false, children: false, chunks: false, chunkModules: false } } };
24. 显示打包进度
-
如下插件,可显示打包进度,具体配置可查阅 GitHub
-
progress-bar-webpack-plugin
-
nyan-progress-webpack-plugin
-
progress-bar-webpack-plugin
-
progress-webpack-plugin
-