因为换了个工作,所以博客停了一段时间。
这是上个月留下来的坑,webpack的源码已经不太想看了,又臭又长,恶心的要死,想去看node的源码……总之先补完这个
上一节完成了babel-loader对JS文件字符串的转换,最后返回后进入如下代码:
// NormalModule.jsbuild(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.jscompiler.apply( ???// 1 ???new HarmonyModulesPlugin(options.module), ???// 2 ???new UseStrictPlugin(),);
地方不好找,总之一个一个过:
HarmonyModulesPlugin
// HarmonyModulesPlugin.js => HarmonyDetectionParserPlugin.jsparser.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.jsparser.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.jsbuildModule(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
原文地址:https://www.cnblogs.com/QH-Jimmy/p/8515179.html