JavaScript 教程 · 第十四章

REX 模块说明

好消息:这一章是整个教程里最轻松的一章。因为 REX 的模块和你已经学过的 Surge 模块,基本上是“一家人”。

第一部分这一章学什么

一句话目标:知道 REX 模块和 Surge 模块基本同构(长得几乎一样)——学完 Surge 那一章,REX 的模块就能看懂八九成;再记住写 REX 模块的三条注意事项,这一章就过关了。

三条注意事项,先混个眼熟,后面还会细讲:

  1. 先拿模板改:写 REX 模块时,先找一个官方或社区现成的模块当模板,在它上面改,别白手起家。
  2. 先小范围测试:脚本先只对单个域名生效,确认 $done(脚本收尾的信号)的行为正常,再全量启用。
  3. 三端兼容用判端模板:同一个脚本想同时在 Surge、Loon、REX 上跑,用第十五章的判端模板。

第二部分概念:和 Surge 一脉相承

REX 是一个相对新的代理工具,它的模块体系直接沿用了 Surge 的设计:文件的骨架一样、声明的写法一样、脚本能用的接口也一样。你在 Surge 那一章学的东西,到这里几乎原样能用。

文件结构:一模一样的骨架

REX 模块文件同样是 #!name 开头(写模块名字),然后用方括号分成几段:

一个 REX 模块文件的样子
#!name=示例模块
#!desc=演示用的模块

[Rule]
DOMAIN,example.com,DIRECT

[URL Rewrite]
^https://old\.example\.com https://new.example.com 302

[Script]
示例脚本 = type=http-response, pattern=^https://api\.example\.com, script-path=demo.js, requires-body=true

[MITM]
hostname = api.example.com

这四段的名字、顺序、写法,和 Surge 模块完全一致。

脚本声明的参数:含义一致

[Script] 段里每个参数的意思,REX 和 Surge 也是一样的:

type
脚本在什么时候运行,比如 http-response 表示“等服务器回完数据之后运行”。
pattern
网址要匹配的样式(一段匹配规则),网址对上了才运行这个脚本。
script-path
脚本文件的位置,可以是本地文件,也可以是一个网址。
requires-body
要不要把请求或回应的正文交给脚本。写 true 就是要,脚本才能读到、改到正文内容。
argument
传给脚本的附加参数,脚本里可以取出来用,相当于给脚本捎句话。

脚本能用的接口:还是那一套

脚本里面和外界打交道的工具,REX 也沿用 Surge 的那套名字和用法:

$request这次请求的信息(网址、请求头等)
$response这次回应的信息(正文、状态等)
$httpClient让脚本自己去发一个网络请求
$persistentStore把数据存下来,下次运行还能读到
$notification弹一条通知给你看
$done脚本干完活了,用它收尾、把结果交回去

所以结论很简单:学完 Surge 那一章,REX 的模块就能看懂个八九成。剩下的那一两成,是下面这个提醒。

⚠ 要留意的一点 REX 相对新,还在快速更新。不同版本对个别字段、个别接口的支持程度可能有出入——比如某些 $done 的参数、定时任务 cron 的写法,在某个版本上可能还不支持或者写法略有不同。遇到“在 Surge 上好好的、搬到 REX 不灵”,先怀疑版本支持问题,去对照当前版本的说明。

写 REX 模块的三条注意事项

  1. 先拿模板改,别白手起家。找一个官方或社区现成的、能跑的 REX 模块,在它上面改名字、改域名、换脚本。骨架现成,不容易写错格式。
  2. 先小范围测试。新脚本先让 pattern 只匹配单个域名,跑通了、确认 $done 收尾行为正常(不卡住、不报错),再放开范围全量启用。
  3. 三端兼容用判端模板。同一个脚本想同时兼容 Surge、Loon、REX,不要写三份,用第十五章的判端模板:一份代码先判断自己在哪个 App 里,再走对应的写法。

第三部分动手试:并排对照找不同

下面是同一个功能的脚本声明,左边按 Surge 模块的写法,右边按 REX 模块的写法。你先自己逐字看一遍,再回答下面的问题。

Surge 模块 · [Script] 段
去广告 = type=http-response, pattern=^https://api\.example\.com/ads, script-path=remove-ads.js, requires-body=true
REX 模块 · [Script] 段
去广告 = type=http-response, pattern=^https://api\.example\.com/ads, script-path=remove-ads.js, requires-body=true

两行拆开看:等号左边是脚本起的名字;type 是运行的时机;pattern 是要匹配的网址;script-path 是脚本文件;requires-body 表示需要正文。

问题:这两行声明,有什么不同?

再试一个:用代码检查模块段落齐不齐

一个模块文件最容易出的错,是漏写了某一段。下面这段 JavaScript 用字符串的 includes 方法(意思是“看看有没有包含”)逐个检查 [Rule]、[Script]、[MITM] 三段在不在。你可以改下面的模块文本,再点“运行”看结果。

检查段落的 JavaScript 代码
// 要检查的三个段落
let 段落 = ["[Rule]", "[Script]", "[MITM]"];
let 缺的 = [];

// 逐个检查:文本里包含这一段吗?
for (let 段 of 段落) {
  if (文本.includes(段)) {
    console.log("✓ " + 段 + " 有");
  } else {
    console.log("✗ " + 段 + " 缺了");
    缺的.push(段);
  }
}

if (缺的.length === 0) {
  console.log("三个段落齐了!");
} else {
  console.log("还缺:" + 缺的.join("、"));
}

模块文本(可以直接改,比如把某一行删掉再运行):

看懂这段代码的关键就一个词:includes。它问的是“这段长文本里,包不包含这个小词”,答案只有 true(包含)或 false(不包含)两种。

第四部分练一练

共 5 道题,点选项立刻判对错答对 0 / 5

第五部分适用案例

案例一

把一个 Surge 模块改成 REX 模块,要动哪几处?

  1. 先把 Surge 模块和一个现成的 REX 模块并排对照:你会发现 #!name 开头和四段结构完全一样,先放心。
  2. [Rule]、[URL Rewrite]、[Script] 的声明原样保留,参数不用改。
  3. 只逐个核对个别字段:这个模块如果用到了 cron 定时、或者 $done 的特殊参数,去查当前 REX 版本支不支持、写法是否相同。
  4. [MITM] 的 hostname 照抄,然后按案例二的方法小范围测一遍再全量启用。
案例二

新脚本先在小范围测试 $done,再全量启用

  1. 把脚本声明里的 pattern 先只写单个域名,比如只匹配 ^https://api\.example\.com/test 这一个测试地址。
  2. 实际访问一次,看脚本是否正常运行、$done 是否正常收尾:页面能正常打开、不转圈卡住、不报错,就是通过。
  3. 确认没问题后,再把 pattern 放开到正式范围,全量启用。出问题时影响面也只限于那一个域名,好排查。
案例三

一份脚本同时跑 Surge、Loon、REX

  1. 你有一个去广告脚本,想在三个 App 里都用——不要复制三份分别维护,改一处就要改三次,早晚改漏。
  2. 用第十五章的判端模板:脚本开头先判断自己正跑在哪个 App 里,再调用那个 App 对应的写法。
  3. 三个 App 的模块里,script-path 都指向这同一份脚本。以后修 bug、加功能,只改这一个文件,三端同时生效。
一句话小结:REX 模块和 Surge 模块是一家人——结构一样、声明一样、接口一样,你已经会了八九成;剩下的就是记住三条:先拿模板改、先小范围试、三端兼容用判端模板。
下一章预告
第十五章:多端兼容写法

学判端模板:让同一份脚本自己认出在 Surge、Loon 还是 REX 里,三端一份代码搞定。