所以我们在traverse里面加一个CallExpression:
traverse(ast, { ImportDeclaration(p) { // 跟前面的差不多,省略了 }, CallExpression(p) { // 如果调用的是import进来的函数 if (p.node.callee.name === importVarName) { // 就将它替换为转换后的函数名字 p.node.callee.name = `${importCovertVarName}.default`; } }, });这样转换后,我们再重新生成一下代码,已经像那么个样子了:
递归解析多个文件现在我们有了一个parseFile方法来解析处理入口文件,但是我们的文件其实不止一个,我们应该依据模块的依赖关系,递归的将所有的模块都解析了。要实现递归解析也不复杂,因为前面的parseFile的依赖dependcies已经返回了:
我们创建一个数组存放文件的解析结果,初始状态下他只有入口文件的解析结果
根据入口文件的解析结果,可以拿到入口文件的依赖
解析所有的依赖,将结果继续加到解析结果数组里面
一直循环这个解析结果数组,将里面的依赖文件解析完
最后将解析结果数组返回就行
写成代码就是这样:
function parseFiles(entryFile) { const entryRes = parseFile(entryFile); // 解析入口文件 const results = [entryRes]; // 将解析结果放入一个数组 // 循环结果数组,将它的依赖全部拿出来解析 for (const res of results) { const dependencies = res.dependencies; dependencies.map((dependency) => { if (dependency) { const ast = parseFile(dependency); results.push(ast); } }); } return results; }然后就可以调用这个方法解析所有文件了:
const allAst = parseFiles(config.entry); console.log(allAst);看看解析结果吧:
这个结果其实跟我们最终需要生成的__webpack_modules__已经很像了,但是还有两块没有处理:
一个是import进来的内容作为变量使用,比如
import hello from './hello'; const world = 'world'; const helloWorld = () => `${hello} ${world}`;
另一个就是export语句还没处理
替换import进来的变量(作为变量调用)前面我们已经用CallExpression处理过作为函数使用的import变量了,现在要处理作为变量使用的其实用Identifier处理下就行了,处理逻辑跟之前的CallExpression差不多:
traverse(ast, { ImportDeclaration(p) { // 跟以前一样的 }, CallExpression(p) { // 跟以前一样的 }, Identifier(p) { // 如果调用的是import进来的变量 if (p.node.name === importVarName) { // 就将它替换为转换后的变量名字 p.node.name = `${importCovertVarName}.default`; } }, });现在再运行下,import进来的变量名字已经变掉了:
替换export语句从我们需要生成的结果来看,export需要进行两个处理:
如果一个文件有export default,需要添加一个__webpack_require__.d的辅助方法调用,内容都是固定的,加上就行。
将export语句转换为普通的变量定义。
对应生成结果上的这两个:
要处理export语句,在遍历ast的时候添加ExportDefaultDeclaration就行了:
traverse(ast, { ImportDeclaration(p) { // 跟以前一样的 }, CallExpression(p) { // 跟以前一样的 }, Identifier(p) { // 跟以前一样的 }, ExportDefaultDeclaration(p) { hasExport = true; // 先标记是否有export // 跟前面import类似的,创建一个变量定义节点 const variableDeclaration = t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("__WEBPACK_DEFAULT_EXPORT__"), t.identifier(p.node.declaration.name) ), ]); // 将当前节点替换为变量定义节点 p.replaceWith(variableDeclaration); }, });然后再运行下就可以看到export语句被替换了:
然后就是根据hasExport变量判断在AST转换为代码的时候要不要加__webpack_require__.d辅助函数:
const EXPORT_DEFAULT_FUN = ` __webpack_require__.d(__webpack_exports__, { "default": () => (__WEBPACK_DEFAULT_EXPORT__) });\n `; function parseFile(file) { // 省略其他代码 // ...... let newCode = generate(ast).code; if (hasExport) { newCode = `${EXPORT_DEFAULT_FUN} ${newCode}`; } }最后生成的代码里面export也就处理好了: