swc-plugindev-canyon
v0.1.0
Published
[Canyon](https://github.com/canyon-project/canyon) 生态下的 **SWC 覆盖率后处理插件**。它在 `swc-plugin-coverage-instrument` 完成代码插桩之后运行,对 AST 中的 `coverageData` 对象做精简、注入构建元数据,并可在 CI 环境中写出初始覆盖率文件。
Readme
swc-plugindev-canyon
Canyon 生态下的 SWC 覆盖率后处理插件。它在 swc-plugin-coverage-instrument 完成代码插桩之后运行,对 AST 中的 coverageData 对象做精简、注入构建元数据,并可在 CI 环境中写出初始覆盖率文件。
本仓库是 Canyon SWC 插件的开发/验证版本,产物为 WebAssembly(.wasm),通过 SWC 的 experimental plugins 机制加载。
功能概览
| 能力 | 说明 |
|------|------|
| 精简运行时 bundle | 默认移除 statementMap、fnMap、branchMap、inputSourceMap、hash、_coverageSchema 等大体积字段 |
| 注入 buildHash | 根据仓库元数据生成唯一构建标识,用于将运行时 hit 数据与对应构建版本关联 |
| 文件过滤 | 支持 include / exclude glob、extensions 扩展名白名单 |
| CI 输出 | 开启 ci 后,将带完整 map 与元数据的初始覆盖率写入 .canyon_output/ |
| 内容哈希 | 为源码及 statement 片段计算 contentHash,便于后端做源码版本校验 |
工作原理
Canyon 采用 「Hit 与 Map 分离」 策略:构建阶段保留完整 coverage map(供服务端存储),运行时 bundle 只携带精简后的 hit 计数结构,以减小体积。
源码
│
▼
swc-plugin-coverage-instrument ← 第一步:插桩,生成 coverageData(含 _coverageSchema)
│
▼
swc-plugindev-canyon ← 第二步:精简 / 注入 buildHash / 可选写 CI 文件
│
▼
最终产物(dist / 部署包)在 CI 模式 下,插件会在变异 coverageData 之前,先将包含完整 map 的覆盖率快照序列化为 JSON,写入:
.canyon_output/coverage-final-init-{随机后缀}.json该文件可由 Canyon CLI 上传至服务端,与运行时收集的 hit 数据合并。
环境要求
- Rust(含
wasm32-wasip1target) - Node.js ≥ 18(用于本地测试脚本)
- @swc/core ≥ 1.x
- swc-plugin-coverage-instrument(插桩前置插件)
安装 Rust WASM 目标
rustup target add wasm32-wasip1构建
# 编译 release 版 WASM
cargo build-wasip1 --release
# 产物路径
# target/wasm32-wasip1/release/swc_plugindev_canyon.wasm也可通过 npm 脚本触发构建:
npm run prepack在 SWC 中使用
插件必须排在 swc-plugin-coverage-instrument 之后,对已经插桩的代码做二次 transform。
import swc from "@swc/core";
const coverageWasm =
"node_modules/swc-plugin-coverage-instrument/target/wasm32-wasip1/release/swc_plugin_coverage.wasm";
const canyonWasm =
"target/wasm32-wasip1/release/swc_plugindev_canyon.wasm";
const pluginConfig = {
repoId: "9050",
sha: "abc123def456",
provider: "gitlab",
buildTarget: "production",
ci: true,
keepMap: false,
include: ["src/**/*.js"],
exclude: ["**/*.test.js"],
instrumentCwd: "/path/to/project",
};
// 第一步:插桩
const { code: instrumented } = await swc.transform(source, {
filename: "/path/to/project/src/foo.js",
jsc: {
experimental: {
plugins: [[coverageWasm, {}]],
},
},
});
// 第二步:Canyon 后处理
const { code } = await swc.transform(instrumented, {
filename: "/path/to/project/src/foo.js",
jsc: {
experimental: {
plugins: [[canyonWasm, pluginConfig]],
},
},
});在 .swcrc 中配置时,需保证两个插件按上述顺序出现在 jsc.experimental.plugins 数组中,且 Canyon 插件位于 coverage-instrument 之后。
配置项
所有配置通过 SWC plugin 的第二个参数传入,字段名为 camelCase JSON:
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| repoId | string | "" | 仓库 ID(如 GitLab CI_PROJECT_ID) |
| sha | string | "" | Git commit SHA |
| provider | string | "" | 代码托管平台,如 gitlab、github |
| buildTarget | string | "" | 构建目标标识(如 production、staging) |
| ci | boolean | false | 为 true 时写出 .canyon_output/coverage-final-init-*.json |
| instrumentCwd | string | "." | 插桩根目录,用于路径归一化及 buildHash 计算 |
| include | string[] | [] | 需要处理的文件 glob;为空表示不过滤 include |
| exclude | string[] | [] | 排除的文件 glob |
| extensions | string[] | 见下方 | 允许处理的文件扩展名 |
| keepMap | boolean | false | 为 true 时保留 statement/fn/branch map,不精简 bundle |
默认 extensions:
.js .cjs .mjs .ts .tsx .jsx .vuebuildHash 生成规则
buildHash 是对以下字段按 字典序 序列化后做 SHA-1 的结果:
providerrepoIDshabuildTargetinstrumentCwd
相同配置在不同构建中会得到相同的 buildHash,用于 Canyon 服务端关联覆盖率 hit 与源码 map。
keepMap 与 ci 的组合
| keepMap | ci | 运行时 bundle | CI 产物 |
|-----------|------|---------------|---------|
| false | false | 精简,仅含 hit 计数 + buildHash | 无 |
| false | true | 精简 | 写出完整 map 至 .canyon_output/ |
| true | * | 保留完整 map | 按 ci 决定是否写文件 |
生产环境通常使用 keepMap: false + ci: true,在 bundle 体积与服务端 map 完整性之间取得平衡。
转换效果示例
插桩后(coverage-instrument 输出),coverageData 包含完整 map:
var coverageData = {
path: "/project/features/add.js",
statementMap: { /* ... */ },
fnMap: { /* ... */ },
branchMap: { /* ... */ },
_coverageSchema: "1a...",
s: { "0": 0, "1": 0 },
f: { "0": 0 },
b: { "0": [0, 0] },
};经本插件处理后(keepMap: false):
var coverageData = {
all: false,
path: "/project/features/add.js",
s: { "0": 0, "1": 0, "2": 0, "3": 0 },
f: { "0": 0 },
b: { "0": [0, 0] },
buildHash: "a7b7d1390cdbc0b473cf76bb6a2eaae88a07e718",
};未被 include 匹配的文件(如 features/jian.js 当 include 仅含 features/add.js 时)将跳过 Canyon 处理,保留 coverage-instrument 的原始输出。
本地开发与测试
# 安装依赖
npm install
# 构建 WASM 并运行端到端测试
npm run test-swc-plugin测试脚本 scripts/test-swc-plugin.mjs 会:
- 对
features/下所有.js文件先跑 coverage-instrument,再跑本插件 - 将结果写入
dist/ - 校验
features/add.js含buildHash且已移除statementMap - 校验未 include 的
features/jian.js未被 Canyon 处理 - 在
ci: true时检查.canyon_output/是否生成文件
运行 Rust 单元测试:
cargo test项目结构
swc-plugindev-canyon/
├── src/
│ ├── lib.rs # SWC plugin 入口(#[plugin_transform])
│ ├── visitor.rs # AST 访问器:识别 coverageData、触发 CI 输出
│ ├── coverage.rs # coverageData 识别、精简、序列化、文件 I/O
│ ├── config.rs # 插件配置解析与文件过滤逻辑
│ └── hash.rs # buildHash / contentHash 计算
├── features/ # 测试用示例源码
├── scripts/
│ └── test-swc-plugin.mjs
├── .cargo/config.toml # cargo build-wasip1 别名
├── Cargo.toml
└── package.json与 Canyon 生态的关系
本插件对应 Canyon monorepo 中的 SWC Plugin 能力,与以下组件配合使用:
- swc-plugin-coverage-instrument — 前置插桩
- @canyonjs/babel-plugin — Babel toolchain 下的等价方案
- @canyonjs/cli — 上传 CI 产物与运行时 hit 数据
更多概念说明见 Canyon 文档中的 Separate Hit and Map 设计。
许可证
ISC
