webpack已经成为开发web应用不可或缺的工程化工具,而作为webpack支柱功能的插件系统,是我们深入学习webpack绕不过的一道坎。站在插件开发的视角,你可以更细粒度地控制webpack的工作,实现那些通过修改配置文件做不到的事情。
小程序 & webpack
最近公司团队使用webpack来给微信小程序项目做工程化,每个页面的js文件作为webpack的一个独立entry,经过编译打包后,每个entry都对应生成一个output js文件,引用了node_modules文件夹下面的模块也会被打包进来,因此不需要执行小程序构建npm这一过程。但是造成了模块重复打包的问题:
如上图,A模块被1、2两个页面引用,最终A模块被重复打包进了两个页面的输出文件里,造成小程序打包体积过大。
经过修改webpack配置、提取公共模块之后解决了该问题。但毕竟是跳过了“构建npm”这一过程,因此我决定开发一款webpack插件,模拟小程序的这一过程。
What to do?
先看一下小程序在“构建npm”这一步骤做了什么:
构建后的目录结构大致如下:
|--node_modules
|--miniprogram_npm
| |--testComp // 小程序 npm 包
| | |-index.js
| | |-index.json
| | |-index.wxss
| | |-index.wxml
| |--testa // 其他 npm 包
| |--index.js // 打包后的文件
| |--miniprogram_npm
| |--testb
| |--index.js // 打包后的文件
| |--index.js.map
|--pages
|--app.js
|--app.wxss
|--app.json
|--project.config.js
简单来说:先将node_modules
目录下的模块,拷贝到同级的miniprogram_npm
目录;然后将所有对node_modules
模块的引用,修改到miniprogram_npm
,例如require('node_modules/abc')
改为require('miniprogram_npm/abc')
。
我们要做的就是用webpack插件复刻上面的步骤:
- 遍历所有webpack解析出的模块,找出所有公共Modules(node_modules目录、以及作为参数传给插件的目录)
- 公共Modules与其被引用的所有Chunks解绑
- 给每个公共Module创建新Chunk,一个Chunk关联一个Module,并插入Chunks列表
- 生成Assets阶段,修改文件源代码,根据本模块的dependencies和目标模块的reasons,插入require语句
How to do?
1. 对官方文档有个大概的了解
只是大概,着手开发之前不需要看的特别细,因为大部分知识都没有通过文档整理出来。可以看下这篇:webpack.js.org/contribute/…,作为入门。
tapable,懂得用哪种类型的挂钩即可,原理暂不必深究。
(一定要看webpack英文官网,中文官网内容更新较晚!)
2. 善用断点调试
因为是在node端编程,我们在webstorm、vscode这样的编辑器里面就可以打断点,然后启动debug模式
各变量的数据结构一目了然。
3. 了解webpack常用变量的结构
例如Compiler、Compilation、Module、Chunk等,官方文档对这部分并没有说明,因此还是要通过断点+源码的方式。
- 盯断点:可能需要在断点模式下,盯着数据结构看很久、很多次
- 看源码:看类的常用方法,ts文件(有些ts类型相对于源码少了东西,应该是官方没来得及更新)
4. 增删改内部变量时,优先调用封装好的函数
例如解除module和chunk的绑定,如果执行module._chunk.delete(chunk)
,仅仅是解除了module对chunk的依赖,而两者是双向依赖,就意味着还需要反向解除chunk对module的依赖。
但不需要这么麻烦,通过源码我们发现,可以直接调用Module类提供的一个方法:
removeChunk(chunk) {
if (this._chunks.delete(chunk)) {
chunk.removeModule(this);
return true;
}
return false;
}
解除了module和chunk的双向依赖。
5. 选对时机:hooks
Compiler和Compilation都提供各种钩子函数,可以参考这篇文档:webpack.js.org/api/compile…。列表里的函数是按照执行顺序来排列的,注意区分回调函数传入参数的不同。
Compiler:我理解为webpack的实例,全局唯一 Compilation:一次编译,run模式下只执行一次,watch模式下每次文件变化执行一次
各个钩子函数的解释文档里面都有,但我还是不知道从何入手,怎么办? 这时可以找一个比较成熟的插件,参考它的源码。
比如我要做的功能,和SplitChunkPlugin
的功能非常相似,于是我去github搜索,没有找到...然后发现它已经被webpack内置了,在webpack的源代码中找到了它。然后就是参考它的hooks切入点,模仿它。这是对新手最友好的方式,特别是看hooks文档看的一头雾水的时候。
6. 修改webpack config
当插件要实现的功能,需要修改webpack的配置文件怎么办?其实可以在插件内部修改:
compiler.hooks.environment.tap('HelloWorldPlugin', () => {
// 修改webpack config,必须抽出runtime.js
compiler.options.optimization.runtimeChunk = {
name: 'runtime'
}
});
在适当的hooks里面,通过compiler.options
对象访问、修改。
或者不这样实现,而是手动修改配置文件,两种方式,自由选择,这里只是说明插件拥有这个功能。
成果
插件引用方式:
new HelloWorldPlugin({
libPaths: ['src/utils']
}),
引入了插件之后的打包结果:
|--mp_node_modules // 类似小程序的miniprogram_npm目录
|--pages
|--utils
|--components
|--app.js
|--app.wxss
|--app.json
|--runtime.js
|--sitemap.json
根据依赖关系,在文件头部插入require语句:
小程序打包缺陷
因为该原则的原因,所有node_modules
模块,都会被打包到主包,因此对于稍大型的应用来说,随着工程规模扩大,主包随时可能超过2M上限,需要将业务拆分至多个小程序才能解决。
不过换个角度一想,可能小程序的初衷就是“小”,有可能是微信官方故意而为之。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!