核心是解析代码为ast并安全重命名标识符:用@babel/parser解析(需sourcetype:'module'、ecmaversion≥2022),过滤保留字及不可混淆节点,基于作用域唯一映射,用@babel/traverse+path.replacewith替换,最后用@babel/generator生成并验证语义一致性。

核心是解析代码为 AST,识别可安全重命名的标识符,按作用域独立映射并替换,最后生成混淆后代码。不碰关键字、内置对象、外部引用和已声明的导出名。
解析源码生成标准 AST
用 @babel/parser 解析 JS 字符串,必须显式指定 sourceType: 'module',否则 import/export 会报错。ecmaVersion 推荐设为 2022 或更高,以支持可选链、数字分隔符等新语法。解析结果是一个纯对象树,所有节点都有 type、start、end、loc 等基础字段,后续遍历和判断都基于此结构。
准确识别可混淆的标识符
不是所有 Identifier 节点都能改名。需严格过滤:
- 跳过 JavaScript 保留字(如 if、class、yield),可用
is-reserved-word库校验 - 排除 MemberExpression 中非计算属性的 property 名(如
obj.name中的 name),除非它已在当前作用域中被声明为变量 - 忽略 ClassDeclaration.id、ExportNamedDeclaration.specifiers、ImportSpecifier.local,这些名字影响模块契约,不能混淆
- 只处理 VariableDeclarator.id、FunctionDeclaration.id、ArrowFunctionExpression.params、CatchClause.param 这几类绑定标识符
构建作用域感知的重命名逻辑
混淆名必须在作用域内唯一,且不同作用域间不冲突:
- 利用 @babel/traverse 自带的
path.scope,它已维护好完整的嵌套作用域链和 binding 映射 - 对每个匹配的 Identifier 节点,先调用
path.scope.getBinding(node.name),确认binding.kind === 'local'或'param' - 为每个 Scope 实例维护一个 Map,键为原始名,值为生成的短名(如
_a、$0);生成时可用递增计数器,避免随机导致重复 - 务必用
path.replaceWith()替换节点,而不是直接改node.name,否则 scope 不会自动更新引用关系
生成混淆后代码并验证行为
用 @babel/generator 将修改后的 AST 转回字符串。建议开启 compact: true 减少空格,但保留 sourceMaps: true 方便调试。生成后应做基本验证:
- 用 Node.js 直接执行混淆后代码,确认无 SyntaxError 或 ReferenceError
- 对比原代码与混淆代码的运行输出(如 console.log 结果),确保语义未变
- 检查是否意外混淆了全局对象(如
window、console)或第三方库变量名——这类需在 visitor 中硬编码白名单过滤











