.15-浅析webpack源码之WebpackOptionsApply模块-…
2018-06-24 01:07:24来源:未知 阅读 ()
总体过了一下后面的流程,发现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中。
总结如图:
完结!
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 浅析XMLHttpRequest的缓存问题 2020-02-25
- webpack打包配置禁止html标签全部转为小写 2019-08-14
- 入门webpack,看这篇就够了 2019-08-14
- image-webpack-loader包安装报错解决 2019-08-14
- Vue学习之webpack中使用vue(十七) 2019-08-14
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash