对比搬自:https://github.com/HolyZheng/holyZheng-blog/issues/46,建议直接读原文,这里只是个人的备忘+自己遇到问题的解决方案以及读到的不错文章
背景
写了一个库,打包用的webpack,然后就做了升级。
升级问题
可能是因为自主优化打包不多,所以只遇到了sourcemap生成的问题,问了别人,没人响应。然后阅读了官网,解决问题,官网才是最好的学习资料。
即:sourceMap: false 提到上一层级修改为devtool:"cheap-module-source-map"
参考官网TerserWebpackPlugin 和 devtool
Note about source maps
Works only with source-map, inline-source-map, hidden-source-map and nosources-source-map values for the devtool option.
Why?
eval wraps modules in eval(“string”) and the minimizer does not handle strings.
cheap has not column information and minimizer generate only a single line, which leave only a single mapping.
Using supported devtool values enable source map generation.
// webpack 4
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
output: {
filename: '[name].[chunkhash:20].js'
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: false
}
},
cache: true,
parallel: true,
sourceMap: false,
})
]
}
};
// webpack 5
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
mode: "production",
output: {
filename: "[name].[chunkhash:20].js",
},
devtool:"cheap-module-source-map",
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: false,
ecma: 5,
compress: {
drop_console: true,
drop_debugger: true,
},
},
parallel: true,
}),
],
},
};
webpack 45对比
记录一下webpack的一些常用优化手段以及webpack5的新特性。
- Persistent Caching. ✅ — 😍
- Automatic Node.js Polyfills Removed. ✅ — 😕
- Deterministic Chunk and Module IDs. ✅ — 😀
- SplitChunks and Module Sizes. ✅ — 😀
- Nested/Inner-module/CommonJs tree-shaking
Persistent Caching
webpack4时代
从webpack4开始,我们需要指定配置的 mode 为 production或develop。当我们指定为 production的时候,webpack会将 optimization.minimize 的值置为 true,然后使用 terser-webpack-plugin 来进行代码压缩:
optimization: {
minimize: true
}
我们也可以覆盖webpack默认的terser-webpack-plugin的配置:
minimizer: [
new TerserPlugin({
cache: true, // 开启该插件的缓存,默认缓存到node_modules/.cache中
parallel: true, // 开启“多线程”,提高压缩效率
terserOptions: {
// 其他配置项
},
sourceMap: true, // 生成sourceMap
exclude: /node_modules/
}),
],
我们也可以使用其他JavaScript压缩插件来替代 terser-webpack-plugin ,比如uglifyjs-webpack-plugin。
minimizer: [
new UglifyJsPlugin({
parallel: true,
cache: true,
uglifyOptions: {
// 其他配置项
},
sourceMap: true,
exclude: /node_modules/
})
]
但是官方更推荐的是terser-webpack-plugin,因为uglifyjs-webpack-plugin基于 uglifyjs,而 uglifyjs 不支持es6语法。也就是说,如果你需要压缩的代码具有es6代码的话,压缩就会失败。uglifyjs-webpack-plugin为了支持es6的压缩曾经将uglifyjs换成了uglify-es,但由于uglify-es停止了维护,所以uglifyjs-webpack-plugin又用回了uglifyjs。可以参考
无论是terser-webpack-plugin 还是uglifyjs-webpack-plugin,我们都可以开启多线程parallel以及缓存cache来提高我们的打包效率。缓存会存放到 node_module/.cache/terser-webpack-plugin 或 node_module/.cache/uglifyjs-webpack-plugin 文件夹下。每当我们的webpack配置发送改变的时候,就会重新生成缓存。
webpack5 的改变
到了webpack5 这一块并没有太大的变化,依旧是推荐使用terser-webpack-plugin来进行JS的压缩,但是我们除了可以使用插件plugin提供的cache机制外,webpack5 自身也提供了缓存机制
webpack5 为了提高打包编译的速度,添加了cache特性。在development模式下默认会设为 cache: memory;production模式下会默认会取消该配置。我们可以通过设置:
module.exports = {
cache: {
type: ‘filesystem’,
version: ‘yourVersion’
}
}
来让webpack将缓存保存到本地硬盘中,默认保存的路径是 node_modules/.cache/webpack。这里要注意每当我们修改了webpack配置,记得更新cache的version,否则可能会出现因为重用了缓存导致配置没生效的问题。
| cache措施 | webpack v4 | webpack v5 |
|---|---|---|
| TerserPlugin 提供的 cache | 第一次打包 27000ms+,第二次打包 7000ms+ | 第一次打包 36000ms+,第二次打包 7000ms+ |
| webpack.cache = ‘filesystem’ | 无 | 第一次打包 35000ms+,第二次打包 28000ms+ |
| webpack.cache = ‘filesystem’ && TerserPlugin 提供的 cache | 无 | 第一次打包 36000ms+,第二次打包 1000ms+ |
Node.js Polyfills
在webpack5之前,webpack会自动的帮我们项目引入Node全局模块polyfill。我们可以通过node配置
// false: 不提供任何方法(可能会造成bug),‘empty’: 引入空模块, ‘mock’: 引入一个mock模块,但功能很少
module.exports = {
// …
node: {
console: false,
global: false,
process: false,
// …
}
}
但是webpack团队认为,现在大多数工具包多是为前端用途而编写的,所以不再自动引入polyfill。我们需要自行判断是否需要引入polyfill,当我们用weback5打包的时候,webpack会给我们类似如下的提示:
// 在项目中我使用到了 crypto 模块,webpack5会询问是否引入对应的 polyfill。
Module not found: Error: Can't resolve 'crypto' in '/Users/xxx/Documents/private-project/webpack/ac_repair_mobile_webpack_5/node_modules/sshpk/lib/formats'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need these module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add an alias 'resolve.alias: { "crypto": "crypto-browserify" }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.alias: { "crypto": false }
webpack5中,增加了resolve.alias配置项来告诉webpack是否需要引入对应polyfill。node配置项也做了调整。
module.exports = {
// …
resolve: {
alias: {
crypto: ‘crypto-browserify’,
// …
}
},
node: {
// https://webpack.js.org/configuration/node/#root
// 只能配置这三个
global: false,
__filename: false,
__dirname: false,
}
}
也就是说到了webpack5,我们需要清楚自己的项目需要引入哪些node polyfill。更加了配置的门槛,但是减少了代码的体积。
webpack5中将path、crypto、http、stream、zlib、vm的node polyfill取消后
| webpack v4 | webpack v5 | |
|---|---|---|
| 行为 | webpack尝试自动引入各个node模块的polyfill | webpack默认不主动引入node模块的polyfill |
| 措施 | 开发者可以使用 node: {}配置项来改成mock或不提供模块 | 开发者可以使用 resolve.alias配置项决定每个node模块是否引入polyfill |
| 体验 | 一般不会主动配置 | 在打包前必须确定好用到的所有node模块是否需要引入polyfill,否则打包会被中断。同时难以提前确定哪些node模块可以省略polyfill |
| 效果 | 最终js代码体积2.78M | 最终js代码体积:2.17M(由于下面所说原因,不确定省略了这些polyfill后项目是否正常运行) |
问题
使用webpack5后,项目运行始终报错:Uncaught ReferenceError: process is not defined. 这是因为webpack5 删减了node:{}的配置内容,并且将node.process 始终设为false。但是目前无法通过resolve.alias来给process指明polyfill。(buffer也同理)如果项目里用到了process这个node模块的话如何解决?
Deterministic Chunk and Module IDs
在日常开发中,我们会对打包出来的代码文件加上哈希后缀,以便做版本管理。同时也带来了另一个问题:哈希改变导致缓存失效。我们需要尽量减少哈希改变的情况,这就是我们一直说的“优化持久化缓存”。一个最普遍的问题就是chunkId或moduleId的改变导致文件哈希后缀的改变。(业务逻辑没有变化的情况下)
ps:chunkId:打包出来的每个文件就是一个chunk。moduleId:代码里的每一个模块都是module。webpack5之前他们都是自增id。
// 在入口文件index.js新增了模块demo
// …
import {a} from ‘./demo’
console.log(a);
// …
所有文件的哈希后缀都发生了改变,不符合期望,vender~xxx.js的hash不应发生变化。
继续当我们新增一个入口的时候:
entry: {
index: [’./src/index.js’],
index2: [’./src/index2.js’]
},

同样的所有文件的哈希后缀都发生了改变,不符合期望,原有文件hash不应发生变化。
webpack4可以通过设置optimization.moduleIds = ‘hashed’与optimization.namedChunks=true来解决这写问题,但都有性能损耗等副作用。而webpack5 在production模式下optimization.chunkIds和optimization.moduleIds默认会设为’deterministic’,webpack会采用新的算法来计算确定性的chunkI和moduleId。进而避免上述情况发生。
| 稳定ModuleIDs | webpack v4 | webpack v5 |
|---|---|---|
| 措施 | optimization.moduleIds = ‘hashed’ | 默认的 optimization.moduleIds = ‘deterministic’ |
| 添加新模块时表现 | 只有index.xxx.js文件的hash发变化,符合预期 | 同左,符合预期 |
| 优缺点 | 将模块路径进行hash作为moduleId,该过程有一定的性能损耗(感知小) |
| 稳定ChunkIDs | webpack v4 | webpack v5 |
|---|---|---|
| 措施 | optimization.namedChunks=true | 默认的 optimization.chunkIds = ‘deterministic’; namedChunks在生产模式下被禁用 |
| 新增入口时的表现 | 原有文件哈希没有发生变化,符合预期(moduleIds = 'hashed’也要同时开启) | 同左,符合预期 |
| 优缺点 | 基于NamedChunksPlugin将chunk名称作为chunkId,本意是为了开发环境更方便调试 |
SplitChunks and Module Sizes
在项目中有时候我们需要将一些重复的公用代码或一些异步加载的代码提取出来从而对打包出来的代码进行体积或网络缓存方面的优化。webpack3的时候,我可以通过 CommonsChunkPlugin 来进行处理:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ‘common’ // 指定公共 bundle 的名称。
})
]
到了webpack4以及webpack5,我们可以通过 SplitChunksPlugin 来处理,我们利用它来对代码进行分割:
// 默认配置
module.exports = {
//…
// https://github.com/webpack/changelog-v5#changes-to-the-configuration
// https://webpack.js.org/plugins/split-chunks-plugin/
optimization: {
splitChunks: {
chunks: ‘async’, // 只对异步加载的模块进行处理
minSize: 30000, // 模块要大于30kb才会进行提取
minRemainingSize: 0, // 代码分割后,文件size必须大于该值 (v5 新增)
maxSize: 0,
minChunks: 1, // 被提取的模块必须被引用1次
maxAsyncRequests: 6, // 异步加载代码时同时进行的最大请求数不得超过6个
maxInitialRequests: 4, // 入口文件加载时最大同时请求数不得超过4个
automaticNameDelimiter: ‘~’, // 模块文件名称前缀
cacheGroups: {
// 分组,可继承或覆盖外层配置
// 将来自node_modules的模块提取到一个公共文件中 (又v4的vendors改名而来)
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
// 其他不是node_modules中的模块,如果有被引用不少于2次,那么也提取出来
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
SplitChunksPlugin的默认配置是经过webpack团队经过大量测试精心挑选的较为通用的配置,对于我们大部分的项目我们直接使用默认配置即可,当然为了更大程度的利用代码分割的特性,我们可以将chunks修改为 chunks: 'all',这样webpack就会对我们应用的entry points以及import() 的模块进行分割处理。
提取webpack runtime代码
另外还可以通过 optimization.runtimeChunk 配置将webpack运行时的代码提取出来,因为这部分代码理论上是不会变的。可以通过将这部分文件进行缓存。同时也可以以防万一避免这部分代码的改变导致其他提出出来的模块的哈希后缀发送变化而导致缓存失效。
optimization: {
// …
runtimeChunk: true
},
最大化利用缓存
在日常开发中,我们还会给打包出来的文件加上哈希后缀,以便缓存以及版本管理。而添加哈希后缀有以下几种常规做法:
// 1. 不推荐,每个文件间的hash值存在耦合
output: {
filename: ‘js/[name].[hash].js’
// …
},
// 2. 可以
output: {
filename: ‘js/[name].[chunkhash].js’
// …
},
// 3. 推荐
output: {
filename: ‘js/[name].[contenthash].js’
// …
},
为了做到版本控制的同时最大化利用缓存,我们要避免每个文件之间的哈希后缀的耦合。每个文件的哈希后缀应该只与它自身的内容有关。使用 contenthash 可以在你修改了业务代码重新打包的时候,提取自node_modules以及webpack runtime的文件哈希后缀保持不变,继续利用缓存。而且在webpack5中,我们可以对 minSize和maxSize进行更详细的配置(而不是只能填一个数字),并且还可以设置js之外的文件类型的size:
splitChunks: {
chunks: ‘all’,
minSize: {
javascript: 30000,
style: 20000,
}
}
tree-shaking
webpack5的tree-shaking可以涵盖更多的场景:
在小型项目,或比较少问题的项目里感知不强。
总结:
等到webpack v5发布和相关生态跟上后,还是非常建议在新项目上去使用webpack5的。整体的效果就是:
- 打包速度大幅度提升 (Persistent Caching)
- 代码体积减少 (Automatic Node.js Polyfills Removed-主要 && Better Tree-shaking-次要)
- 长期缓存得到webpack默认优化,降低了对应的配置门槛(Deterministic Chunk and Module IDs)
- 配置门槛稍微提高,需提前自行判断是否需要引入Polyfills(Automatic Node.js Polyfills Removed)
- 更灵活的代码分割配置 (虽然只是灵活了一点点—minSize/maxSize的改动以及一些新字段的添加)


1012

被折叠的 条评论
为什么被折叠?



