扩展脚本(Script)与配置合并(Merge)进阶指引
利用 JavaScript 与 YAML Merge 动态改写第三方托管订阅。掌握 Clash Verge 内置 JS 运行时、配置动态插装、自定义规则追加与全自动节点筛选代码实战。
在网络工程实践中,用户通常需要从第三方服务商导入远程托管订阅。然而,这些由外部生成的订阅配置文件往往存在固有的局限性:服务商往往只提供粗放的通用分流规则,或者预设的 DNS 配置并不适配你的本地局域网环境;更棘手的是,一旦你在本地直接手动修改该配置,每次服务商发布节点更新或重新拉取订阅时,你所做的一切本地修改都将被完全冲刷覆盖。
Clash Verge 客户端 创造性地提供了 YAML Merge(配置合并) 与 JavaScript Script(扩展脚本) 两种分层改写机制。通过将自定义逻辑抽象为独立的代码模块,客户端在拉取并解析原始订阅的瞬时,会在内存中动态对其进行函数式流水线加工。
本文将深入拆解 Clash Verge 的脚本运行时上下文,并提供生产级 JavaScript 脚本范式,助你实现规则全自动追加、节点正则重组与高可用策略编排。
1. 动态改写架构演进:为什么需要与原始订阅解耦?
传统的配置管理模式面临三大不可调和的矛盾:
- 修改覆盖危机:手动追加的几百条直连规则,在点击一次“更新订阅”后瞬间归零。
- 多订阅维护成本:如果你同时订阅了多家服务商,为每个配置文件重复维护相同的自定义规则与 DNS 配置会导致严重的维护地狱。
- 缺乏动态计算能力:静态 YAML 无法根据节点名称中的倍率特征、国家地区或协议类型执行条件过滤与动态重组。
[ 远程服务商原始订阅 (Raw Profile) ]
│
▼ (点击更新或定时自动拉取)
[ Clash Verge 核心预处理器 (Pre-processor) ]
│
┌──────┴────────────────────────┐
▼ ▼
【方式 A: YAML Merge】 【方式 B: JavaScript Script】
以纯声明式键值覆盖 以纯函数式图灵完备代码
进行浅层/深层字典合并 执行遍历、正则过滤与算法重组
│ │
└──────────────┬────────────────┘
▼
【内存中生成的最终运行时配置 (Runtime Config)】
│
▼ (传递给底层的 Mihomo 核心启动)
[ 具备完整个人客制化与防冲刷特性的代理内核 ]
通过这一架构,用户的个人配置逻辑与服务商的节点数据实现了彻底的关注点分离(Separation of Concerns)。
2. YAML Merge vs JavaScript Script:双重机制横向对比
在开始编写代码前,首先需要根据业务场景选择最适宜的客制化方式:
| 特性维度 | YAML Merge (配置合并) | JavaScript Script (扩展脚本) |
|---|---|---|
| 核心机制 | 静态声明式 YAML 字典合并 | 图灵完备的动态 JavaScript 脚本执行 |
| 上手门槛 | 极低(只需掌握基础 YAML 语法) | 需要基础编程能力与 JS 对象操作经验 |
| 规则增补 | 适合全局追加一段固定的静态参数 | 适合按条件(如只对特定前缀节点)动态挂载 |
| 节点筛选 | 不支持(无法读取并过滤动态节点数组) | 极其强大(支持完整的 RegExp 正则、数组映射与排序) |
| 容错机制 | 语法错误会被 YAML 解析器阻断 | 支持 try/catch 异常自愈,防止启动崩溃 |
| 适用场景 | 统一修改 DNS 或开启 TUN 模式 | 重构策略组拓扑、按倍率剔除节点、动态规则编译 |
3. JavaScript 扩展脚本执行环境与生命周期模型
在 Clash Verge 中,每个脚本文件本质上是一个遵循特定契约的 CommonJS / ES 模块。当脚本被激活并绑定至某个配置文件时,运行时沙箱会执行约定的入口函数:
// Clash Verge 扩展脚本标准接口定义
/**
* 配置处理入口函数
* @param {Object} config - 原始订阅反序列化后的完整 JavaScript 对象
* @param {Object} profile - 当前订阅的元数据上下文 (包含 name, url, selected 等)
* @returns {Object} 处理完成后的最终配置对象
*/
function main(config, profile) {
// 必须返回一个结构完整的 config 对象
return config;
}
3.1 沙箱环境技术约束:
- 纯同步调用:
main函数必须是纯同步函数,目前不支持异步async/await或发起外网 HTTP 异步请求,以确保客户端启动与配置热加载的毫秒级时延。 - 纯内存变异:函数接收的
config是一个纯粹的 JSON 友好型对象。你可以直接在内存中对其属性进行增删改查。 - 不可变容错原则:如果脚本执行期间抛出未捕获的未受检异常(Uncaught Exception),Clash Verge 会回退并直接采用原始未改写的配置,同时在控制台记录错误,避免导致核心彻底闪退。
4. 生产级 JavaScript 脚本实战案例集锦
以下提供了三个直接可在生产环境中复用的工业级扩展脚本。
4.1 实战一:在第三方订阅顶部安全插入自定义直连与拦截规则
此脚本可确保你的私有网络规则永远处于匹配的最顶端,不受服务商自带规则的影响:
function main(config, profile) {
// 1. 定义私有高优先级规则列表
const customPrependRules = [
// 强制广告拦截
"DOMAIN-SUFFIX,doubleclick.net,REJECT",
"DOMAIN-KEYWORD,adservice,REJECT",
// 内部私有云与开发环境直连
"DOMAIN-SUFFIX,corp.internal,DIRECT",
"IP-CIDR,10.50.0.0/16,DIRECT,no-resolve",
// 常用开发工具镜像直连
"DOMAIN-SUFFIX,npmmirror.com,DIRECT",
"DOMAIN-SUFFIX,goproxy.cn,DIRECT"
];
// 2. 防御性检查: 确保原始配置包含 rules 数组
if (!Array.isArray(config.rules)) {
config.rules = [];
}
// 3. 将自定义规则解构拼接至规则数组最前端 (保证最高匹配优先级)
config.rules = [...customPrependRules, ...config.rules];
return config;
}
4.2 实战二:按正则自动过滤节点并动态构建地区策略组
此脚本会自动扫描订阅中的所有节点,剔除名称中带有“高倍率”或“测试”字样的节点,并将香港、日本、新加坡的优质节点按地区自动归类为专属子策略组:
function main(config, profile) {
// 防御性校验
if (!Array.isArray(config.proxies) || config.proxies.length === 0) {
return config;
}
// 1. 过滤掉无意义或过高倍率的节点
const validProxies = config.proxies.filter(p => {
const name = p.name || "";
// 过滤包含 "官网", "剩余流量", "重置", "3倍", "5倍" 的节点
return !/(官网|剩余|流量|重置|[3-9]倍|[1-9]\d倍)/i.test(name);
});
// 提取清洗后的节点名称集合
const allProxyNames = validProxies.map(p => p.name);
// 2. 按地区正则匹配分类
const hkNodes = allProxyNames.filter(n => /(香港|HK|Hong Kong)/i.test(n));
const jpNodes = allProxyNames.filter(n => /(日本|JP|Tokyo|Osaka)/i.test(n));
const sgNodes = allProxyNames.filter(n => /(新加坡|SG|Singapore)/i.test(n));
// 3. 构建现代分层策略组
const regionalGroups = [
{
name: "🇭🇰 香港自动选路",
type: "url-test",
url: "http://cp.cloudflare.com/generate_204",
interval: 300,
tolerance: 50,
proxies: hkNodes.length > 0 ? hkNodes : ["DIRECT"]
},
{
name: "🇯🇵 日本自动选路",
type: "url-test",
url: "http://cp.cloudflare.com/generate_204",
interval: 300,
tolerance: 50,
proxies: jpNodes.length > 0 ? jpNodes : ["DIRECT"]
},
{
name: "🇸🇬 新加坡自动选路",
type: "url-test",
url: "http://cp.cloudflare.com/generate_204",
interval: 300,
tolerance: 50,
proxies: sgNodes.length > 0 ? sgNodes : ["DIRECT"]
}
];
// 4. 重塑主策略组
const mainProxyGroup = {
name: "🚀 节点选择",
type: "select",
proxies: [
"🇭🇰 香港自动选路",
"🇯🇵 日本自动选路",
"🇸🇬 新加坡自动选路",
"DIRECT",
...allProxyNames
]
};
// 5. 替换并挂载策略组
config.proxies = validProxies;
config["proxy-groups"] = [
mainProxyGroup,
...regionalGroups,
// 保留原始配置中可能存在的非重复策略组
...(config["proxy-groups"] || []).filter(g => g.name !== "🚀 节点选择")
];
return config;
}
4.3 实战三:统一强制覆写全局 DNS 模块与 TUN 模式
无论服务商自带的 DNS 配置多么简陋,此脚本都会将其强制提升为符合企业级防污染标准的双轨体系(与 DNS 防污染指南 和 TUN 模式配置手册 深度协同):
function main(config, profile) {
// 强制全量覆盖 DNS 架构
config.dns = {
enable: true,
listen: "127.0.0.1:1053",
ipv6: false,
"enhanced-mode": "fake-ip",
"fake-ip-range": "198.18.0.1/16",
"default-nameserver": ["223.5.5.5", "119.29.29.29"],
nameserver: [
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query"
],
fallback: [
"https://1.1.1.1/dns-query",
"https://8.8.8.8/dns-query"
],
"fallback-filter": {
geoip: true,
"geoip-code": "CN"
}
};
// 强制开启安全 TUN 模式参数
config.tun = {
enable: true,
stack: "mixed",
device: "MetaTunDevice",
"auto-route": true,
"auto-detect-interface": true,
"dns-hijack": ["any:53", "tcp://any:53"],
"strict-route": true,
mtu: 1420
};
return config;
}
5. 脚本调试艺术:日志捕获与常见报错排障
编写脚本时,最令人头疼的是语法错误导致配置静默失效。利用现代调试技巧可以快速定位故障点。
[ 编写/修改 JS 脚本 ] ───▶ [ 点击 Clash Verge 重新加载配置 ]
│
├───▶ (语法错误 / 空指针异常)
│ │
│ ▼
│ 【控制台弹出红色警告 / 捕获堆栈】
│ │
│ ▼
│ 【执行 try/catch 防御自愈】
│
└───▶ (处理成功) ───▶ 【配置热加载生效】
5.1 常用排障手段:
- 打印内部变量:虽然沙箱环境限制了部分终端 I/O,但在脚本中执行
console.log(JSON.stringify(config.proxies[0]))会将输出打印在 Clash Verge 的应用日志中。详情可参阅 日志查看与连接状态实时监控诊断指南。 - 规避对象深拷贝陷阱:在对
config.rules进行变异时,避免直接修改循环中的引用,推荐使用扩展运算符[...array]或解构赋值,防止意外污染原型链。 - 异常全面包裹机制:对于生产级脚本,建议用
try ... catch包裹核心逻辑,并在异常发生时安全降级:function main(config, profile) { try { // 执行核心变异操作... } catch (err) { console.error("扩展脚本执行发生致命异常: " + err.message); // 发生异常时原样返回,确保不断网 return config; } return config; }
6. 基础设施对自动化脚本的底层支持与专线选型
许多高级自动化脚本(如自动测速容灾、根据协议归类策略)的稳定运行,高度依赖服务商底层节点命名的规范性与线路架构的确定性。
6.1 劣质服务商对动态脚本的破坏性影响
- 随意更改节点命名:一些缺乏工程化运维的小作坊,经常在节点名字中随意插入 emoji、临时推广标语或打乱国家代码前缀,导致基于正则表达式的脚本全部失效。
- 节点大面积失活:若服务商后端频繁宕机,
url-test脚本会在策略组中频繁触发故障转移,导致 TCP 连接不断重置,带来极差的上网体验。
6.2 规范化企业级专线服务商的代码友好度
具备高工业水准的基础设施提供商,拥有严格的持续交付标准:
| 评估维度 | 不规范的普通服务商 | 工业级 IEPL 专线服务商 |
|---|---|---|
| 节点命名规范度 | 随意掺杂乱码与营销词汇,导致正则经常失效 | 遵循标准 ISO 国家代码与层级编号,正则极其健壮 |
| 底层协议原生性 | 协议参数不规范,脚本解析容易抛出格式错误 | 标准化 Clash Meta/VLESS/Reality 协议,无缝契合 |
| 节点可用率 (SLA) | 经常大面积红显超时,导致自动选路组雪崩 | 99.9% 以上高 SLA 保障,策略组心跳探活几乎零丢包 |
如果希望你的自动化脚本体系发挥出最大效能,建议接入具备高规范度与专线保障的基础设施:
- 查看具备高规范化节点池的服务商列表:28 机场品牌库全景对比矩阵
- 考察具备清晰节点架构与高可用专线的 光速云网络评测 与高性价比的 飞猫云线路解析
- 掌握从开发者视角评估订阅健康度的核心指南:订阅服务全景选购指南
7. 常见问题深度解答 (FAQ) 与相关技术链路闭环
Q1: 为什么脚本修改完成后,保存并应用时报错 main is not defined?
这说明脚本文件中遗漏了标准入口函数声明,或者函数名大小写错误。请确保你的代码中包含且仅包含一个名为 `function main(config, profile)` 的全局顶级导出函数。
Q2: 多个脚本文件的执行顺序是怎样的?会互相冲突吗?
在 Clash Verge 中,如果为一个订阅关联了多个扩展脚本,系统会按照列表中的上下排列顺序以管道流(Pipeline)方式依次链式执行前一个脚本的输出作为后一个脚本的输入。请注意将最基础的 DNS/TUN 配置脚本排在最前,将复杂的策略组重组脚本排在最后。Q3: 遇到订阅完全拉取失败、无法加载脚本上下文怎么办?
请参考 [订阅链接导入与配置更新完整操作指南](/tutorials/import-subscription) 检查远程链接合法性,或参考 [订阅更新失败网络排障](/troubleshooting/subscription-update-failed) 定位 TLS 证书或网络阻断问题。下一步进阶阅读与技术链路闭环:
- 将改写好的规则落到实处:详细了解每一种规则原语的匹配权重,请参阅 生产级分流规则体系构建指南。
- 让改写后的配置接管系统全量网络:深入了解内核级虚拟网卡调优,请阅读 TUN 虚拟网卡全量流量接管配置教程。
- 夯实客户端基础配置:如果是刚接触客户端的新手,建议先温习 Clash Verge 零基础新手快速入门教程。
延伸阅读与进阶指引 (相关推荐)
漏斗内链推荐 (3篇)DNS 防污染与防泄漏权威指南:Fake-IP vs Redir-Host 深度解析
深入剖析 Clash Verge 的 DNS 解析全流程。详述 RFC 3089 Fake-IP 映射机制、Redir-Host 局限、DoH/DoT 加密查询、防 DNS 泄漏与智能分流 Policy 生产级实战。
Clash Verge 内存泄漏、CPU 占用过高与卡顿深度优化指南
全面攻克 Clash Verge 长时间运行内存飙升、CPU 单核打满与大订阅滚动掉帧卡顿。深入 V8 垃圾回收、Mihomo 连接池句柄泄漏、虚拟滚动优化与轻量化规则集实战。
处理器架构全景指南:x86_64、ARM64、RISC-V 与 MIPS 选型适配
深度剖析现代 CPU 处理器架构对 Clash Verge 及代理内核的影响。涵盖 x86_64 (AMD64)、ARM64 (aarch64)、MIPS 与 RISC-V 架构特性、AES-NI 与 NEON 硬件加密加速实战。