Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack plugin开发相关资料 #96

Open
Genluo opened this issue Aug 9, 2020 · 3 comments
Open

webpack plugin开发相关资料 #96

Genluo opened this issue Aug 9, 2020 · 3 comments

Comments

@Genluo
Copy link
Owner Author

Genluo commented Aug 9, 2020

webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
webpack Tapable使用

@Genluo
Copy link
Owner Author

Genluo commented Aug 23, 2020

bundle:由多个不同的模块打包生成生成最终的js文件,一个js文件即是1个bundle。

chunk: Graph的组成部分。一般有n个入口=n个bundle=graph中有n个chunk。但假设由于n个入口有m个公共模块会被重复打包,需要分离,最终=n+m个bundle=graph中有n+m个chunk

chunk生成依赖图算法

@Genluo
Copy link
Owner Author

Genluo commented Aug 23, 2020

[---初始化阶段---]

初始化参数:webpack.config.js / shell+yargs(optimist) 获取配置options
初始化 Compiler 实例 (全局只有一个,继承自Tapable,大多数面向用户的插件,都是首先在 Compiler 上注册的)

Compiler:存放输入输出配置+编译器Parser对象
Watching():监听文件变化
初始化 complier上下文,loader和file的输入输出环境
初始化础插件WebpacOptionsApply()(根据options)
[entry-option] :读取配置的 Entrys,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备
[after-plugins] : 调用完所有内置的和配置的插件的 apply 方法。
[after-resolvers] : 根据配置初始化完 resolver,resolver 负责在文件系统中寻找指定路径的文件。
[environment] : 开始应用 Node.js 风格的文件系统到 compiler 对象,以方便后续的文件寻找和读取。
[after-environment]

[----构建Graph阶段 1----]

入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理

[before-run]
[run]启动一次新的编译

  • 使用信息Compiler.readRecords(cb)

  • 触发Compiler.compile(onCompiled) (开始构建options中模块)

  • 创建参数Compiler.newCompilationParams()
    [normal-module-factory] 引入NormalModule工厂函数
    [context-module-factory] 引入ContextModule工厂函数
    [before-compile]执行一些编译之前需要处理的插件
    [compile]

  • 实例化compilation对象

    • Compiler.newCompilation(params)
    • Compiler.createCompilation()
      该对象负责组织整个编译过程,包含了每个构建环节对应的方法。对象内部保留了对compile的引用,供plugin使用,并存放所有modules,chunks,assets(对应entry),template。根据test正则找到导入,并分配唯一id
      [this-compilation]触发 compilation 事件之前
      [compilation]通知订阅的插件,比如在compilation.dependencyFactories中添加依赖工厂类等操作

[----构建Graph阶段 2----]

[make]是compilation初始化完成触发的事件

通知在WebpackOptionsApply中注册的EntryOptionPlugin插件
EntryOptionPlugin插件使用entries参数创建一个单入口(SingleEntryDependency)或者多入口(MultiEntryDependency)依赖,多个入口时在make事件上注册多个相同的监听,并行执行多个入口
tapAsync注册了一个DllEntryPlugin, 就是将入口模块通过调用compilation.addEntry()方法将所有的入口模块添加到编译构建队列中,开启编译流程。
随后在addEntry 中调用_addModuleChain开始编译。在_addModuleChain首先会生成模块,最后构建。在_addModuleChain中根据依赖查找对应的工厂函数,并调用工厂函数的create来生成一个空的MultModule对象,并且把MultModule对象存入compilation的modules中后执行MultModule.build,因为是入口module,所以在build中没处理任何事直接调用了afterBuild;在afterBuild中判断是否有依赖,若是叶子结点直接结束,否则调用processModuleDependencies方法来查找依赖
上面讲述的afterBuild肯定至少存在一个依赖,processModuleDependencies方法就会被调用;processModuleDependencies根据当前的module.dependencies对象查找该module依赖中所有需要加载的资源和对应的工厂类,并把module和需要加载资源的依赖作为参数传给addModuleDependencies方法;在addModuleDependencies中异步执行所有的资源依赖,在异步中调用依赖的工厂类的create去查找该资源的绝对路径和该资源所依赖所有loader的绝对路径,并且创建对应的module后返回;然后根据该module的资源路径作为key判断该资源是否被加载过,若加载过直接把该资源引用指向加载过的module返回;否则调用this.buildModule方法执行module.build加载资源;build完成就得到了loader处理过后的最终module了,然后递归调用afterBuild,直到所有的模块都加载完成后make阶段才结束。
在make阶段webpack会根据模块工厂(normalModuleFactory)的create去实例化module;实例化moduel后触发this.hooks.module事件,若构建配置中注册了DllReferencePlugin插件,DelegatedModuleFactoryPlugin会监听this.hooks.module事件,在该插件里判断该moduel的路径是否在this.options.content中,若存在则创建代理module(DelegatedModule)去覆盖默认module;DelegatedModule对象的delegateData中存放manifest中对应的数据(文件路径和id),所以DelegatedModule对象不会执行bulled,在生成源码时只需要在使用的地方引入对应的id即可。
make结束后会把所有的编译完成的module存放在compilation的modules数组中,通过单例模式保证同样的模块只有一个实例,modules中的所有的module会构成一个图。
[before-resolve]准备创建Module
[factory]根据配置创建Module的工厂类Factory(Compiler.js) 使用Factory创建 NormalModule实例 根据rule.modules创建RulesSet规则集
[resolver]通过loader的resolver来解析loader路径
[resolve]使用loaderResolver解析loader模块路径
[resolve-step]
[file]
[directory]
[resolve-step]
[result]
[after-resolve]
[create-module]
[module]
[build-module] NormalModule实例.build() 进行模块的构建
[normal-build-loader] acron对DSL进行AST分析
[program] 遇到require创建依赖收集;异步处理依赖的module,循环处理依赖的依赖
[statement]
[succeed-module]

[---- 优化Graph----]

compilation.seal(cb)根据之前收集的依赖,决定生成多少文件,每个文件的内容是什么. 对每个module和chunk整理,生成编译后的源码,合并,拆分,生成 hash,保存在compilation.assets,compilation.chunk

[seal]密封已经开始。不再接受任何Module
[optimize] 优化编译. 触发optimizeDependencies类型的一些事件去优化依赖(比如tree shaking就是在这个地方执行的)

根据入口module创建chunk,如果是单入口就只有一个chunk,多入口就有多个chunk;
根据chunk递归分析查找module中存在的异步导module,并以该module为节点创建一个chunk,和入口创建的chunk区别在于后面调用模版不一样。
所有chunk执行完后会触发optimizeModules和optimizeChunks等优化事件通知感兴趣的插件进行优化处理。
createChunkAssets生产assets给chunk生成hash然后调用createChunkAssets来根据模版生成源码对象.所有的module,chunk任然保存的是通过一个个require聚合起来的代码,需要通过template产生最后带有__webpack__reuqire()的格式。

createChunkAssets.jpg
根据chunks生产sourceMap使用summarizeDependencies把所有解析的文件缓存起来,最后调用插件生成soureMap和最终的数据
把assets中的对象生产要输出的代码assets是一个对象,以最终输出名称为key存放的输出对象,每一个输出文件对应着一个输出对象
[after-optimize-assets]资产已经优化
[after-compile] 一次 Compilation 执行完成。
[---- 渲染Graph----]

[should-emit] 所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。
Compiler.emitAssets()

[emit]

按照 output 中的配置项异步将将最终的文件输出到了对应的 path 中
output:plugin结束前,在内存中生成一个compilation对象文件模块tree,枝叶节点就是所有的module(由import或者require为标志,并配备唯一moduleId),主枝干就是所有的assets,也就是我们最后需要写入到output.path文件夹里的文件内容。
MainTemplate.render()和ChunkTemplate.render()处理入口文件的module 和 非首屏需异步加载的module
MainTemplate.render()

处理不同的模块规范Commonjs,AMD...
生成好的js保存在compilation.assets中
[asset-path]

[after-emit]

[done]

if needAdditionalPass

needAdditionalPass()

回到compiler.run
else this.emitRecords(cb)
调用户自定义callback
[failed] 如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant