宿主端模板替换
将服务端渲染的值(例如 Go 模板风格的 `{{ .Field }}` 占位符)注入 VM 混淆后的 JavaScript,而不破坏混淆流程。
问题
您的后端(Go、Rails、Django、PHP 等)提供一个 JavaScript 文件,其内容必须按请求部分渲染:一个 API 端点、一组功能开关、一段初始状态数据、一个构建 ID 或一个 nonce。您用 vmObfuscation: true 混淆这段 JS,但同时还需要模板引擎在混淆完成之后替换混淆输出中的占位符。reservedNames 和 reservedStrings 能让占位符在输出中保持可见,从而可以被替换。没有它们,字符串占位符会被吸收进 VM 字节码,标识符占位符则会在多个被改写的位置输出,于是模板引擎要么找不到可替换的内容,要么会破坏输出。
考虑改为以数据形式加载这些值
如果可以,最好完全不修改受保护的输出:在受保护的包运行之前,以数据形式加载运行时配置。把它放在单独的脚本中,或从经过身份验证的端点获取。这样受保护的输出保持不变,vmSelfDefending 也可以继续启用。无论哪种方式,发送到浏览器的数据对该浏览器都是可见的。
当这些值确实必须替换进混淆后的文件时,请使用下面的模式。
关于 Go 示例的说明: 它们对混淆后的输出做的是 纯字符串替换(strings.ReplaceAll / strings.NewReplacer),并自行处理转义,每种模式中都有展示。它们没有经过 Go 的 html/template 包处理,否则该包会在此基础上再套用它自己的上下文相关 JavaScript 转义,导致载荷被双重转义。如果您确实通过 html/template 渲染,请去掉手动转义,让模板引擎只转义一次。
该选择哪个选项?
| 服务端的值是… | 使用 |
|---|---|
| 一个原始 JS 值(数组、对象、数字、布尔值,即任意 JSON 值) | 模式 1 - reservedNames + 标识符占位符 |
| 一个字符串(常见情况:渲染后的 JSON、构建 ID、nonce) | 模式 2 - reservedStrings + 模板字面量占位符 |
| 一个字符串,且代码必须保持 ES5,或周围的 API 需要带引号的字符串字面量 | 模式 3 - reservedStrings + 带引号字符串占位符 |
混淆后替换与 vmSelfDefending: true 不兼容。请参阅本方案末尾的兼容性说明。
模式 1:reservedNames 配合标识符占位符
最适合:注入原始 JS 表达式(数组、对象、数字等),完全无需担心引号转义。
选择一个绝不会与真实代码冲突的独特标识符,直接引用它,并在 reservedNames 中列出匹配它的正则表达式。在 VM 混淆下,该标识符会经由保留表达式数组处理,并原样出现在输出中,例如:
用一个完整的、经 JSON 序列化的表达式替换该标识符。不要把值放进引号或反引号中:该标识符并不在字符串字面量内,因此注入的值会被 JS 引擎当作普通表达式解析。请使用可信的序列化器;当脚本嵌入 HTML 时要转义 <;并测试包含引号、反斜杠、换行、反引号和 ${...} 的值。
模式 2:reservedStrings 配合模板字面量占位符
最适合:Go / Jinja 风格的模板引擎,它们要求使用 {{ .Field }} 这类分隔符,而这些分隔符用反引号包裹后恰好是合法的 JS 文本。
占位符位于一个只有单个 quasi、不含插值的模板字面量中。在 VM 混淆下,它会经由保留表达式数组处理,并在输出中保留原始的反引号形式。
源代码
混淆器选项
界面
要保留上面源代码中的 {{.Config.FeatureFlags}} 占位符,请把下面的正则表达式添加到 Reserved Strings 字段中,使用单个反斜杠。界面会原样存储该值,因此与 JS 代码中不同,反斜杠无需双写:

API
混淆后
占位符被逐字节保留,包括两侧的反引号。
宿主端替换
把 {{.Config.FeatureFlags}} 替换为一个已针对模板字面量转义的 JSON 字符串。反引号内部的 " 无需转义,但载荷中的反斜杠、反引号或 ${ 仍会改变或提前结束该字面量,因此要转义这三者:
运行时:JSON.parse(`{"newCheckout":true,"darkMode":false}`),可以正常工作。
对于字符串值,为什么优先使用模板字面量而不是单引号/双引号?反引号不需要转义 JSON 载荷中的 "。这一点很重要,因为大多数服务端渲染的值都是 JSON,而 JSON 中充满了双引号。使用双引号占位符时,您需要转义每一个内部的 "(参见模式 3);使用反引号时,载荷只需转义少见的反斜杠、反引号和 ${。
模式 3:reservedStrings 配合带引号的字符串占位符
最适合:必须保持为 ES5(不能使用模板字面量)的源代码,或周边 API 需要普通字符串字面量的场景。
占位符是一个单引号或双引号字符串字面量。在 VM 混淆下,它会经由保留字符串数组处理,该数组以 JSON.stringify 序列化的 JS 数组形式输出,无论输入使用哪种引号,输出始终是双引号。
源代码
混淆器选项
界面
要保留上面源代码中的 {{.Page.Tags}} 占位符,请把下面的正则表达式添加到 Reserved Strings 字段中,使用单个反斜杠。界面会原样存储该值,因此与 JS 代码中不同,反斜杠无需双写:

API
混淆后
宿主端替换
由于占位符位于 JS 双引号字符串内,注入的 JSON 载荷必须转义其中的反斜杠和内部的 " 字符:
输出结果:
运行时:JSON.parse("[\"news\",\"tech\",\"release\"]") → ["news","tech","release"]。
如果忘记转义,浏览器会看到不成对的引号并抛出 SyntaxError。模式 2 使用反引号,完全避开了双引号问题。
同一程序中的多个占位符
三种模式可以组合使用。单个带有分支结构的 reservedStrings 正则表达式,就能匹配模板引擎输出的所有占位符形式,这里同时匹配 {{ .Field }} 和 %{ .Field } 两种风格:
您可以在同一份源代码中混用标识符占位符(用于原始 JS 值)和字符串占位符(用于渲染后的 JSON),并根据服务端实际要注入的内容为每个占位符分别选择。
兼容性说明
vmSelfDefending 会破坏混淆后的替换
VM Self Defending 会检测到构建完成后对混淆输出的任何改动,包括合法的模板替换,随后受保护的代码将拒绝运行。
如果您依赖宿主端模板替换,请设置 vmSelfDefending: false。同时也要关闭 selfDefending:它在 VM 混淆下不起作用,但在不使用 VM 时,它同样禁止对输出做任何修改。
占位符提示
- 不要在标识符和字符串之间复用占位符。像
__TOKEN__这样的保留名称,与匹配__TOKEN__的保留字符串,对应的是两条不同的代码路径(保留表达式数组与保留字符串数组)。请为两者使用不同的文本形式,例如标识符占位符采用UPPER_SNAKE约定,字符串占位符采用分隔符包裹的形式({{ ... }}、%{...}、<<<...>>>)。这样,一个正则表达式中的错误就不会静默地匹配到另一类占位符。 reservedStrings正则表达式针对原始字符串值进行匹配。正则表达式匹配的是字符串的运行时值,而不是源代码文本。\{\{[^}]+\}\}会匹配包含{{.something}}(或任何其他{{...}}形式)的字符串。如果您的占位符可能被额外内容包裹("prefix-{{.Field}}-suffix"),正则表达式仍会匹配,但整个字符串都会被保留,请据此规划替换逻辑。- 适用于任何模板引擎。虽然示例使用的是 Go 语法,但 javascript-obfuscator 集成中没有任何内容是 Go 专属的。任何能对混淆器输出做字符串级替换的工具都可以使用:Rails ERB、Django、PHP 短标签、CI 流水线中的 sed 等。请选择您的引擎天然输出、且不会与真实 JS 语法冲突的分隔符。
