有趣的显现和设计
循环依赖导致模块导出为空对象
const fn = lazyFunction(() => require("./webpack")); // lib\index.js 128
初看可能不会将它和解决循环依赖导致的导出模块为空对象联系在一起,不过你可以试着这样子修改这行代码
const fn = require("./webpack");
然后重新启动debugger,你会发现一个报错,说webpack.ProvidePlugin is not a constructor
然后我们跟着这个compiler溯源发现,这里的compiler.webpack到赋值处
Compiler.js文件中的webpack值来源于导入
const webpack = require("."); //lib\Compiler.js 17, 解释一下这里的.导入的是./index.js文件
我们的index.js中存在这行代码
get Compiler() {
return require("./Compiler");
}, // lib\index.js 189-191
从这里我们可以看出 index.js 导入了 Compiler.js。然后Compiler.js又导入了index.js。这样子就产生了循环依赖。循环依赖会导致模块还没加载完成,从而导致了导出的是空对象的情况。
模块导入收集以及构建缓存
该文章的开始构建部分中我们了解到了构建过程中的函数调用,对于当前模块导入的依赖进行分析,然后递归出链式的导入依赖,从this._processModuleDependencies (任务队列调用-模块依赖)调用开始
_processModuleDependencies() {
// ...其它逻辑
this.handleModuleCreation() // 递归的开始,Entry逻辑中编译部分亦是通过这个方法。
} // lib\Compilation.js 1593-1873
可以在这部分代码中看到sortedDependencies,这个变量用来收集上一个模块中的dependencies(模块依赖数组),这个数组的产生一定离不开对于当前模块解析。
承接上篇文章的编译部分,build之后的ast处理逻辑以及dep收集
当前模块解析模块依赖逻辑
└── module.build (模块对应的解析处理实例, 大多数是NormalModule,lib\NormalModule.js)
└── (this.parser).parse (_.doBuild最后的入参,一个回调函数 1357行. (this.parser),这里的parser是当前实例的属性值,是在链式执行中通过发布订阅赋予的值, javascript/esm 对应 JavascriptParser.apply中发布方法产生的返回值)
└── JavascriptParser._parse
└── acorn.parse (三方库acorn)
下面是借助acorn.parse分析简单字符串的输出,可以看出esm的导入type是"ImportDeclaration",表达式声明的type是"ExpressionStatement"。由此可以区分出当前模块依赖的模块是哪些,然后通过后续的逻辑启用递归。