.36-浅析webpack源码之Parser类
眼看webpack4都出了,我还在撸3的源码,真的是捉急啊……
不过现在只是beta版本,等出稳定版本后跑跑4的源码去。
之前漏了一个东西没有讲,如下:
asyncLib.parallel([/**/], (err, results) => {
if (err) return callback(err);
loaders = results[0].concat(loaders, results[1], results[2]);
process.nextTick(() => {
callback(null, {
context: context,
request: loaders.map(loaderToIdent).concat([resource]).join("!"),
dependencies: data.dependencies,
userRequest,
rawRequest: request,
loaders,
resource,
resourceResolveData,
// 这个东西
parser: this.getParser(settings.parser)
});
});
});
在解析完入口文件路径与JS文件对应的babel-loader路径后,返回的包装对象多了一个parser属性,看似简单,实则麻烦的要死。
这里的参数settings.parser为undefined,来源如下:
const settings = {};
const useLoadersPost = [];
const useLoaders = [];
const useLoadersPre = [];
result.forEach(r => {
if (r.type === "use") {
// ...
} else {
// parser参数
settings[r.type] = r.value;
}
});
这里的参数来源于modules.rules的parser参数,一般情况下不会传吧,反正vue-cli中没有……
因此这里的parserOptions参数为undefined,直接看原型方法getParser:
getParser(parserOptions) {
// 默认键
let ident = "null";
// 检测参数
if (parserOptions) {
if (parserOptions.ident)
ident = parserOptions.ident;
else
ident = JSON.stringify(parserOptions);
}
// 尝试获取缓存
const parser = this.parserCache[ident];
if (parser)
return parser;
// 创建新的parser并设置缓存
return this.parserCache[ident] = this.createParser(parserOptions);
}
老套的尝试获取对应键的缓存值,如果不存在就新建一个,所有参数为undefined的默认键为null字符串。
createParser(parserOptions) {
// new一个Parser对象
const parser = new Parser();
// 触发事件流
this.applyPlugins2("parser", parser, parserOptions || {});
// 返回parser
return parser;
}
这个方法简单易懂,就是生成一个新的parser,但是每一行代码都是分量足足,让我联想到了vue源码中的parse方法……
Parser类
这东西就跟compiler、compilation对象一样,内容非常繁杂,干讲是没办法的,所以简单过一下构造,调用的时候跑内部方法。
class Parser extends Tapable {
constructor(options) {
super();
this.options = options;
this.scope = undefined;
this.state = undefined;
this.comments = undefined;
this.initializeEvaluating();
}
initializeEvaluating() {
this.plugin("evaluate Literal", expr => {
switch (typeof expr.value) {
case "number":
return new BasicEvaluatedExpression().setNumber(expr.value).setRange(expr.range);
case "string":
return new BasicEvaluatedExpression().setString(expr.value).setRange(expr.range);
case "boolean":
return new BasicEvaluatedExpression().setBoolean(expr.value).setRange(expr.range);
}
if (expr.value === null)
return new BasicEvaluatedExpression().setNull().setRange(expr.range);
if (expr.value instanceof RegExp)
return new BasicEvaluatedExpression().setRegExp(expr.value).setRange(expr.range);
});
// 非常多的plugin...
// 非常非常多的原型方法
}
}
这个类内容非常多,但是内容类型很简单,只有事件流的注入与一些原型方法,所以在初始化不会有任何实际动作。
这样就返回了一个包含大量事件流的Parser对象,之后apply的时候再回头解析。
parser事件流
这个事件流回头翻了好几节才找到,集中在第24节的大图中,插件列表如下:
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)
);
NodeSourcePlugin
但是第一个事件是在NodeSourcePlugin插件中,注入地点如下:
class WebpackOptionsApply extends OptionsApply {
constructor() {
super();
}
process(options, compiler) {
// ...
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;
// ...
}
// ...
}
// ...
}
}
感觉一下回到了2017年的感觉……
很遗憾,这个插件很快就挂了,源码简要如下:
// NodeSourcePlugin.js
params.normalModuleFactory.plugin("parser", function(parser, parserOptions) { if (parserOptions.node === false)
return; // ...more
});
由于传进来的parserOptions为空对象,所以直接返回。
剩下的均来源于后面的批量plugin。
CompatibilityPlugin、HarmonyModulesPlugin...
// CompatibilityPlugin.js
params.normalModuleFactory.plugin("parser", (parser, parserOptions) => {
if (typeof parserOptions.browserify !== "undefined" && !parserOptions.browserify)
return; // ...
});
阵亡。
后面的HarmonyModulesPlugin、AMDPlugin、CommonJsPlugin、RequireJsStuffPlugin分别尝试获取parserOptions的对应属性,失败直接返回。
APIPlugin
这个没有阵亡,做了点事。
const REPLACEMENTS = {
__webpack_require__: "__webpack_require__", // eslint-disable-line camelcase
__webpack_public_path__: "__webpack_require__.p", // eslint-disable-line camelcase
__webpack_modules__: "__webpack_require__.m", // eslint-disable-line camelcase
__webpack_chunk_load__: "__webpack_require__.e", // eslint-disable-line camelcase
__non_webpack_require__: "require", // eslint-disable-line camelcase
__webpack_nonce__: "__webpack_require__.nc", // eslint-disable-line camelcase
"require.onError": "__webpack_require__.oe" // eslint-disable-line camelcase
};
const REPLACEMENT_TYPES = {
__webpack_public_path__: "string", // eslint-disable-line camelcase
__webpack_require__: "function", // eslint-disable-line camelcase
__webpack_modules__: "object", // eslint-disable-line camelcase
__webpack_chunk_load__: "function", // eslint-disable-line camelcase
__webpack_nonce__: "string" // eslint-disable-line camelcase
};
class APIPlugin {
apply(compiler) {
compiler.plugin("compilation", (compilation, params) => {
// set...
params.normalModuleFactory.plugin("parser", parser => {
// 简单明了注入事件流
Object.keys(REPLACEMENTS).forEach(key => {
// 'expression __webpack_require__'等等
parser.plugin(`expression ${key}`, ParserHelpers.toConstantDependency(REPLACEMENTS[key]));
// 'evaluate typeof __webpack_require__'等等
parser.plugin(`evaluate typeof ${key}`, ParserHelpers.evaluateToString(REPLACEMENT_TYPES[key]));
});
});
});
}
}
module.exports = APIPlugin;
十分的简单,根据两个内置的配置对象批量注入事件,通过ParserHelpers类来辅助生成事件函数。
该辅助类的两个方法源码如下:
ParserHelpers.toConstantDependency = function(value) {
// value来源于APIPlugin插件
// expr为触发事件流时给的参数 暂时不知道是什么
return function constDependency(expr) {
var dep = new ConstDependency(value, expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
};
};
ParserHelpers.evaluateToString = function(value) {
// 一样的
return function stringExpression(expr) {
return new BasicEvaluatedExpression().setString(value).setRange(expr.range);
};
};
这里又引入了两个辅助类来生成新对象。
第一个方法有一个方法调用:this.state.current.addDependency,我是根本找不到定义的地点,不知道这个方法是如何执行的,尝试在控制台输出,结果发现整个打包过程中,根本就没有触发这两批事件流。
这就完全无法得知其作用了,所以这个插件暂时跳过,可以当做没有。
ConstPlugin
这个插件注入了三个事件流,简单看一下:
params.normalModuleFactory.plugin("parser", parser => {
parser.plugin("statement if", function(statement) {
// ...
});
parser.plugin("expression ?:", function(expression) {
// ...
});
parser.plugin("evaluate Identifier __resourceQuery", function(expr) {
// ...
});
parser.plugin("expression __resourceQuery", function() {
// ...
});
});
UseStrictPlugin
// UseStrictPlugin.js
params.normalModuleFactory.plugin("parser", (parser) => {
const parserInstance = parser;
parser.plugin("program", (ast) => {
// ...
});
});
注入program事件流。
后面的RequireIncludePlugin、RequireEnsurePlugin、RequireContextPlugin、ImportPlugin、SystemPlugin插件全部跳过,内容我就不贴进来了。
总的来说,由于传进来的options为空对象,这个parser事件流的触发毫无意义,只是单纯注入了几个事件流,最后返回了这个parser对象,作为了request的一个对象参数,具体原型方法的调用后面再看。
.36-浅析webpack源码之Parser类的更多相关文章
- .39-浅析webpack源码之parser.parse
因为换了个工作,所以博客停了一段时间. 这是上个月留下来的坑,webpack的源码已经不太想看了,又臭又长,恶心的要死,想去看node的源码……总之先补完这个 上一节完成了babel-loader对J ...
- 从Webpack源码探究打包流程,萌新也能看懂~
简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...
- .30-浅析webpack源码之doResolve事件流(1)
这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...
- .34-浅析webpack源码之事件流make(3)
新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...
- .30-浅析webpack源码之doResolve事件流(2)
这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...
- .3-浅析webpack源码之预编译总览
写在前面: 本来一开始想沿用之前vue源码的标题:webpack源码之***,但是这个工具比较巨大,所以为防止有人觉得我装逼跑来喷我(或者随时鸽),加上浅析二字,以示怂. 既然是浅析,那么案例就不必太 ...
- JDK源码之Integer类分析
一 简介 Integer是int基本类型的包装类,同样继承了Number类,实现了Comparable接口,String类中的一些转化方法就使用了Integer类中的一些API,且fianl修饰不可继 ...
- webpack源码-依赖收集
webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compi ...
- Struts2 源码分析——Result类实例
本章简言 上一章笔者讲到关于DefaultActionInvocation类执行action的相关知识.我们清楚的知道在执行action类实例之后会相关处理返回的结果.而这章笔者将对处理结果相关的内容 ...
随机推荐
- hbase首次导入大批次的数据成功!
本次主要是采用hbase自带的importtsv工具来导入.首先要把数据文件上传到hdfs上,然后导入hbase表格.该导入方式只支持.tsv数据文件的导入. 导入流程: 1.下载数据.我们在本文中将 ...
- 两台linux之间建立信任关系,实现免密码ssh远程登录或scp数据上传
两台linux之间建立信任关系,实现免密码远程登录或数据上传 1.执行ssh-keygen命令,生成建立安全信任关系的证书: linux1上:执行命令 ssh-keygen -t rsa 在程序提 ...
- asp.net core mvc 中间件之路由
asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...
- [Ynoi2018]未来日记(分块)
分块神题. 看了一会儿题解,看懂了思路,然后写了两个小时,调了一个多小时,好多地方写错了. 我们考虑对序列和值域都分块.\(sum1[i][j]\) 表示前 \(i\) 个块,第 \(j\) 块值域有 ...
- Yii2 Apache + Nginx 路由重写
一.什么是路由重写 原本的HTTP访问地址: www.test.com/index.php?r=post/view&id=100 表示这个请求将由PostController 的 action ...
- webstorm “Unterminated statement”
使用webstorm的时候,写console.log,或者一些其他语句的时候 偶尔会出现这种提示,不是报错,就是看着别扭,应该是写法规范问题. 解决办法: 在下面空一行就行了
- Node学习笔记---初识Node
博客原文地址:Claiyre的个人博客 https://claiyre.github.io/ 博客园地址:http://www.cnblogs.com/nuannuan7362/ 如需转载,请在文章开 ...
- 移动端测试接口--Fiddler抓包工具
Fiddler抓包工具是一款免费且功能强大的数据包抓取软件.它通过代理的方式获取程序http通讯的数据,可以用其检测网页和服务器的交互情况,能够记录所有客户端和服务器间的http请求,支持监视.设置断 ...
- win10+Theano+GPU
1. cuda + cudnn 首先还是要先安装GPU库,具体和caffe安装中一样. 2. Theano 为防止下载速度慢,配置清华镜像 conda config --add channels ht ...
- linux shell使用别名,切换当前目录
别名alias 别名就是一种快捷方式,以省去用户输入一长串命令序列的麻烦. 别名使用alias命令 比如 alias cp='cp -i' alias l.='ls -d .* --color=tty ...