插件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的思想,充分思考並抽象出穩定的基礎模塊,劃分生命週期,將模塊之間的業務邏輯,特殊需求交由插件去解決。
完。