.39-浅析webpack源码之parser.parse
因为换了个工作,所以博客停了一段时间。
这是上个月留下来的坑,webpack的源码已经不太想看了,又臭又长,恶心的要死,想去看node的源码……总之先补完这个
上一节完成了babel-loader对JS文件字符串的转换,最后返回后进入如下代码:
// NormalModule.js
build(options, compilation, resolver, fs, callback) {
// ... return this.doBuild(options, compilation, resolver, fs, (err) => {
// ... // 进入这里
try {
this.parser.parse(this._source.source(), {
current: this,
module: this,
compilation: compilation,
options: options
});
} catch (e) {
const source = this._source.source();
const error = new ModuleParseError(this, source, e);
this.markModuleAsErrored(error);
return callback();
}
return callback();
});
}
在看这个parse方法之前,需要过一下参数,首先是这个source方法。
这个_source并不是转换后的字符串,而是进行一层封装的对象,source是其原型方法,源码如下:
class OriginalSource extends Source {
// value => babel-loader转换后的字符串
// name => 'D:\\workspace\\node_modules\\babel-loader\\lib\\index.js!D:\\workspace\\doc\\input.js'
constructor(value, name) {
super();
this._value = value;
this._name = name;
} source() {
return this._value;
} //...
}
有病啊!还是返回了转换后的字符串。
parser.parse
这个parser的parse方法有两部分,如下:
parse(source, initialState) {
let ast;
const comments = [];
// 这一部分负责解析源代码字符串
for (let i = 0, len = POSSIBLE_AST_OPTIONS.length; i < len; i++) {
// ...
}
// 这里再次进行尝试
if (!ast) {
// ...
}
if (!ast || typeof ast !== "object")
throw new Error("Source couldn't be parsed"); // code...
// 这里传入parse后的ast触发parse的事件流
if (this.applyPluginsBailResult("program", ast, comments) === undefined) {
this.prewalkStatements(ast.body);
this.walkStatements(ast.body);
}
// code...
return state;
}
先不用管这里POSSIBLE_AST_OPTIONS是啥,总之这里做了两件事情
1、对返回的字符串再做一次parse
2、将得到的ast作为参数触发program事件流
一个一个来,首先是parse代码块,如下:
/*
const POSSIBLE_AST_OPTIONS = [
{
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
sourceType: "module",
plugins: {
dynamicImport: true
}
},
{
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
sourceType: "script",
plugins: {
dynamicImport: true
}
}
];
*/
// 抽象语法树
let ast;
// 注释数组
const comments = [];
for (let i = 0, len = POSSIBLE_AST_OPTIONS.length; i < len; i++) {
if (!ast) {
try {
comments.length = 0;
POSSIBLE_AST_OPTIONS[i].onComment = comments;
// 传入JS字符串与本地默认配置参数
ast = acorn.parse(source, POSSIBLE_AST_OPTIONS[i]);
} catch (e) {
// ignore the error
}
}
}
这里引入了别的模块acorn来解析字符串,负责将JS字符串解析成抽象语法树。
这里不关心解析的过程,假设解析完成,简单看一下一个JS文件源码与解析后的树结构:
源码
const t = require('./module.js');
t.fn();
抽象语法树
{
"type": "Program",
"start": 0,
"end": 41,
"body": [{
"type": "VariableDeclaration",
"start": 0,
"end": 33,
"declarations": [{
"type": "VariableDeclarator",
"start": 6,
"end": 32,
"id": { "type": "Identifier", "start": 6, "end": 7, "name": "t" },
"init": {
"type": "CallExpression",
"start": 10,
"end": 32,
"callee": {
"type": "Identifier",
"start": 10,
"end": 17,
"name": "require"
},
"arguments": [{
"type": "Literal",
"start": 18,
"end": 31,
"value": "./module.js",
"raw": "'./module.js'"
}]
}
}],
"kind": "const"
}, {
"type": "ExpressionStatement",
"start": 34,
"end": 41,
"expression": {
"type": "CallExpression",
"start": 34,
"end": 40,
"callee": {
"type": "MemberExpression",
"start": 34,
"end": 38,
"object": { "type": "Identifier", "start": 34, "end": 35, "name": "t" },
"property": { "type": "Identifier", "start": 36, "end": 38, "name": "fn" },
"computed": false
},
"arguments": []
}
}],
"sourceType": "script"
}
这里涉及到一个抽象语法树的规则,详情可见https://github.com/estree/estree
接下来会调用Parser上的program事件流,定义地点如下:
// WebpackOptionsApply.js
compiler.apply(
//
new HarmonyModulesPlugin(options.module),
//
new UseStrictPlugin(),
);
地方不好找,总之一个一个过:
HarmonyModulesPlugin
// HarmonyModulesPlugin.js => HarmonyDetectionParserPlugin.js
parser.plugin("program", (ast) => {
// 这里对Import/Export的表达式进行检索
const isHarmony = ast.body.some(statement => {
return /^(Import|Export).*Declaration$/.test(statement.type);
});
if(isHarmony) {
const module = parser.state.module;
const dep = new HarmonyCompatibilityDependency(module);
dep.loc = {
start: {
line: -1,
column: 0
},
end: {
line: -1,
column: 0
},
index: -2
};
// 如果存在就对该模块进行特殊标记处理
module.addDependency(dep);
module.meta.harmonyModule = true;
module.strict = true;
module.exportsArgument = "__webpack_exports__";
}
});
这里的正则可以参考https://github.com/estree/estree/blob/master/es2015.md的Modules部分说明,简单讲就是检索JS中是否出现过Import * from *、Export default *等等。
如果存在会对该模块进行标记。
UseStrictPlugin
// UseStrictPlugin.js
parser.plugin("program", (ast) => {
const firstNode = ast.body[0];
// 检测头部是否有'use strict'字符串
if(firstNode &&
firstNode.type === "ExpressionStatement" &&
firstNode.expression.type === "Literal" &&
firstNode.expression.value === "use strict") {
// Remove "use strict" expression. It will be added later by the renderer again.
// This is necessary in order to not break the strict mode when webpack prepends code.
// @see https://github.com/webpack/webpack/issues/1970
const dep = new ConstDependency("", firstNode.range);
dep.loc = firstNode.loc;
parserInstance.state.current.addDependency(dep);
parserInstance.state.module.strict = true;
}
});
这个就比较简单了,判断JS是否是严格模式,然后做个标记。
事件流走完,parse方法也就调用完毕,接下来调用build方法的callback,一路回到了Compilation类的buildModule方法。
// Compilation.js
buildModule(module, optional, origin, dependencies, thisCallback) {
this.applyPlugins1("build-module", module);
if(module.building) return module.building.push(thisCallback);
const building = module.building = [thisCallback]; function callback(err) {
module.building = undefined;
building.forEach(cb => cb(err));
}
module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => {
// 处理错误与警告
const errors = module.errors;
for(let indexError = 0; indexError < errors.length; indexError++) {
// ...
}
const warnings = module.warnings;
for(let indexWarning = 0; indexWarning < warnings.length; indexWarning++) {
// ...
}
module.dependencies.sort(Dependency.compare);
// 事件流不存在
if(error) {
this.applyPlugins2("failed-module", module, error);
return callback(error);
}
this.applyPlugins1("succeed-module", module);
return callback();
});
这里对模块解析后的警告与错误进行处理,根据是否有错误走两个不同的事件流,然后触发callback。
这里神神秘秘的搞个callback函数,还forEach,往上面一看,傻了吧唧的就是强行给外部callback参数弄成数组,实际上就是调用了thisCallback。
现在总算摸清了callback的套路,就跟this一样,谁调用就找谁,于是这个callback回到了Compilation的_addModuleChain函数的尾部:
// Compilation.js
_addModuleChain(context, dependency, onModule, callback) {
// ... this.semaphore.acquire(() => {
moduleFactory.create({
// ...
}, (err, module) => {
// ...
// 从这里出来
this.buildModule(module, false, null, null, (err) => {
if(err) {
this.semaphore.release();
return errorAndCallback(err);
}
// 这属性就是个计时器
// 计算从读取模块内容到构建完模块的时间
if(this.profile) {
const afterBuilding = Date.now();
module.profile.building = afterBuilding - afterFactory;
} moduleReady.call(this);
}); function moduleReady() {
this.semaphore.release();
// 跳入下一个阶段
this.processModuleDependencies(module, err => {
if(err) {
return callback(err);
} return callback(null, module);
});
}
});
});
}
至此,模块的构建基本完成,先到这里吧……
.39-浅析webpack源码之parser.parse的更多相关文章
- .36-浅析webpack源码之Parser类
眼看webpack4都出了,我还在撸3的源码,真的是捉急啊…… 不过现在只是beta版本,等出稳定版本后跑跑4的源码去. 之前漏了一个东西没有讲,如下: asyncLib.parallel([/**/ ...
- .3-浅析webpack源码之预编译总览
写在前面: 本来一开始想沿用之前vue源码的标题:webpack源码之***,但是这个工具比较巨大,所以为防止有人觉得我装逼跑来喷我(或者随时鸽),加上浅析二字,以示怂. 既然是浅析,那么案例就不必太 ...
- 从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 ...
- webpack源码-依赖收集
webpack源码-依赖收集 version:3.12.0 程序主要流程: 触发make钩子 Compilation.js 执行EntryOptionPlugin 中注册的make钩子 执行compi ...
- .17-浅析webpack源码之compile流程-入口函数run
本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now( ...
- 浅析libuv源码-node事件轮询解析(3)
好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...
随机推荐
- RxSwift学习笔记3:生命周期/订阅
有了 Observable,我们还要使用 subscribe() 方法来订阅它,接收它发出的 Event. let observal = Observable.of("a",&qu ...
- CentOS 7.0 Firewall防火墙配置
启动停止 获取firewall状态 systemctl status firewalld.service firewall-cmd --state 开启停止防火墙 开机启动:systemctl ena ...
- 使用wget命令爬取整站
快速上手(整个bootstrap网页全被你抓取下来了~_~) wget -c -r -npH -k -nv http://www.baidu.com 参数说明 -c:断点续传 -r:递归下载 -np: ...
- 「PKUWC2019」拓扑序计数(状压dp)
考场只打了 \(52\) 分暴力...\(ljc\) 跟我说了一下大致思路,我回去敲了敲. \(f[i]\) 表示状态为 \(i\) 时的方案数.我们用二进制 \(0/1\) 表示不选/选点 \(i\ ...
- C#6.0语言规范(十) 类
类是可以包含数据成员(常量和字段),函数成员(方法,属性,事件,索引器,运算符,实例构造函数,析构函数和静态构造函数)和嵌套类型的数据结构.类类型支持继承,这是一种派生类可以扩展和专门化基类的机制. ...
- iOS--各种bug详解
1.为什么传的参数都对,但是就是请求不下来数据. 答:检查下传的字符串中,是不是有多的空格. 例如: 错误:{"startIndex":"1","en ...
- ASP.NETCore学习记录(一)
ASP.NETCore学习记录(一) asp.net core介绍 Startup.cs ConfigureServices Configure 0. ASP.NETCore 介绍 ASP.N ...
- 用yourls 搭建短链接地址服务
最近工作中遇到一个需求,将app下载地址变成短链接进行推广,索性就研究了下yourls . 发现这个玩意功能挺强大的,不但可以批量生成自己的短地址,还可以管理,统计每个短地址点击数量,还可以提供api ...
- 在Ubuntu下编译安装nginx
一.安装nginx 1.安装前提 a)epoll,linux内核版本为2.6或者以上 b)gcc编译器,g++编译器 c)pcre库,函数库,支持解析正则表达式 d)zlib库:压缩解压功能 e)op ...
- POJ 2612
#include<iostream> #include<stdio.h> #include<algorithm> #define MAXN 11 using nam ...