宿主端模板替换
将服务端渲染的值(例如 Go html/template 的 `{{ .Field }}` 表达式)注入到 VM 混淆后的 JavaScript 中,而不破坏混淆流程。
问题
您的后端(Go、Rails、Django、PHP 等)提供一个 JavaScript 文件,其内容必须按每次请求进行
部分渲染 —— 一个 API 端点、一份特性开关列表、一段初始状态数据、一个构建 id、一个 nonce。您用
vmObfuscation: true 混淆该 JS,但您还需要模板引擎在混淆 完成之后 替换混淆
输出中的占位符。如果占位符被吸收进 VM 字节码(字符串和
标识符的默认行为),模板引擎就没有任何东西可供替换了。
我该选哪个选项?
| 服务端值是…… | 使用 |
|---|---|
| 一个 原始 JS 表达式(数组字面量、对象、数字、布尔值、函数调用) | reservedNames + 标识符占位符 |
| 一个 字符串,且您能控制模板定界符 | reservedStrings + 模板字面量占位符 |
一个 字符串,且模板引擎强制使用某些定界符(例如 Go 的 {{ .Field }}) | reservedStrings + 字符串或模板字面量占位符 |
模式 1 - 带标识符占位符的 reservedNames
最适合: 注入原始 JS 表达式(数组、对象、数字……),且完全无需担心引号转义。
选择一个绝不会与真实代码冲突的独特标识符,直接引用它,并在 reservedNames 中列出一个匹配
它的正则表达式。在 VM 混淆下,该标识符会被路由到保留表达式数组中,并在输出中
原样出现。
源码
(function () {
var initialState = __INITIAL_STATE__;
bootstrap(initialState);
})();
混淆器选项
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$']
});
混淆之后
在输出的某处,您会找到一个类似这样的保留表达式数组:
let _r = [__INITIAL_STATE__, /* …other entries… */];
标记 __INITIAL_STATE__ 在标识符重命名中留存下来,并且不会被吸收进 VM 字节码。
宿主端替换(Go 示例)
tpl := template.Must(template.New("obf.js").Parse(obfuscated))
tpl.Execute(w, map[string]any{
"InitialState": map[string]any{"user": "alice", "theme": "dark"},
})
……其中您的 Go 模板会把该标记替换为一个原始 JS 表达式:
{{ `__INITIAL_STATE__` }} → {{ .InitialState | toJSON }}
结果:
let _r = [{"user":"alice","theme":"dark"}, /* … */];
没有引号,也无需转义 —— 注入的值会被 JS 引擎当作一个普通表达式来解析。
模式 2 - 带模板字面量占位符的 reservedStrings
最适合: Go / Jinja 风格的模板引擎,它们要求使用像 {{ .Field }} 这样的定界符,而这些定界符
一旦被反引号包裹,恰好就是合法的 JS 文本。
占位符位于一个 单一 quasi、无插值 的模板字面量内部。在 VM 混淆下,它会 被路由到保留表达式数组,从而在输出中保留原始的反引号形式。
源码
(function () {
var featureFlags = JSON.parse(`{{.Config.FeatureFlags}}`);
applyFlags(featureFlags);
})();
混淆器选项
UI
要从上面的源码中保留 {{.Config.FeatureFlags}} 占位符,请把下面这个正则表达式添加到 Reserved Strings
字段 —— 使用单反斜杠。UI 会原样存储该值,因此与 JS 代码不同,反斜杠不会
被加倍:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
混淆之后
let _r = [`{{.Config.FeatureFlags}}`, /* … */];
该占位符会被逐字节保留,包括其外围的反引号。
宿主端替换
把 {{.Config.FeatureFlags}} 替换为一个合法的 JSON 字符串 —— 它在运行时会位于反引号内部,因此不
需要对内部引号进行转义:
flagsJSON, _ := json.Marshal(config.FeatureFlags) // e.g. {"newCheckout":true,"darkMode":false}
out := strings.ReplaceAll(obfuscated, "{{.Config.FeatureFlags}}", string(flagsJSON))
运行时:JSON.parse(`{"newCheckout":true,"darkMode":false}`) —— 可用。
模式 3 - 带引号字符串占位符的 reservedStrings
最适合: 必须保持 ES5(无模板字面量)的源码,或周围 API 期望得到一个 常规字符串字面量的情形。
占位符是一个单引号或双引号字符串字面量。在 VM 混淆下,它会被路由到
保留字符串数组,该数组会作为一个经 JSON.stringify 序列化的 JS 数组输出 —— 无论输入的引号
风格如何,始终为双引号。
源码
(function () {
var tags = JSON.parse("{{.Page.Tags}}");
renderTags(tags);
})();
混淆器选项
UI
要从上面的源码中保留 {{.Page.Tags}} 占位符,请把下面这个正则表达式添加到 Reserved Strings 字段 ——
使用单反斜杠。UI 会原样存储该值,因此与 JS 代码不同,反斜杠不会被加倍:
\{\{[^}]+\}\}

API
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
混淆之后
var _rs = ["{{.Page.Tags}}", /* … */];
宿主端替换
由于该占位符位于一个双引号 JS 字符串内部,注入的 JSON 负载必须将其内部的 "
字符用 \" 转义:
raw, _ := json.Marshal(page.Tags) // e.g. ["news","tech","release"]
// Escape " for embedding inside a JS double-quoted string.
escaped := strings.ReplaceAll(string(raw), `"`, `\"`)
out := strings.ReplaceAll(obfuscated, "{{.Page.Tags}}", escaped)
生成的输出:
var _rs = ["[\"news\",\"tech\",\"release\"]", /* … */];
运行时:JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"]。
如果您忘了转义,浏览器会看到不成对的引号并抛出 SyntaxError。模式 2 通过使用反引号
完全规避了这一点。
一个程序中的多个占位符
三种模式都能组合。一个带有交替(alternation)的 reservedStrings 正则表达式就能匹配您
模板引擎产生的每一种占位符形态:
JavaScriptObfuscator.obfuscate(source, {
vmObfuscation: true,
reservedNames: ['^__INITIAL_STATE__$'],
reservedStrings: ['\\{\\{[^}]+\\}\\}']
});
您可以在同一份源码中混用标识符占位符(用于原始 JS 值)和字符串占位符(用于渲染后的 JSON)—— 按每个占位符、依据服务端实际会注入什么来选择。
兼容性说明
- 不要在标识符与字符串之间复用占位符。 一个像
__TOKEN__这样的保留名称,与一个匹配__TOKEN__的保留字符串,描述的是两条不同的代码路径(保留表达式数组 vs. 保留字符串数组)。请为二者使用彼此不同的文本形态 —— 例如,为标识符占位符采用UPPER_SNAKE约定,而为字符串占位符采用定界符包裹的形态({{ ... }}、%{...}、<<<...>>>)。这样,一个正则中的缺陷就不会悄悄匹配到另一个。 reservedStrings正则是对原始字符串值运行的。 该正则匹配的是字符串的运行时值,而非源文本。\{\{[^}]+\}\}匹配 包含{{.something}}(或任何其他{{...}}形态)的字符串。如果您的占位符可能被额外内容包裹("prefix-{{.Field}}-suffix"),正则仍会匹配,但整个字符串都会被保留 —— 请据此规划您的替换。- 适用于任意模板引擎。 尽管这些示例使用 Go 的
text/template语法,但 javascript-obfuscator 集成中没有任何东西是 Go 专属的。任何能对混淆器输出做字符串级替换的工具都可以:Rails ERB、Django、PHP 短标签、CI 流水线里的 sed,等等。请选择您的引擎自然产生、且不与真实 JS 语法冲突的定界符。
