手写一个webpack,看看AST怎么用 (5)

所以我们在traverse里面加一个CallExpression:

traverse(ast, { ImportDeclaration(p) { // 跟前面的差不多,省略了 }, CallExpression(p) { // 如果调用的是import进来的函数 if (p.node.callee.name === importVarName) { // 就将它替换为转换后的函数名字 p.node.callee.name = `${importCovertVarName}.default`; } }, });

这样转换后,我们再重新生成一下代码,已经像那么个样子了:

image-20210207175649607

递归解析多个文件

现在我们有了一个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);

看看解析结果吧:

image-20210208152330212

这个结果其实跟我们最终需要生成的__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进来的变量名字已经变掉了:

image-20210208153942630

替换export语句

从我们需要生成的结果来看,export需要进行两个处理:

如果一个文件有export default,需要添加一个__webpack_require__.d的辅助方法调用,内容都是固定的,加上就行。

将export语句转换为普通的变量定义。

对应生成结果上的这两个:

image-20210208154959592

要处理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语句被替换了:

image-20210208160244276

然后就是根据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也就处理好了:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxzpz.html