news 2026/10/3 1:55:31

ScriptCat Cloud Script Export 深度指南:将用户脚本连同数据打包为可独立执行的 Node.js 归档

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ScriptCat Cloud Script Export 深度指南:将用户脚本连同数据打包为可独立执行的 Node.js 归档
  • 前端
  • 开发者工具
  • 插件系统

【免费下载链接】scriptcat

ScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展

项目地址:https://gitcode.com/gh_mirrors/sc/scriptcat
点击查看免费下载

ScriptCat 的 Cloud Script Export 模块(packages/cloudscript/)负责把单个用户脚本连同其GM_*values、cookies 和元数据一起打包为一个可独立执行的 zip 归档,供脚本迁移或离线执行使用。读完本文,你将掌握该模块的架构与类型定义、导出参数(value/cookie 表达式)的语法规则、打包产物结构与运行方式,并能通过源码级证据理解"本地打包执行"与"云端同步备份"两条路径的根本区别。

定位:本地打包,而非云端同步

packages/cloudscript/README.md开篇就强调了一个容易混淆的关键点:Cloud Script Export 是把单个脚本连同其数据打包为可独立执行的归档,而不是同步到远端云存储,它与packages/filesystem提供的云同步完全是两回事。

  • Cloud Script Export(本文主题):产出物是一个 zip 归档,落地到本地(当前唯一 target 为local),不涉及任何远端上传,目的是脚本迁移与离线执行;
  • 云端同步/备份:由 packages/filesystem 提供,支持 WebDAV、OneDrive、Google Drive、Dropbox、百度网盘、S3 等存储后端,语义细节见 docs/cloud-sync.md。

两者可以这样区分:一个负责"把脚本包成一个可运行的程序带走",另一个负责"把脚本仓库同步到远端存储"。下文所有内容均围绕前者展开。

架构总览:工厂 + Target 实现

cloudscript包采用"工厂 + 目标实现"的结构,核心文件有三个:

文件职责
packages/cloudscript/cloudscript.ts定义导出接口CloudScript、参数类型ExportParams、cookie/value 表达式解析函数
packages/cloudscript/factory.tsCloudScriptFactory工厂,按ExportTarget创建对应实现
packages/cloudscript/local.tsLocalCloudScript,将脚本与数据写入 zip 的本地打包实现

在 src/app/repo/export.ts 中可以看到导出目标的类型定义:

export type ExportTarget = "local" | "tencentCloud";

ExportTarget目前声明了local与tencentCloud两个可能值,但工厂侧只落地了local。在 packages/cloudscript/factory.ts 中:

export default class CloudScriptFactory { static create(type: ExportTarget, params: ExportParams) { switch (type) { case "local": return new LocalCloudScript(params); default: throw new Error(`unknown type ${type}`); } } static params(): { [key: string]: CloudScriptParams } { return { local: {} }; } }

从源码结构看,tencentCloud是预留的扩展点,目前调用CloudScriptFactory.create("tencentCloud", ...)会直接抛出unknown type异常。扩展一个新的导出目标只需:在ExportTarget中追加类型、在create()的 switch 中注册对应实现类、在params()中补充该目标需要的 UI 参数声明(title/type: "select"/options)。

LocalCloudScript实现了统一的CloudScript接口:

export default interface CloudScript { exportCloud(script: Script, code: string, values: Value[], cookies: ExportCookies[]): Promise<void>; }

该接口的四个入参正是导出包的四大数据来源:脚本对象、脚本源码、已筛选的 values、已筛选的 cookies。

导出参数与表达式语法

ExportParams是导出行为的所有可调参数(packages/cloudscript/cloudscript.ts):

export type ExportParams = { [key: string]: any; exportValue: string; // value 导出表达式 exportCookie: string; // cookie 导出表达式 overwriteValue: boolean; // 导入时是否覆盖原 value overwriteCookie: boolean; // 导入时是否覆盖原 cookie };

参数明细如下:

参数类型默认值含义
exportValuestring脚本@exportvalue元数据(无则为空字符串)指定要导出哪些GM_*values 的 key 表达式
exportCookiestring脚本@exportcookie元数据(无则为空字符串)指定要导出哪些域下 cookies 的表达式
overwriteValuebooleanfalse运行时(utils.js导入逻辑)是否覆盖原有 value
overwriteCookiebooleanfalse运行时(utils.js导入逻辑)是否覆盖原有 cookie

value 表达式:一行一个 key,逗号分隔

parseExportValue(script, exportValue)负责解析 value 表达式。语法规则:按换行分隔,每一行内再用逗号分隔多个 key,只导出当前脚本中真实存在的 key。

export async function parseExportValue(script: Script, exportValue: string): Promise<Value[]> { const lines = exportValue.split("\n"); const values = await valueClient.getScriptValue(script); for (const line of lines) { if (line.trim()) { const s = line.split(","); for (const key of s) { const k = key.trim(); if (k && values[k]) { result.push(values[k]); } } } } return result; }

示例表达式(三行,第一行含两个 key):

apiToken theme,language lastLoginAt

解析时theme与language都会被导出。由于有values[k]的存在性校验,不存在的 key 会被静默跳过。

cookie 表达式:一行一个站点,分号分隔参数

parseExportCookie(exportCookie)的语法规则:按换行分隔站点,每个站点行内用分号分隔key=value参数,必须提供url或domain之一才会触发抓取,抓取结果挂在cookies字段上。

export function parseExportCookie(exportCookie: string): Promise<ExportCookies[]> { const lines = exportCookie.split("\n"); for (const line of lines) { const detail: ExportCookies = {}; for (const param of line.split(";")) { const s = param.split("="); if (s.length !== 2) continue; detail[s[0].trim()] = s[1].trim(); } if (detail.url || detail.domain) { result.push( new Promise<ExportCookies>((resolve) => { getCookies(detail).then((cookies) => { detail.cookies = cookies; resolve(detail); }); }) ); } } return Promise.all(result); }

getCookies内部走chrome.cookies.getAll,并处理了chrome.runtime.lastError(记录错误后继续执行,不阻断导出流程)。实际抓取时传入的参数即chrome.cookies.GetAllDetails所支持的条件(url、domain、name等)。

示例表达式(两行):

url=https://example.com;name=session_id domain=.example.org

ExportCookies的结构为:

export type ExportCookies = { [key: string]: any; domain?: string; url?: string; cookies?: chrome.cookies.Cookie[]; };

默认值来源:脚本元数据

在 src/pages/components/CloudScriptPlan/index.tsx 中,导出对话框打开时会从脚本 metadata 读取默认表达式:

export function cloudDefaultParams(script: Script): Pick<ExportParams, "exportValue" | "exportCookie"> { return { exportValue: script.metadata.exportvalue?.[0] ?? "", exportCookie: script.metadata.exportcookie?.[0] ?? "", }; }

即脚本头部元数据@exportvalue与@exportcookie可以作为导出表达式的默认值,用户仍可在导出对话框中手动覆盖。

打包产物:zip 内部结构

LocalCloudScript.exportCloud是打包的核心(packages/cloudscript/local.ts),它把六类内容写入 zip:

exportCloud(script: Script, code: string, values: Value[], cookies: ExportCookies[]): Promise<void> { this.zip.file("userScript.js", code); this.zip.file("cookies.js", `exports.cookies = ${JSON.stringify(cookies)}`); this.zip.file("values.js", `exports.values = ${JSON.stringify(values)}`); this.zip.file("config.js", `export default ${JSON.stringify({ version: ExtVersion, uuid: script.uuid, overwrite: { value: this.params.overwriteValue, cookie: this.params.overwriteCookie, }, })}`); this.zip.file("package.json", <string>packageTpl); this.zip.file("utils.js", <string>utilsTpl); this.zip.file("index.js", <string>indexTpl); return Promise.resolve(); }
归档内文件内容来源
userScript.js用户脚本完整源码ScriptCodeDAO读取的脚本代码
cookies.jsexports.cookies = [...],即按表达式筛选出的 cookiesparseExportCookie的产物
values.jsexports.values = [...],即按表达式筛选出的 valuesparseExportValue的产物
config.js元数据:扩展版本、脚本 uuid、overwrite.value/overwrite.cookieExtVersion与导出参数
package.jsonnpm 包声明,依赖scriptcat-nodejssrc/template/cloudcat-package/package.tpl
utils.js运行时引导逻辑:读取源码与数据,驱动ScriptCat执行src/template/cloudcat-package/utils.tpl
index.jsnpm 入口:require('./utils')后调用utils.run()src/template/cloudcat-package/index.tpl

元数据 config.js

config.js记录了三个关键信息:

{ "version": "<扩展版本号>", "uuid": "<脚本uuid>", "overwrite": { "value": false, "cookie": false } }
  • version取自@App/app/const中的ExtVersion,标识打包时使用的 ScriptCat 扩展版本;
  • uuid是脚本的唯一标识,供恢复/迁移时关联原始脚本;
  • overwrite来自导出参数overwriteValue/overwriteCookie,由运行时导入逻辑消费。

package.json 与外部依赖

src/template/cloudcat-package/package.tpl 声明了包的基本信息:

{ "name": "cloudcat-package", "version": "1.0.0", "description": "scriptcat后台脚本打包项目", "main": "index.js", "scripts": { "run": "node index.js" }, "license": "MIT", "dependencies": { "scriptcat-nodejs": "^0.1.7" } }

关键点:package.json声明了外部依赖scriptcat-nodejs(^0.1.7),因此产出的包不是解压后即可直接node index.js运行的。

运行时引导 utils.js

src/template/cloudcat-package/utils.tpl 展示了实际的离线执行逻辑:

const fs = require('fs'); const { ScriptCat } = require("scriptcat-nodejs/dist/src/scriptcat"); const { ModelValues } = require("scriptcat-nodejs/dist/src/storage/values"); const { cookies } = require('./cookies'); const { values } = require('./values'); exports.run = function () { const code = fs.readFileSync('userScript.js', 'utf8'); const run = new ScriptCat(); run.RunOnce(code, { cookies: cookies, values: new ModelValues(values), }).then((res) => { console.log(res); }); }

执行时:

  1. 读取同目录userScript.js作为脚本源码;
  2. 读取cookies.js/values.js中的数据;
  3. 通过scriptcat-nodejs提供的ScriptCat.RunOnce在 Node.js 环境中运行脚本一次,values 用ModelValues包装以模拟GM_*value 存储;
  4. 执行结果res输出到控制台。

运行方式:两步走

由于依赖scriptcat-nodejs,产出的 zip 需要两步才能运行:

# 1. 解压归档 unzip <script-uuid>.zip -d cloudcat-package cd cloudcat-package # 2. 安装外部依赖 npm install # 3. 运行(等价于 node index.js) npm run run

npm run run由 index.tpl 驱动,最终调用utils.run()。这种"先安装依赖、再执行"的设计,使得归档可以在任意具备 Node.js 与 npm 的环境中离线运行脚本,达到脚本迁移或离线执行的目的。

完整调用链:从导出对话框到 zip 下载

从 src/pages/components/CloudScriptPlan/index.tsx 可以还原完整的导出流程:

  1. 读取导出计划:ExportDAO.findByScriptID(script.uuid)查询该脚本是否已有保存的导出计划(目标、参数);
  2. 组装参数:优先取已保存参数,否则回退到cloudDefaultParams(@exportvalue/@exportcookie元数据)与默认开关值;
  3. 解析数据:parseExportValue(script, params.exportValue)筛选 values,parseExportCookie(params.exportCookie)筛选 cookies;
  4. 创建 zip 与导出:CloudScriptFactory.create("local", { zip, ...params })创建LocalCloudScript,ScriptCodeDAO.findByUUID读取脚本源码,然后exportCloud(script, code, values, cookies)写入全部文件;
  5. 压缩与下载:zipFile.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 9 }, comment: "Created by Scriptcat" })生成 Blob,再通过chrome.downloads.download以saveAs: true、文件名${script.uuid}.zip触发下载。

值得注意的压缩细节:导出使用 DEFLATE 压缩、等级 9(最高压缩比),zip 包注释为Created by Scriptcat,相关实现见 src/pkg/utils/jszip-x.ts。

导出计划的持久化由ExportDAO(src/app/repo/export.ts)负责,以脚本uuid为主键保存target与各目标的参数快照,下次打开对话框时自动回填。

与 filesystem 云同步的边界(再次强调)

最后再次明确边界,避免误用:

  • 需要把脚本打包带走、离线执行或迁移:使用 Cloud Script Export(本文),产物是 zip,本地下载;
  • 需要把脚本同步/备份到 WebDAV、OneDrive、Google Drive、Dropbox、百度网盘、S3:使用 packages/filesystem,语义细节见 docs/cloud-sync.md。

Cloud Script Export 全程不产生任何远端网络上传,产出物完全由用户本地持有,这一点是它与云同步路径最本质的区别。

  • 前端
  • 开发者工具
  • 插件系统

【免费下载链接】scriptcat

ScriptCat, a browser extension that can execute userscript; 脚本猫,一个可以执行用户脚本的浏览器扩展

项目地址:https://gitcode.com/gh_mirrors/sc/scriptcat
点击查看免费下载

相关推荐

上一篇:Ant Design Vue Pro中的CI/CD流程搭建:自动化部署的最佳实践
下一篇:Qwen2.5-VL-3B:30亿参数重构多模态AI应用边界

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!