.23-浅析webpack源码之事件流compilation(1)
正式开始跑编译,依次解析,首先是:
compiler.apply( new JsonpTemplatePlugin(options.output), // start new FunctionModulePlugin(options.output), new NodeSourcePlugin(options.node), new LoaderTargetPlugin(options.target) );
流程图如下:
这里是第一个compilation事件注入的地方,注入代码如下:
compiler.plugin("compilation", (compilation) => { compilation.moduleTemplate.requestShortener = this.requestShortener || new RequestShortener(compiler.context); compilation.moduleTemplate.apply(new FunctionModuleTemplatePlugin()); });
这里的requestShortener为FunctionModulePlugin的第二个参数,没有传所以是undefined。
options.output为传入的output参数,但是这里并没有用到,而是传入了compiler.context,如果没有传默认为命令执行路径。
RequestShortener
首先看第一个,源码简化如下:
"use strict"; const path = require("path"); // 匹配反斜杠 => \ const NORMALIZE_SLASH_DIRECTION_REGEXP = /\\/g; // 匹配特殊字符 const PATH_CHARS_REGEXP = /[-[\]{}()*+?.,\\^$|#\s]/g; // 匹配正反斜杠 => /\ const SEPARATOR_REGEXP = /[/\\]$/; // 匹配以'!'开头或结尾 const FRONT_OR_BACK_BANG_REGEXP = /^!|!$/g; // 匹配 /index.js const INDEX_JS_REGEXP = /\/index.js(!|\?|\(query\))/g; // 将反斜杠替换为正斜杠 const normalizeBackSlashDirection = (request) => { return request.replace(NORMALIZE_SLASH_DIRECTION_REGEXP, "/"); }; // 将路径中特殊字符转义 例如 - => \- // 返回一个正则 const createRegExpForPath = (path) => { const regexpTypePartial = path.replace(PATH_CHARS_REGEXP, "\\$&"); return new RegExp(`(^|!)${regexpTypePartial}`, "g"); }; class RequestShortener { constructor(directory) { /**/ } shorten(request) { /**/ } } module.exports = RequestShortener;
可以看到都是对路径做处理,正则都比较简单,接下来看一下构造函数,其中传进来的directory为命令执行上下文。
class RequestShortener { constructor(directory) { // 斜杠转换 directory = normalizeBackSlashDirection(directory); // 没看懂啥用 if (SEPARATOR_REGEXP.test(directory)) directory = directory.substr(0, directory.length - 1); // 上下文路径正则 // /(^|!)转义后的路径/g if (directory) { this.currentDirectoryRegExp = createRegExpForPath(directory); } // 返回目录名 const dirname = path.dirname(directory); // 这里也不懂干啥用的 const endsWithSeperator = SEPARATOR_REGEXP.test(dirname); const parentDirectory = endsWithSeperator ? dirname.substr(0, dirname.length - 1) : dirname; // 目录正则 if (parentDirectory && parentDirectory !== directory) { this.parentDirectoryRegExp = createRegExpForPath(parentDirectory); } // .....\node_modules\webpack\lib if (__dirname.length >= 2) { // webpack的目录 const buildins = normalizeBackSlashDirection(path.join(__dirname, "..")); // 目录检测 const buildinsAsModule = this.currentDirectoryRegExp && this.currentDirectoryRegExp.test(buildins); // false this.buildinsAsModule = buildinsAsModule; // 生成webpack目录路径正则 this.buildinsRegExp = createRegExpForPath(buildins); } } shorten(request) { /**/ } }
主要是生成了3个目录匹配正则,上下文、上下文目录、webpack主目录三个。
这里上下文一般不会是webpack的目录,所以这个buildingsAsModule理论上都是flase。
再简单看一下原型方法shorten:
class RequestShortener { constructor(directory) { /**/ } shorten(request) { if (!request) return request; // 转化路径斜杠 request = normalizeBackSlashDirection(request); // false if (this.buildinsAsModule && this.buildinsRegExp) request = request.replace(this.buildinsRegExp, "!(webpack)"); // 将上下文转换为!. if (this.currentDirectoryRegExp) request = request.replace(this.currentDirectoryRegExp, "!."); // 将上下文目录转换为!.. if (this.parentDirectoryRegExp) request = request.replace(this.parentDirectoryRegExp, "!.."); // false if (!this.buildinsAsModule && this.buildinsRegExp) request = request.replace(this.buildinsRegExp, "!(webpack)"); // 把路径中的index.js去了 留下参数 // /index.js?a=1 => ?a=1 request = request.replace(INDEX_JS_REGEXP, "$1"); // 把头尾的!去了 return request.replace(FRONT_OR_BACK_BANG_REGEXP, ""); } }
可以看出,这个方法将传入的路径根据上下文的目录进行简化,变成了相对路径,然后去掉了index.js。
FunctionModuleTemplatePlugin
这个模块没有实质性内容,主要是对compilation.moduleTemplate注入事件流,源码如下:
"use strict"; const ConcatSource = require("webpack-sources").ConcatSource; class FunctionModuleTemplatePlugin { apply(moduleTemplate) { moduleTemplate.plugin("render", function(moduleSource, module) { /**/ }); moduleTemplate.plugin("package", function(moduleSource, module) { /**/ }); moduleTemplate.plugin("hash", function(hash) { /**/ }); } } module.exports = FunctionModuleTemplatePlugin;
等触发的时候再回头看。
ConcatSource后面单独讲。
下面是第二个插件,源码整理如下:
class NodeSourcePlugin { constructor(options) { this.options = options; } apply(compiler) { const options = this.options; if (options === false) // allow single kill switch to turn off this plugin return; function getPathToModule(module, type) { /**/ } function addExpression(parser, name, module, type, suffix) { /**/ } compiler.plugin("compilation", function(compilation, params) { params.normalModuleFactory.plugin("parser", function(parser, parserOptions) { /**/ }); }); compiler.plugin("after-resolvers", (compiler) => { /**/ }); } };
可以看到,这里只是简单判断了是否关闭了node插件,然后在之前的params参数中的normalModuleFactory属性上注入了一个parser事件。
第三个插件就更简单了,如下:
class LoaderTargetPlugin { constructor(target) { this.target = target; } apply(compiler) { compiler.plugin("compilation", (compilation) => { // 这个完全不懂干啥的 compilation.plugin("normal-module-loader", (loaderContext) => loaderContext.target = this.target); }); } }
这个plugin目前根本看不出来有什么用。
总之,前三个compilation比较水,没有什么内容。
老子要日穿V8引擎