Babel中使用WeakSet跟踪已访问节点两次遍历失效问题求助
解决Babel两次遍历中WeakSet无法识别AST节点的问题
哈哈,这个问题我之前也踩过坑!核心原因其实很简单:你两次调用了babel.transformSync(code, ...),每次调用都会重新把原始代码解析一遍,生成完全全新的AST节点对象。
第一次遍历添加到WeakSet里的是第一次解析出来的节点对象,第二次遍历拿到的是第二次解析生成的、内容完全一样但内存地址完全不同的新对象——WeakSet是靠对象的内存引用判断的,所以has()自然返回false啦!
给你两种靠谱的解决方案,看你需求选:
方案一:在同一个Transform调用里完成两次遍历(推荐)
既然问题出在两次解析生成不同AST,那我们就只解析一次,在同一个插件里先完成第一次遍历标记,再手动做第二次遍历。Babel插件的post阶段会在第一次AST遍历完成后触发,刚好适合做这件事:
const babel = require('@babel/core'); const code = ` function greet(name) { return 'Hello ' + name; } console.log(greet('tanhauhau')); `; const visited_past = new WeakSet(); babel.transformSync(code, { plugins: [ function myCustomPlugin() { return { visitor: { // 第一次遍历:标记访问过的节点 StringLiteral(path) { console.log("first traversal :"); console.log(path.node); visited_past.add(path.node); } }, // 第一次遍历完成后,手动触发第二次遍历 post(state) { babel.traverse(state.ast, { StringLiteral(path) { if (!visited_past.has(path.node)) { console.log("second traversal (unvisited):"); console.log(path.node); // 在这里写你对未访问节点的修改逻辑 } else { console.log("second traversal :"); console.log(path.node); console.log(visited_past.has(path.node)); // 现在会返回true啦 } } }); } }; }, ], });
这个方案只解析一次AST,性能更好,而且WeakSet的逻辑完全能正常工作。
方案二:必须分两次Transform?用节点唯一标识替代对象引用
如果你的业务场景必须要分两次独立的transformSync调用,那我们就不能靠对象引用判断了,得用节点的唯一标识来标记。比如用节点的start和end属性组合(同一个代码里的节点位置是固定的),或者结合value+位置避免相同内容的字符串混淆:
const babel = require('@babel/core'); const code = ` function greet(name) { return 'Hello ' + name; } console.log(greet('tanhauhau')); `; // 用Set存储节点的唯一键,比如start+end的组合 const visitedKeys = new Set(); const output = babel.transformSync(code, { plugins: [ function myCustomPlugin() { return { visitor: { StringLiteral(path) { console.log("first traversal :"); console.log(path.node); // 生成唯一键 const key = `${path.node.start}-${path.node.end}`; visitedKeys.add(key); }, }, }; }, ], }); const output2 = babel.transformSync(code, { plugins: [ function myCustomPlugin() { return { visitor: { StringLiteral(path) { console.log("second traversal :"); console.log(path.node); const key = `${path.node.start}-${path.node.end}`; console.log(visitedKeys.has(key)); // 现在会返回true if (!visitedKeys.has(key)) { // 处理未访问的节点逻辑 } }, }, }; }, ], });
注意哦,如果两次transform之间代码有修改,节点的start/end会变化,这时候可能需要换其他标识方式,比如节点的value加上所在行号列号的组合。
内容的提问来源于stack exchange,提问作者saha
相关产品推荐
相关产品推荐

