webpack 高级配置
摇树(tree shaking)
我主要是想说摇树失败的原因(tree shaking 失败的原因),先讲下摇树本身效果
什么是摇树?
举个例子
首先 webpack.config.js 配置
在固定 a.js 用 esm 导出,b.js 用 commonjs 导出不变动
例子 1:import a.js 和 require b.js
打包结果:a.j 和 b.js 都摇树了,只输出了 f1 和 f3。所以导入用import,导出esm和commonjs都可以
![](https://static001.geekbang.org/infoq/20/2070ab63abe22e933e32413e14775142.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
例子 2:import a.js 和 import b.js
打包结果:a.js 摇,b.js 没摇,输出了 f1 、f3、f4。所以导入用require不成功
![](https://static001.geekbang.org/infoq/84/842a0e1cc0ac8a65fc152b950b5266e8.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
结论:
摇树只能import,导出用esm和commonjs都可以
因为摇树发生在编译阶段
,只支持 esm 的 import,不支持 commonjs 的 require,因为 esm 是编译时,commonjs 是运行时
摇树失败的原因
三方面可能导致失败:
1、代码没用 import 引入
2、webpack 配置没开启摇树
3、副作用(sideEffects)
4、babel 配置 preset-env 没写 module:false 参数
代码没用 import 引入
这一点上面已经说明,必须用 import 导入,导出用 esm 或者 commonjs 都行
webpack 配置没开启摇树
开启摇树两步:
1、usedExports 设置 true,标记无用代码,esm 导出的没使用到的导出函数标记为unused harmony export f2
,commonjs 导出的没使用的导出函数赋值为__webpack_unused_export__
2、terser-webpack-plugin 插件做代码压缩去除无用代码,根据一步两种标记,压缩代码会去除
mode: production
模式下,默认开启摇树,不用做任何配置,由源码看出none
和development
不会开启摇树,需要手动加这两步,注意要设置minimize:true
,或者放到 plugins 中
看 webpack 源码默认配置,参考 webpack 视频讲解:进入学习
![](https://static001.geekbang.org/infoq/3a/3a859a1f5789c9585902014620cf9851.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
副作用(sideEffects)
先来解释下什么是副作用:修改当前作用域之外的行为都叫副作用
,比如在函数内部,修改 dom,修改全局对象等等
这条主要是针对引入三方包,三方包package.json的sideEffects字段默认true表示有副作用
,可以设置为 false 表示没有副作用,设置为数组列出有副作用的文件
在webpack.config.js设置sideEffects:true表示检查三方包的sideEffects字段
,webpack 在用 userExports 标记无用代码时,如果判断不出库中代码是否有副作用,就不会标记,则压缩的时候也没法清除,如果判断有副作用,则更不会标记清除
mode: production
模式下,默认开启摇树,不用做任何配置,usedExports: true
babel 配置 preset-env 没写 module:false 参数
在文章 我掌握的 Babel 配置 中详细讲解了 module: false 参数,简单说不设置 false 时,只针对babel相关的runtime包
的引入会使用 require,设置了 false 引入会使用 import,就能让 webpack 去摇树,回到第一点上
拆包(splitChunks)
splitChunks 是 webpack 配置下 optimization 下的配置,即优化。看单词理解意思就是拆分多个 chunk。
什么是 chunk
webpack 的本质是把多个 js 模块合并到一个 js 中,即一个入口得到一个输出 js 文件(bundle.js)。
但是导致的问题是,如果这个 bundle.js 文件很大,那么浏览器请求的时候,导致请求时间很长,首屏长时间白屏。
所以优化手段就是把 bundle.js 文件拆分成多个小的 js 文件,同时请求,首屏当然就更快渲染显示。
所以入口文件,chunk 文件,输出文件三者的关系从原来的一个入口文件对应一个chunk最后输出一个bundle文件
改变为一个入口文件对应多个chunk最后输出多个bundle文件
三种方式获得 chunk
1、入口文件可以生成 chunk,入口文件即 webpack 配置的
entry
选项;2、异步请求 import 函数调用 或者 require.ensure 可以生成 chunk;如:import 函数即我们在写 vue-router 时写的异步请求路由方式,这里
webpackChunkName
可以魔法定义 chunk 名,也可不写
3、webpack 配置 splitChunks 手动拆分生成 chunk,最后独立输出到 js 文件
splitChunks 配置
简单配置,把 react 相关包都单独提到一个文件
先来看下 webpack 默认的 splitChunks 参数
![](https://static001.geekbang.org/infoq/23/234f52bd0b0c8c6c74f8c046b380f086.webp?x-oss-process=image%2Fresize%2Cp_80%2Fformat%2Cpng)
看图production
和非production
模式下有参数不一样,下面这些参数表示自动拆包的条件:
chunks
重要:
拆包的范围,默认 async,只针对异步请求的,即上面第二条的 import 函数调用的 chunk 里面;initial 表示只针对初始化入口 entry 的;all 表示最大包含 async + entry
cacheGroups
重要:
自定义拆包规则,name 是 chunk 名,test 正则包名,priority 优先级(因为同一个包可能符合多个拆包规则,会处理给优先级高的);看图可知,默认会有两个包规则,defaultVendors
规则表示node_modules
会拆到一个 chunk 包,default
规则表示只有被两个即以上 chunk 引用就要拆到一个 chunk 包
minChunks
拆分前必须共享模块的最小 chunks 数,可以不用修改
maxAsyncRequests
浏览器发送异步请求时,最大不超过 30 个请求,即上面第二条的 import 函数调用,可以不用修改
maxInitialRequests
浏览器请求入口 entry 时,最大不超过 30 个,可以不用修改
热更新
我们主要是说明热更新的 module.hot.accept()
先来了解一下热更新怎么配置的?
热更新配置
装包
webpack.config.js
package.json
结论
到此热更新配置完成,正常写代码,但是发现问题了,此时更新页面是整个刷新页面的,并不是局部刷新
,怎么回事呢,原来需要在每个文件中最后加上module.hot.accept()
才会触发局部更新,accept 可以接受两个参数,依赖和回调
随即产生了另一个疑问,这太麻烦了吧,每个文件文件都需要去加module.hot.accept()
,但是我们在实际写下项目的时候怎么没有写这句呢?
原因是不论 css、vue、react 的 loader 都帮我们自动加了这句。
css 有 style-loader,react 有 react-hot-loader,vue 有 vue-loader。
对于 jsx 文件,有vue-jsx-hot-loader
按需加载
一段时间以来,我一直把 tree shaking 和按需加载混为一谈,其实应该分开理解,这里我主要是想说第三方包的按需加载,比如使用 element-ui、lodash、vant
tree shaking 的前提是使用 import 导入,但是按需加载并不需要
还有一个点需要注意:如果是我们封装的库,如组件库,导出格式根据文件类型不同,如是 js 文件可以为 commonjs + es5、esm + es5;如是 vue 或 react 文件,esm/commonjs + es6/es5 任意都行,因为我们用 babel-loader 时会排除 node_modules 目录不编译,vue-loader 等会去编译 vue 文件
使用 babel 插件
babel.config.js
完毕!
评论