JavaScript CommonJS模块混淆需确保module.exports导出结构和语义不变,关键在于保留宿主环境依赖的属性名(如activate、deactivate),而非单纯保留变量名;混淆工具须禁用controlFlowFlattening等破坏性选项,并配置reservedNames白名单。

JavaScript 中 CommonJS 模块在混淆后保持导出名称可被正常识别,核心不是“让混淆器保留名字”,而是避免混淆破坏 module.exports 的结构和语义。Atom 插件、Node.js 加载器等运行环境只认 module.exports 的实际值,不解析变量名或 AST 逻辑。一旦混淆改写了导出对象的键名或层级,就会触发 Cannot find module 或 undefined is not a function。
关键原则:混淆不能动导出契约
CommonJS 的模块识别完全依赖 module.exports 的最终值形态。比如:
// lib/main.js —— Atom 插件入口
module.exports = {
activate() { /* ... */ },
deactivate() { /* ... */ }
};这个 { activate, deactivate } 是 Atom 启动时通过 require('./lib/main') 取到的对象,它必须有 activate 这个属性名。混淆若把 activate 改成 _0xabc123,Atom 就找不到生命周期方法。
所以真正要保护的不是“代码安全”,而是导出接口的稳定性。
立即学习“Java免费学习笔记(深入)”;
必须保留的导出形式(按优先级)
-
module.exports = { key1: fn1, key2: fn2 }
→ 所有key1、key2必须不被重命名(需配置白名单) -
module.exports = class MyClass {}
→ 类名MyClass可混淆,但module.exports本身不能被赋值给别名变量后再导出 -
exports.activate = function() {}
→activate是属性名,必须保留;exports变量名可混淆,但不能切断它与module.exports的引用关系 -
module.exports = function() {}(默认函数导出)
→ 函数名可混淆,不影响;但不能把整个赋值语句替换成module.exports = _0x456()这类调用表达式(可能执行时机错乱)
混淆工具配置要点(以 babel-plugin-transform-obfuscator 为例)
在 .babelrc 或 Babel 配置中,必须关闭会破坏导出结构的选项,并显式保留关键标识符:
{
"plugins": [
["transform-obfuscator", {
"identifiersPrefix": "_",
"controlFlowFlattening": false,
"deadCodeInjection": false,
"stringArray": false,
"rotateStringArray": false,
"reservedNames": [
"activate",
"deactivate",
"serialize",
"configSchema",
"provide",
"consume"
]
}]
]
}-
reservedNames列表必须包含所有被宿主环境(如 Atom、VS Code 扩展主机)按名调用的导出属性 - 不启用
controlFlowFlattening和deadCodeInjection:它们会改变执行顺序或插入不可预测逻辑,可能干扰require时的同步初始化 -
stringArray: false:避免把'activate'这样的字面量字符串加密成['a','c','t','i','v','a','t','e'].join(''),否则module.exports[decryptedKey]无法静态分析
开发与发布分离的实践建议
混淆只应在发布前执行,且必须可验证:
- 在
prepublish.js中生成混淆版到dist/,同时保留原始lib/用于本地调试 - 发布前跑一次
node -e "console.log(require('./dist/main').activate)",确认返回的是函数而非undefined - 若插件使用
package.json#main指向lib/main.js,发布时需同步更新为dist/main.js,或在package.json中用"main": "dist/main.js"并确保dist/已构建
常见翻车点(务必避开)
- 把
module.exports = { activate }混淆成module.exports = { _0x789: activate }→ Atom 找不到activate - 用
const m = module; m.exports = { activate }再混淆m→ Node.js 不认这种间接赋值 - 在导出对象上动态添加属性:
exports[process.env.NODE_ENV === 'prod' ? 'init' : 'debug'] = fn→ 混淆后环境变量失效,属性名丢失 - 对
require('./utils')的返回结果再做属性访问并混淆:const u = require('./utils'); u.run()→ 若u被重命名为_0x123,但run被保留,仍可工作;但若run也被混淆,就断链
本质上,CommonJS 混淆不是“加密”,而是“可控脱敏”。只要导出侧的键名、结构、求值时机不变,运行时就不会报错。


















