總體過了一下后面的流程,發現Compiler模塊確實不適合單獨講解,這里繼續講解后面的代碼:
compiler.options = new WebpackOptionsApply().process(options, compiler);
這行代碼與之前設置options默認值非常相似,但是復雜程度根本不是一個次元的。
這一節只能簡單的看一眼內部到底有多少東西,整理后源碼如下:
"use strict"; const OptionsApply = require("./OptionsApply"); // ...巨量插件引入 class WebpackOptionsApply extends OptionsApply { constructor() { super(); } process(options, compiler) { let ExternalsPlugin; compiler.outputPath = options.output.path; compiler.recordsInputPath = options.recordsInputPath || options.recordsPath; compiler.recordsOutputPath = options.recordsOutputPath || options.recordsPath; compiler.name = options.name; compiler.dependencies = options.dependencies; // 在默認參數配置中被設置為web if (typeof options.target === "string") { let JsonpTemplatePlugin; let NodeSourcePlugin; let NodeTargetPlugin; let NodeTemplatePlugin; switch (options.target) { case "web": JsonpTemplatePlugin = require("./JsonpTemplatePlugin"); NodeSourcePlugin = require("./node/NodeSourcePlugin"); compiler.apply( new JsonpTemplatePlugin(options.output), new FunctionModulePlugin(options.output), new NodeSourcePlugin(options.node), new LoaderTargetPlugin(options.target) ); break; // other case... default: throw new Error("Unsupported target '" + options.target + "'."); } } else if (options.target !== false) { options.target(compiler); } else { throw new Error("Unsupported target '" + options.target + "'."); } // options.output.library參數處理 if (options.output.library || options.output.libraryTarget !== "var") { /**/ } // options.output.externals參數處理 if (options.externals) { /**/ } let noSources; let legacy; let modern; let comment; // options.devtool => sourcemap || source-map if (options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)) { /**/ } // options.devtool => eval else if (options.devtool && options.devtool.indexOf("eval") >= 0) { /**/ } // 加載模塊並觸發entry-option事件流 compiler.apply(new EntryOptionPlugin()); compiler.applyPluginsBailResult("entry-option", options.context, options.entry); // 瘋狂加載插件 compiler.apply( /**/ ); // options.performance參數處理 if (options.performance) { /**/ } // 繼續加載插件 compiler.apply(new TemplatedPathPlugin()); compiler.apply(new RecordIdsPlugin()); compiler.apply(new WarnCaseSensitiveModulesPlugin()); // options.performance參數處理 if (options.cache) { /**/ } // 觸發after-plugins compiler.applyPlugins("after-plugins", compiler); if (!compiler.inputFileSystem) throw new Error("No input filesystem provided"); // 給compiler.resolvers設置值 compiler.resolvers.normal = ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem }, options.resolve)); compiler.resolvers.context = ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem, resolveToContext: true }, options.resolve)); compiler.resolvers.loader = ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem }, options.resolveLoader)); // 觸發after-resolvers事件流 compiler.applyPlugins("after-resolvers", compiler); return options; } } module.exports = WebpackOptionsApply;
這個模塊除去父類引入,其余插件光頂部引入就有34個,簡直就是插件之王。
略去具體插件內容,先看流程,父類其實是個接口,啥都沒有:
"use strict";
class OptionsApply {
process(options, compiler) {}
}
module.exports = OptionsApply;
接下來是一個唯一的主方法process,總結下流程依次為:
1、根據options.target加載對應的插件,如果配置文件沒有配置該參數,則在WebpackOptionsDefaulter模塊會被自動初始化為web。
2、處理options.output.library、options.output.externals參數
3、處理options.devtool參數
4、加載EntryOptionPlugin插件並觸發entry-option的事件流
5、加載大量插件
6、處理options.performance參數
7、加載TemplatePathPlugin、RecordIdPlugin、WarnCaseSensitiveModulesPlugin插件
8、觸發after-plugins事件流
9、設置compiler.resolvers的值
10、觸發after-resolvers事件流
如果按類型分,其實只有兩種:加載插件,觸發事件流。
事件流的觸發類似於vue源碼里的鈎子函數,到特定的階段觸發對應的方法,這個思想在Java的數據結構源碼中也被普通應用。
模塊中的參數處理如果該參數比較常用,那么就進行分析,其余不太常用的就先跳過,按順序依次講解。
這里的options經過默認參數模塊的加工,豐富后如下:
{ "entry": "./input.js", "output": { "filename": "output.js", "chunkFilename": "[id].output.js", "library": "", "hotUpdateFunction": "webpackHotUpdate", "jsonpFunction": "webpackJsonp", "libraryTarget": "var", "path": "D:\\workspace\\doc", "sourceMapFilename": "[file].map[query]", "hotUpdateChunkFilename": "[id].[hash].hot-update.js", "hotUpdateMainFilename": "[hash].hot-update.json", "crossOriginLoading": false, "chunkLoadTimeout": 120000, "hashFunction": "md5", "hashDigest": "hex", "hashDigestLength": 20, "devtoolLineToLine": false, "strictModuleExceptionHandling": false }, "context": "D:\\workspace\\doc", "devtool": false, "cache": true, "target": "web", "module": { "unknownContextRequest": ".", "unknownContextRegExp": false, "unknownContextRecursive": true, "unknownContextCritical": true, "exprContextRequest": ".", "exprContextRegExp": false, "exprContextRecursive": true, "exprContextCritical": true, "wrappedContextRegExp": {}, "wrappedContextRecursive": true, "wrappedContextCritical": false, "strictExportPresence": false, "strictThisContextOnImports": false, "unsafeCache": true }, "node": { "console": false, "process": true, "global": true, "Buffer": true, "setImmediate": true, "__filename": "mock", "__dirname": "mock" }, "performance": { "maxAssetSize": 250000, "maxEntrypointSize": 250000, "hints": false }, "resolve": { "unsafeCache": true, "modules": ["node_modules"], "extensions": [".js", ".json"], "mainFiles": ["index"], "aliasFields": ["browser"], "mainFields": ["browser", "module", "main"], "cacheWithContext": false }, "resolveLoader": { "unsafeCache": true, "mainFields": ["loader", "main"], "extensions": [".js", ".json"], "mainFiles": ["index"], "cacheWithContext": false } }
除去entry與output.filename,其余的參數全部是填充上去的,因為后面的流程會檢測參數,所以這里先列出來。
這一節先這樣,具體內容后面進行詳細講解。
發現這節沒啥營養,填充下內容,將事件流的plugin總覽一下,先不做深入分析,在編譯運行階段在做講解。
依次看每一段代碼注入了哪些事件流,絕不深究。
options.target參數
這個一般不會去設置,默認會被置為web,源碼中進入下列case:
switch (options.target) { case "web": JsonpTemplatePlugin = require("./JsonpTemplatePlugin"); NodeSourcePlugin = require("./node/NodeSourcePlugin"); compiler.apply( new JsonpTemplatePlugin(options.output), new FunctionModulePlugin(options.output), new NodeSourcePlugin(options.node), new LoaderTargetPlugin(options.target) ); break; }
這里的apply我在剛開始有個誤區,以為是每個函數必帶的apply,后來其實調用的是父類tapable的apply,而該函數源碼為:
Tapable.prototype.apply = function apply(...fns) { // 遍歷所有參數並執行 for (var i = 0; i < fns.length; i++) { fns[i].apply(this); } };
又是個apply,這樣意思就明白了,compiler.apply是調用每一個函數的apply方法。
class JsonpTemplatePlugin { apply(compiler) { compiler.plugin("this-compilation", (compilation) => { /**/ }); } }
若自定義插件未plugin該事件流,此次為第一次this-compilation。
class FunctionModulePlugin { constructor(options, requestShortener) { /**/ } apply(compiler) { compiler.plugin("compilation", (compilation) => { /**/ }); } }
第一次compilation。
LoaderTargetPlugin插件 => compilation
options.output.library參數、options.externals參數
這兩個參數這里不做講解。
options.devtool參數
在vue-cli構建的腳手架中,開發者模式該參數為'eval-source-map',在生產模式下則為'#source-map'。
這里需要看一下源碼是如何解析這個字符串:
if (options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)) { // 用indexof判斷參數的真假值 // inline,hidden,cheap,moduleMaps,nosources const evalWrapped = options.devtool.indexOf("eval") >= 0; legacy = options.devtool.indexOf("@") >= 0; modern = options.devtool.indexOf("#") >= 0; // eval-source-map => null // #source-map => "\n//# source" + "MappingURL=[url]" comment = legacy && modern ? "\n/*\n//@ source" + "MappingURL=[url]\n//# source" + "MappingURL=[url]\n*/" : legacy ? "\n/*\n//@ source" + "MappingURL=[url]\n*/" : modern ? "\n//# source" + "MappingURL=[url]" : null; let Plugin = evalWrapped ? EvalSourceMapDevToolPlugin : SourceMapDevToolPlugin; compiler.apply(new Plugin({ filename: inline ? null : options.output.sourceMapFilename, moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate, fallbackModuleFilenameTemplate: options.output.devtoolFallbackModuleFilenameTemplate, append: hidden ? false : comment, module: moduleMaps ? true : cheap ? false : true, columns: cheap ? false : true, lineToLine: options.output.devtoolLineToLine, noSources: noSources, })) };
完全沒有格式可言,全部通過indexOf判斷標記參數的真假值,所以說eval-source-map跟eeeeeeevallllll-source-map是一樣的。
兩種情況下,加載的插件一樣,但是append參數不一樣,如下:
EvalSourceMapDevToolPlugin插件 => compilation
class EvalSourceMapDevToolPlugin { constructor(options) { /**/ } apply(compiler) { const options = this.options; compiler.plugin("compilation", (compilation) => { /**/ }); } }
EntryOptionPlugin插件 => entry-option
module.exports = class EntryOptionPlugin { apply(compiler) { compiler.plugin("entry-option", (context, entry) => { /**/ }); } };
在注入事件流后,這里會立即進行調用,代碼如下:
compiler.applyPluginsBailResult("entry-option", options.context, options.entry);
接下來是海量插件引入,不寫垃圾代碼了,直接看看源碼就行:
compiler.apply( new CompatibilityPlugin(), new HarmonyModulesPlugin(options.module), new AMDPlugin(options.module, options.amd || {}), new CommonJsPlugin(options.module), new LoaderPlugin(), new NodeStuffPlugin(options.node), new RequireJsStuffPlugin(), new APIPlugin(), new ConstPlugin(), new UseStrictPlugin(), new RequireIncludePlugin(), new RequireEnsurePlugin(), new RequireContextPlugin(options.resolve.modules, options.resolve.extensions, options.resolve.mainFiles), new ImportPlugin(options.module), new SystemPlugin(options.module) ); compiler.apply( new EnsureChunkConditionsPlugin(), new RemoveParentModulesPlugin(), new RemoveEmptyChunksPlugin(), new MergeDuplicateChunksPlugin(), new FlagIncludedChunksPlugin(), new OccurrenceOrderPlugin(true), new FlagDependencyExportsPlugin(), new FlagDependencyUsagePlugin() );
簡直可怕。
options.performance參數
暫不講解。
然后是三個插件的加載:
TemplatedPathPlugin、RecordIdsPlugin、WarnCaseSensitiveModulesPlugin => compilation
optison.cache參數
該參數會被默認設置為true,所以該插件是被默認加載。
class CachePlugin { constructor(cache) { this.cache = cache || {}; this.FS_ACCURENCY = 2000; } apply(compiler) { if (Array.isArray(compiler.compilers)) { compiler.compilers.forEach((c, idx) => { c.apply(new CachePlugin(this.cache[idx] = this.cache[idx] || {})); }); } else { const registerCacheToCompiler = (compiler, cache) => { compiler.plugin("this-compilation", compilation => { /**/ }); }; registerCacheToCompiler(compiler, this.cache); compiler.plugin("watch-run", (compiler, callback) => { /**/ }); compiler.plugin("run", (compiler, callback) => { /**/ }); compiler.plugin("after-compile", function(compilation, callback) { /**/ }); } } }
這個插件比較麻煩,依次注入了this-compilation、watch-run、run、after-compile事件流。
在所有插件就加載完畢后,會執行after-plugins事件流並給compiler.resolvers賦值,然后執行after-resolvers事件流。
這樣,所有的插件都打包到了compiler當中,其中大部分的事件流都集中在了compilation中。
總結如圖:
完結!