总体过了一下后面的流程,发现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方法。

JsonpTemplatePlugin插件 => this-compilation

class JsonpTemplatePlugin {
apply(compiler) {
compiler.plugin("this-compilation", (compilation) => { /**/ });
}
}

  若自定义插件未plugin该事件流,此次为第一次this-compilation。

FunctionModulePlugin插件 => compilation

class FunctionModulePlugin {
constructor(options, requestShortener) { /**/ }
apply(compiler) {
compiler.plugin("compilation", (compilation) => { /**/ });
}
}

  第一次compilation。

NodeSourcePlugin插件 => compilation、after-resolvers

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中。

  总结如图:

  完结!

.15-浅析webpack源码之WebpackOptionsApply模块-plugin事件流总览的更多相关文章

  1. .6-浅析webpack源码之validateSchema模块

    validateSchema模块 首先来看错误检测: const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchem ...

  2. .4-浅析webpack源码之convert-argv模块

    上一节看了一眼预编译的总体代码,这一节分析convert-argv模块. 这个模块主要是对命令参数的解析,也是yargs框架的核心用处. 生成默认配置文件名数组 module.exports = fu ...

  3. .9-浅析webpack源码之NodeEnvironmentPlugin模块总览

    介绍Compiler的构造比较无趣,不如先过后面的,在用到compiler的时候再做讲解. 这一节主要讲这行代码: // 不管这里 compiler = new Compiler(); compile ...

  4. .14-浅析webpack源码之Watchpack模块

    解决掉了最头疼的DirectoryWatcher内部实现,这一节可以结束NodeWatchFileSystem模块. 关于watch的应用场景,仔细思考了下,这不就是热重载的核心嘛. 首先是监视文件, ...

  5. .13-浅析webpack源码之WatcherManager模块

    从模块流可以看出,这个NodeWatchFileSystem模块非常深,这里暂时不会深入到chokidar模块,有点太偏离本系列文章了,从WatcherManager开始讲解. 流程如图: 源码非常简 ...

  6. .12-浅析webpack源码之NodeWatchFileSystem模块总览

    剩下一个watch模块,这个模块比较深,先大概过一下整体涉及内容再分部讲解. 流程图如下: NodeWatchFileSystem const Watchpack = require("wa ...

  7. .11-浅析webpack源码之Storage模块

    至此已完成NodeJsInputFileSysten模块的讲解,下一步就是实际实用的模块: compiler.inputFileSystem = new CachedInputFileSystem(n ...

  8. .10-浅析webpack源码之graceful-fs模块

    在cachedInput.output.watch三大文件系统中,output非常简单,没有必要讲,其余两个模块依赖于input模块,而input主要是引用了graceful-fs的部分API,所以这 ...

  9. .7-浅析webpack源码之WebpackOptionsDefaulter模块

    WebpackOptionsDefaulter模块 通过参数检测后,会根据单/多配置进行处理,本文基于单配置,所以会进行到如下代码: if (Array.isArray(options)) { com ...

随机推荐

  1. PyCharm选择性忽略PEP8代码风格警告信息

    用了几天的PyCharm,发现确实在编写Python代码上非常好用,但有一点体验不太好,就是代码编写时要按照PEP8代码风格编写,不然会有波浪线的警告信息.解决方法如下: 方法一: 将鼠标移到提示的地 ...

  2. _ZNote_Qt_定时器的总结

    Qt中实现定时器有两种方法. 一种是使用QObject类定时器;一种是使用QTimer类定时器.(定时器的精度依赖于操作系统和硬件,大多数平台支持20ms) 1,QObject类定时器. 通过QObj ...

  3. Beta冲刺 (6/7)

    Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1:(组长)柯奇豪 过去两天完成了哪些任务 部分代码的整合 编辑及标注的提交操 ...

  4. 基于esp32的IIC通讯

    本文源码地址在:http://download.csdn.net/download/noticeable/9962029 IIC 通讯应该是当代比较常用的几种通讯方式之一,其无需特殊的IO接口,连线方 ...

  5. Ngui分辨率适配

    必备知识点 1.分辨率适配必然是Orthographic Camera 2.Camera下对应的“Size”(图1)属性大小的理解:当前摄像机高度 = Size * 2 * UnityUnit(Uni ...

  6. js 基本

    JavaScrip组成:1.ECMAScrip --核心2.DOM 文档对象模型3.BOOM 浏览器对象模型 JavaScrip写法分类:1.内联式写在标签内以属性为表现:2.内嵌式以script标签 ...

  7. 用Python进行有进度条的π计算

    1.tqdm是一个强大的终端进度条工具,我利用pip获取tqdm函数库. 2编写代码 2.1进行π的计算 from random import random from math import sqrt ...

  8. Qt之实现360安全卫士主界面代码开源

    匆匆一年又过去了,总结去年一年的节奏就是忙爆了:生活忙.工作忙,值得庆幸的是没有瞎忙:今天打开博客园查看我的博客,才发现几乎差不多一年时间没写博客了:博客文章就是记忆,就是曾经努力过的见证,感谢博客园 ...

  9. linux内核中GNU C __attribute__ 机制的实用

    很多东西,只看看是不行的,要想深入的去了解一个东西,一定要去不断地学习,实践,反思. 说白了就是要去打磨. 在linux中,最近遇到了这样一个定义: int board_usb_init(int in ...

  10. python中两种栈实现方式的性能对比

    在计算机的世界中,同一个问题,使用不同的数据结构和算法实现,所使用的资源有很大差别 为了方便量化python中算法的资源消耗,对性能做测试非常有必要,这里针对stack做了python语言 下的性能分 ...