原文連接css
最近因爲用着html-webpack-plugin以爲很不爽,因而乎想本身動手寫一個插件。原覺得像gulp插件同樣半天上手一天寫完,但使人鬱悶的是徹底找不到相關的文章。一進官方文檔倒是被嚇傻了。首先是進入how to write a plugin看了一頁簡單的介紹。而後教程會告訴你,你須要去了解compiler和compilation這兩個對象,才能更好地寫webpack的插件,而後做者給了github的連接給你,讓你去看源代碼,我暈。不過幸虧最後給了一個plugins的API文檔,才讓我開發的過程當中稍微有點頭緒。html
how to write a plugin這個教程仍是能夠好好看看的,尤爲是那個simple example,它會教你在compilation的emit事件或以前,將你須要生成的文件放到webpack的compilation.assets裏,這樣就能夠藉助webpack的力量幫你生成文件,而不須要本身手動去寫fs.writeFileSync。node
主要就是這段代碼react
compilation.assets['filelist.md'] = { source: function() { return filelist; }, size: function() { return filelist.length; } };
首先,定義一個函數func,用戶設置的options基本就在這裏處理。webpack
其次,須要設一個func.prototype.apply函數。這個函數是提供給webpack運行時調用的。webpack會在這裏注入compiler對象。git
輸出complier對象,你會看到這一長串的內容,初步一看,我看出了兩大類(有補充的能夠告訴我)。一個webpack運行時的參數,例如_plugins,這些數組裏的函數應該是webpack內置的函數,用於在compiltion,this-compilation和should-emit事件觸發時調用的。另外一個是用戶寫在webpack.config.js裏的參數。隱約以爲這裏好多將來均可能會是webpack暴露給用戶的接口,使webpack的定製化功能更強大。github
Compiler { _plugins: { compilation: [ [Function], [Function], [Function], [Function] ], 'this-compilation': [ [Function: bound ] ], 'should-emit': [ [Function] ] }, outputPath: '', outputFileSystem: null, inputFileSystem: null, recordsInputPath: null, recordsOutputPath: null, records: {}, fileTimestamps: {}, contextTimestamps: {}, resolvers: { normal: Tapable { _plugins: {}, fileSystem: null }, loader: Tapable { _plugins: {}, fileSystem: null }, context: Tapable { _plugins: {}, fileSystem: null } }, parser: Parser { _plugins: { 'evaluate Literal': [Object], 'evaluate LogicalExpression': [Object], 'evaluate BinaryExpression': [Object], 'evaluate UnaryExpression': [Object], 'evaluate typeof undefined': [Object], 'evaluate Identifier': [Object], 'evaluate MemberExpression': [Object], 'evaluate CallExpression': [Object], 'evaluate CallExpression .replace': [Object], 'evaluate CallExpression .substr': [Object], 'evaluate CallExpression .substring': [Object], 'evaluate CallExpression .split': [Object], 'evaluate ConditionalExpression': [Object], 'evaluate ArrayExpression': [Object], 'expression Spinner': [Object], 'expression ScreenMod': [Object] }, options: undefined }, options: { entry: { 'index': '/Users/mac/web/src/page/index/main.js' }, output: { publicPath: '/homework/features/model/', path: '/Users/mac/web/dist', filename: 'js/[name].js', libraryTarget: 'var', sourceMapFilename: '[file].map[query]', hotUpdateChunkFilename: '[id].[hash].hot-update.js', hotUpdateMainFilename: '[hash].hot-update.json', crossOriginLoading: false, hashFunction: 'md5', hashDigest: 'hex', hashDigestLength: 20, sourcePrefix: '\t', devtoolLineToLine: false }, externals: { react: 'React' }, module: { loaders: [Object], unknownContextRequest: '.', unknownContextRecursive: true, unknownContextRegExp: /^\.\/.*$/, unknownContextCritical: true, exprContextRequest: '.', exprContextRegExp: /^\.\/.*$/, exprContextRecursive: true, exprContextCritical: true, wrappedContextRegExp: /.*/, wrappedContextRecursive: true, wrappedContextCritical: false }, resolve: { extensions: [Object], alias: [Object], fastUnsafe: [], packageAlias: 'browser', modulesDirectories: [Object], packageMains: [Object] }, plugins: [ [Object], [Object], [Object], [Object], NoErrorsPlugin {}, [Object], [Object] ], devServer: { port: 8081, contentBase: './dist' }, context: '/Users/mac/web/', watch: true, debug: false, devtool: false, cache: true, target: 'web', node: { console: false, process: true, global: true, setImmediate: true, __filename: 'mock', __dirname: 'mock' }, resolveLoader: { fastUnsafe: [], alias: {}, modulesDirectories: [Object], packageMains: [Object], extensions: [Object], moduleTemplates: [Object] }, optimize: { occurenceOrderPreferEntry: true } }, context: '/Users/mac/web/' }
除此之外,compiler還有一些如run, watch-run的方法以及compilation, normal-module-factory對象。我目前用到的,主要是compilation。其它的等下一篇有機會再說。web
對比起compiler還有compiler.plugin函數。這個至關因而插件能夠進行處理的webpack的運行中的一些任務點,webpack就是完成一個又一個任務而完成整個打包構建過程的。如make是最開始的起點, complie就是編譯任務點,after-complie是編譯完成,emit是即將準備生成文件,after-emit是生成文件以後等等,前面幾個都是比較生動形象的任務點。express
至於compilation,它繼承於compiler,因此能拿到一切compiler的內容(因此你也會看到webpack的options),並且也有plugin函數來接入任務點。在compiler.plugin('emit')任務點輸出compilation,會獲得大體下面的對象數據,由於實在太長,我只保留了最重要的assets部份:json
assetsCompilation { assets: { 'js/index/main.js': CachedSource { _source: [Object], _cachedSource: undefined, _cachedSize: undefined, _cachedMaps: {} } }, errors: [], warnings: [], children: [], dependencyFactories: ArrayMap { keys: [ [Object], [Function: MultiEntryDependency], [Function: SingleEntryDependency], [Function: LoaderDependency], [Object], [Function: ContextElementDependency], values: [ NullFactory {}, [Object], NullFactory {} ] }, dependencyTemplates: ArrayMap { keys: [ [Object], [Object], [Object] ], values: [ ConstDependencyTemplate {}, RequireIncludeDependencyTemplate {}, NullDependencyTemplate {}, RequireEnsureDependencyTemplate {}, ModuleDependencyTemplateAsRequireId {}, AMDRequireDependencyTemplate {}, ModuleDependencyTemplateAsRequireId {}, AMDRequireArrayDependencyTemplate {}, ContextDependencyTemplateAsRequireCall {}, AMDRequireDependencyTemplate {}, LocalModuleDependencyTemplate {}, ModuleDependencyTemplateAsId {}, ContextDependencyTemplateAsRequireCall {}, ModuleDependencyTemplateAsId {}, ContextDependencyTemplateAsId {}, RequireResolveHeaderDependencyTemplate {}, RequireHeaderDependencyTemplate {} ] }, fileTimestamps: {}, contextTimestamps: {}, name: undefined, _currentPluginApply: undefined, fullHash: 'f4030c2aeb811dd6c345ea11a92f4f57', hash: 'f4030c2aeb811dd6c345', fileDependencies: [ '/Users/mac/web/src/js/index/main.js' ], contextDependencies: [], missingDependencies: [] }
assets部份重要是由於若是你想借助webpack幫你生成文件,你須要像官方教程how to write a plugin在assets上寫上對應的文件信息。
除此之外,compilation.getStats()這個函數也至關重要,能獲得生產文件以及chunkhash的一些信息,以下:
assets{ errors: [], warnings: [], version: '1.12.9', hash: '5a5c71cb2accb8970bc3', publicPath: 'xxxxxxxxxx', assetsByChunkName: { 'index/main': 'js/index/index-4c0c16.js' }, assets: [ { name: 'js/index/index-4c0c16.js', size: 453, chunks: [Object], chunkNames: [Object], emitted: undefined } ], chunks: [ { id: 0, rendered: true, initial: true, entry: true, extraAsync: false, size: 221, names: [Object], files: [Object], hash: '4c0c16e8af4d497b90ad', parents: [], origins: [Object] } ], modules: [ { id: 0, identifier: 'multi index/main', name: 'multi index/main', index: 0, index2: 1, size: 28, cacheable: true, built: true, optional: false, prefetched: false, chunks: [Object], assets: [], issuer: null, profile: undefined, failed: false, errors: 0, warnings: 0, reasons: [] }, { id: 1, identifier: '/Users/mac/web/node_modules/babel-loader/index.js?presets[]=es2015&presets[]=react!/Users/mac/web/src/js/main/index.js', name: './src/js/index/main.js', index: 1, index2: 0, size: 193, cacheable: true, built: true, optional: false, prefetched: false, chunks: [Object], assets: [], issuer: 'multi index/main', profile: undefined, failed: false, errors: 0, warnings: 0, reasons: [Object], source: '' // 具體文件內容} ], filteredModules: 0, children: [] }
這裏的chunks數組裏,是對應會生成的文件,以及md5以後的文件名和路徑,裏面還有文件對應的chunkhash(每一個文件不一樣,但若是你使用ExtractTextPlugin將css文件獨立出來的話,它會與require它的js入口文件共享相同的chunkhash),而assets.hash則是統一的hash,對每一個文件都同樣。值得關注的是chunks裏的每一個文件,都有source這一項目,提供給開發者直接拿到源文件內容(主要是js,若是是css且使用ExtractTextPlugin,則請自行打印出來參考)。
接下來,會以最近我寫的一個插件html-res-webpack-plugin做爲引子,來介紹基本的寫插件原理。插件的邏輯就寫在index.js裏。
首先,將用戶輸入的參數在定好的函數中處理,HtmlResWebpackPlugin。
而後,新增apply函數,在裏面寫好插件須要切入的webpack任務點。目前HtmlResWebpackPlugin插件只用到emit這個任務點,其它幾個僅做爲演示。
第三步,調用addFileToWebpackAsset方法,寫compilation.assets,藉助webpack生成html文件。
第四步,在開發模式下(isWatch = true),直接生成html,但在生產模式下(isWatch = true),插件會開始對靜態資源(js,css)進行md5或者內聯。
第五步,調用findAssets方法是爲了經過compilation.getStats()拿到的數據,去匹配對應的靜態資源,還有找到對應的哈希(是chunkhash仍是hash)。
最六步,調用addAssets方法,對靜態資源分別作內聯或者md5文件處理。內聯資源的函數是inlineRes,你會看到我使用了compilation.assets[hashFile].source() 及 compilation.assets[hashFile].children[1]._value。前者是針對於js的,後者是針對使用了ExtractTextPlugin的css資源。
最後一步,便是內聯和md5完成後,再更新一下compilation.assets中對應生成html的source內容,才能正確地生成內聯和md5後的內容。
有興趣能夠試用一下html-res-webpack-plugin這個插件(爲何要寫一個新的html生成插件,我在readme裏寫了,此處不贅述),看看有哪些用得不爽之處。目前只是初版,還不適合用於生產環境。但願第二版的時候能適用於更多的場景,以及性能更好。到是,我也會寫第二篇插件開發文章,將本文還沒提到的地方一一補充完整。也歡迎你們在這裏發貼,或者指出本人的謬誤之處。