一 目录
不折腾的前端,和咸鱼有什么区别
二 前言
- Webpack 的构建过程太花时间
- Webpack 打包的结果体积太大
三 针对 Webpack 本身构建优化
3.1 优化 resolve.modules 配置
resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块,默认是 [‘node_modules’]。
但是,它会先去当前目录的 ./node_modules 查找,没有的话再去 ../node_modules,最后到根目录 —— 即 npm 查找包的规则。
所以可以直接指定项目根目录,这样代码就不需要一层一层查找。
resolve: { modules: [path.resolve(__dirname, ‘node_modules’)], }
3.2 优化 resolve.extensions 配置
在导入没带文件后缀的路径时,Webpack 会自动带上后缀去尝试询问文件是否存在,而 resolve.extensions 用于配置尝试后缀列表;默认为 extensions:[‘js’, ‘json’]。
当遇到 require(‘./data’) 时 Webpack 会先尝试寻找 data.js,没有再去找 data.json;如果列表越长,或者正确的后缀越往后,尝试的次数就会越多。
所以在配置时为提升构建优化需遵守:
- 频率出现高的文件后缀优先放在前面。
- 列表尽可能的少,例如只有 3 个:js、jsx、json。
- 书写导入语句时,尽量写上后缀名。
3.3 优化 resolve.include/exclude 配置
以 babel-loader 为例,可以通过 include 和 exclude 帮助我们避免 node_modules 这类庞大文件夹。
即通过 include 告诉 Webpack 哪些我们是需要检测的,通过 exclude 告诉 Webpack 哪些我们是不需要检测的(例如已经收拾过的静态资源)
四 通过 Loader 和 Plugin 优化
4.1 缓存
在 babel-loader 开启 cache 后,将 loader 的编译结果写进硬盘缓存,再次构建如果文件没有发生变化则会直接拉取缓存。
- uglifyjs-webpack-plugin
通过这个插件也可以解决缓存问题。
注:具体的要根据当前的 Webpack 版本,Loader 和 Plugin 表示 Webpack 每次更新都会淘汰一批没有跟进维护的 Loader 和 Plugin。就跟大佬还在持续学习,你几年没学习之后,遇到金融危机被淘汰的风险就高了。
4.2 多进程
由于有大量文件需要解析和处理,构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack 构建慢的问题会显得严重。
文件读写和计算操作是无法避免的,那能不能让 Webpack 同一时刻处理多个任务,发挥多核 CPU 电脑的威力,以提升构建速度呢?
Happypack 可以将任务分解成多个子进程去并发执行,大大提升打包效率。
除此之外 thread-loader 和 Happypack 一样,但是配置比较简单。
Happypack 已经不维护了。Github - Happypack
4.3 多进程压缩
因为自带的 UglifyjsWebpackPlugin 压缩插件是单线程运行的,而 TerserWebpackPlugin 可以并发运行压缩功能(多进程)。
所以通过 TerserWebpackPlugin 代替自带的 UglifyjsWebpackPlugin 插件。
4.4 静态资源分离
通过 DllPlugin 或者 Externals 进行静态依赖包的分离。
由于 CommonsChunkPlugin 每次构建会重新构建一次 vendor,所以出于效率考虑,使用 DllPlugin 将第三方库单独打包到一个文件中,只有依赖自身发生版本变化时才会重新打包。
4.5 代码分离
在 Webpack 中,到底什么是代码分离?代码分离允许你把代码拆分到多个文件中。如果使用得当,你的应用性能会提高很多。因为浏览器能缓存你的代码。
每当你做出一次修改,包含修改的文件需要被所有访问你网站的人重新下载。但你并不会经常修改应用的依赖库。
如果你能把那些依赖库拆分到完全分离的文件中,即使业务逻辑发生了更改,访问者也不需要再次下载依赖库,直接使用之前的缓存就可以了。
由于有了 SplitChunksPlugin,你可以把应用中的特定部分移至不同文件。如果一个模块在不止一个 chunk 中被使用,那么利用代码分离,该模块就可以在它们之间很好地被共享。
4.6 打包资源压缩
- JS 压缩:UglifyjsWebpackPlugin
- HTML 压缩:HtmlWebpackPlugin
- CSS 压缩:MiniCssExtractPlugin
- 图片压缩:image-webpack-loader
- Gzip 压缩:不包括图片
五 其他优化点
5.1 Tree Shaking
通过 ES6 的 import/export 来检查未引用代码,以及 sideEffects 来标记无副作用代码,最后用 UglifyJSPlugin 来做 Tree Shaking,从而删除冗余代码。
5.2 Scope Hoisting
Scope Hoisting 是 Webpack3 的新功能,直译为 「作用域提升」,它可以让 Webpack 打包出来的 「代码文件更小」,「运行速度更快」。
熟悉 JavaScript 都应该知道 「函数提升」 和 「变量提升」 ,JavaScript 会把函数和变量声明提升到当前作用域的顶部。
「作用域提升」 也类似于此,Webpack 会把引入的 js 文件 “提升到” 它的引入者顶部。
Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能将打散的模块合并到一个函数中,前提是不能造成代码冗余。因此「只有那些被引用了一次的模块才能被合并」。
由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码「必须采用 ES6 模块化语句」,不然它将无法生效。原因和 Tree Shaking 中介绍的类似。
5.3 按需加载
- 什么是 代码分割(code splitting)?
代码分割是指:将脚本中无需立即调用的代码在代码构建时转变为异步加载的过程。
在 Webpack 构建时,会避免加载已声明要异步加载的代码,异步代码会被单独分离出一个文件,当代码实际调用时被加载至页面。
代码分割技术的核心是 异步加载资源。
可喜的是,浏览器允许我们这么做,W3C stage 3 规范: whatwg/loader 对其进行了定义:你可以通过 import() 关键字让浏览器在程序执行时异步加载相关资源。
在 Vue 中,可以直接使用 import() 关键字做到这一点,而在 React 中,你需要使用 react-loadable 去完成同样的事。
六 优化体验
- progress-bar-webpack-plugin:在终端底部,将会有一个构建的进度条,可以让你清晰的看见构建的执行进度。
- webpack-build-notifier:在构建完成时,能够像微信、Lark 这样的 APP 弹出消息的方式,提示构建已经完成。
- webpack-dashboard:对 Webpack 原始的构建输出不满意的话,也可以使用这样一款 Plugin 来优化你的输出界面。
- speed-measure-webpack-plugin:该插件可以测量各个插件和 loader 所花费的时间。
- webpack-bundle-analyzer:可视化分析。通过矩阵树图的方式将包内各个模块的大小和依赖关系呈现出来。
- webpack-chart
- webpack-analyse
七 参考文献
- Webpack优化——将你的构建效率提速翻倍【阅读建议:10min】
- 性能优化篇—-Webpack构建速度优化【阅读建议:10min】
- 使用webpack4提升180%编译速度【阅读建议:10min】
- 多进程并行压缩代码【阅读建议:5min】
- webpack 4: Code Splitting和chunks切分优化【阅读建议:5min】
2018 年文章:
- Tree-Shaking性能优化实践 - 原理篇【阅读建议:10min】
- 体积减少80%!释放webpack tree-shaking的真正潜力【阅读建议:10min】
- 你的Tree-Shaking并没什么卵用【阅读建议:5min】
- webpack 如何通过作用域分析消除无用代码【阅读建议:5min】
- 让你的Webpack起飞—考拉会员后台Webpack优化实战【阅读建议:5min】
- webpack dllPlugin打包体积和速度优化【阅读建议:5min】
- webpack优化之code splitting【阅读建议:5min】
2017 年文章:
- Webpack 打包优化之速度篇【阅读建议:5min】
- 加速Webpack-缩小文件搜索范围【阅读建议:5min】
- Webpack 大法之 Code Splitting【阅读建议:5min】