插件plugin,webpack重要的组成部分。它以事件流的方式让用户能够直接接触到webpack的整个编译过程。plugin在编译的关键地方触发对应的事件,极大的加强了webpack的扩展性。它的出现让webpack从一个面向过程的打包工具,变成了一套完整的打包生态系统。webpack
既然说到了事件流,那么就得介绍Tapable了,Tapable是webpack里面的一个小型库,它容许你自定义一个事件,并在触发后访问到触发者的上下文。固然他也支持异步触发,多个事件同步,异步触发。本次实现用的是较早的v0.1.9版,具体文档可查看tapable v0.19文档git
在webpack内使用,如SingleEntryPlugin中github
compiler.plugin("make",function(compilation,callback){ compilation.addEntry(this.context, new SingleEntryDependency({request: this.entry}), this.name, callback); })
在compiler内部触发。web
this.applyPluginsParallel('make',compilation, err => { /* do something */ })
解析入口文件时,经过EntryOptionPlugin解析entry类型并实例化SingleEntryPlugin, SingleEntryPlugin在调用compilation的addEntry函数开启编译。这种观察者模式的设计,解耦了compiler, compilation,并使它们提供的功能更加纯粹,进而增长扩展性。express
纵观整个打包过程,能够流程划分为四块。编程
接入plugin后,webpack对parse,resolve,build,writeSource等功能的大规模重构。
目前拆分模块为数组
经过exprima将源码解析为AST树,并拆分statements,以及expression直至Identifier基础模块。缓存
case 'CallExpression': //do something this.applyPluginsBailResult('call ' + calleeName, expression); //do something break; case 'MemberExpression': //do something this.applyPluginsBailResult('expression ' + memberName, expression); //do something break; case 'Identifier': //do something this.applyPluginsBailResult('expression ' + idenName, expression); //do something break;
this.plugin('evaluate Literal', (expr) => {}) this.plugin('evaluate ArrayExpression', (expr) => {}) this.plugin('evaluate CallExpression', (expr) => {}) ...
如须要解析require("a"),require.ensure(["b"],function(){})的时候,注册plugin去订阅"call require",以及"call require.ensure",再在回调函数调用evaluateExpression解析expression。数据结构
封装在enhanced-resolve库,提供异步解析文件路径,以及可配置的filestream能力。在webpack用于缓存文件流以及如下三种类型模块的路径解析。app
用法如
ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem, resolveToContext: true }, options.resolve));
具体配置可去查看github文档
子类有NormalModuleFactory,ContextModuleFactory。经常使用的NormalModuleFactory功能以下
这里主要是使用async库的parallel函数并行的解析loaders和module的路径,并整合运行结果。
async.parallel([ (callback) => { this.requestResolverArray( context, loader, resolver, callback) }, (callback) => { resolver.normal.resolve({}, context, req, function (err, result) { callback(null, result) }); }, ], (err, result) => { let loaders = result[0]; const resource = result[1]; //do something })
async模块是一整套异步编程的解决方案。async官方文档
一个编译好的module对象包含modules依赖ModuleDependency和blocks依赖RequireEnsureDependenciesBlock,loaders,源码_source,其数据结构以下:
{ chunks: [], id: null, parser: Tapable { _plugins: { 'evaluate Literal': [Array], 'evaluate ArrayExpression': [Array], 'evaluate CallExpression': [Array], 'call require': [Array], 'call require:commonjs:item': [Array], 'call require.ensure': [Array] }, options: {}, scope: { declarations: [] }, state: { current: [Circular], module: [Circular] }, _currentPluginApply: undefined }, fileDependencies: [ '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js' ], dependencies: [ ModuleDependency { request: './module!d', range: [Array], class: [Function: ModuleDependency], type: 'cms require' }, ModuleDependency { request: './assets/test', range: [Array], class: [Function: ModuleDependency], type: 'cms require' } ], blocks: [ RequireEnsureDependenciesBlock { blocks: [], dependencies: [Array], requires: [Array], chunkName: '', beforeRange: [Array], afterRange: [Array] } ], loaders: [], request: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js', fileName: 'a.js', requires: [ [ 0, 7 ], [ 23, 30 ] ], context: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example', built: true, _source: RawSource { _result: { source: 'require(\'./module!d\');\nrequire(\'./assets/test\');\nrequire.ensure([\'./e\',\'./b\'], function () {\n console.log(1)\n console.log(1)\n console.log(1)\n console.log(1)\n require(\'./m\');\n require(\'./e\');\n});\n' }, _source: 'require(\'./module!d\');\nrequire(\'./assets/test\');\nrequire.ensure([\'./e\',\'./b\'], function () {\n console.log(1)\n console.log(1)\n console.log(1)\n console.log(1)\n require(\'./m\');\n require(\'./e\');\n});\n' } }
一个典型的含有切割文件的多入口entry的assets对象数据结构以下:
assets: { '0.bundle.js': Chunk { name: '', parents: [Array], modules: [Array], id: 0, source: [Object] }, 'main.bundle.js': Chunk { name: 'main', parents: [], modules: [Array], id: 1, entry: true, chunks: [Array], blocks: true, source: [Object] }, 'multiple.bundle.js': Chunk { name: 'multiple', parents: [], modules: [Array], id: 2, entry: true, chunks: [Array], source: [Object] } }
考虑到多入口entry的可能,make调用的是并行异步事件
this.applyPluginsParallel('make', compilation, err => { //do something compilation.seal(err=>{}) //do something }
本人的简易版webpack实现simple-webpack
相信你们都有设计过业务/开源代码,不少状况是越日后写,越难维护。一次次的定制化的需求,将原有的设计改的支离破碎。这个时候能够试试借鉴webpak的思想,充分思考并抽象出稳定的基础模块,划分生命周期,将模块之间的业务逻辑,特殊需求交由插件去解决。
完。