@vizejs/plugin-sdk

中文文档
0.650.0发布于 2026/11/03MITTypeScriptES Module零依赖构建来源已验证

Deterministic authoring and isolated execution for Vize JavaScript plugins

原始使用文档
文档目录9 节
  1. Typed visitor capability descriptors
  2. Lint rules and autofixes
  3. Fact providers
  4. Native template transforms
  5. Formatter and output hooks
  6. Determinism, caches and costs
  7. Isolated callback sources
  8. Virtual TypeScript visitors
  9. Devtools trace visitors

@vizejs/plugin-sdk

Author synchronous rules, fact providers, native template transforms and compiled output hooks. Install @vizejs/native 0.650.0 or newer to execute plugins. The optional native peer lets this package install independently for authoring; its helpers alone do not compile or lint Vue files. Node 22 or newer is required.

Typed visitor capability descriptors

@vizejs/plugin-sdk/visitor defines the stable versioned descriptor boundary for visitor-style plugins. The v1 capability vocabulary covers SFC, template, script, style, virtual TypeScript, diagnostics, lint, format and devtools trace events. Each declared capability requires a matching synchronous callback, and TypeScript narrows the callback payload to that capability.

import {
  VISITOR_API_VERSION,
  defineVisitorPlugin,
  negotiateVisitorPlugin,
} from "@vizejs/plugin-sdk/visitor";

const observer = defineVisitorPlugin({
  apiVersion: VISITOR_API_VERSION,
  name: "virtual-ts-observer",
  version: "1.0.0",
  capabilities: ["virtual-ts", "diagnostics"] as const,
  cacheInputs: [],
  visitors: {
    "virtual-ts"(event) {
      console.log(event.payload.virtualFile, event.payload.mappings.length);
    },
    diagnostics(event) {
      console.log(event.payload.diagnostics.length);
    },
  },
});

negotiateVisitorPlugin(observer, {
  apiVersion: VISITOR_API_VERSION,
  capabilities: ["virtual-ts", "diagnostics"],
});

Definitions fail closed on unknown or duplicate capabilities, missing or undeclared callbacks, asynchronous functions, reserved cache-input names and API version mismatches. runVisitorFixture freezes one authored event and enforces the synchronous return contract for package tests.

This module only creates immutable descriptors and author fixtures. It does not register callbacks in the native compiler, linter or language server, so importing it adds no work to those paths. A production host must explicitly negotiate the descriptor and owns cancellation, deadlines, isolation and event construction. Virtual TypeScript and devtools trace observers use separate opt-in host entrypoints; importing this module never registers either callback globally.

Lint rules and autofixes

import * as native from "@vizejs/native";
import { definePlugin, applyFixes } from "@vizejs/plugin-sdk";

const rule = definePlugin({
  name: "team-rule",
  version: "1.0.0",
  visit: ["ui.bind"],
  demands: [],
  cacheInputs: [],
  rules: {
    check(ctx) {
      for (const node of ctx.nodes) {
        if (node.name === "disabled" && node.value === "true") {
          ctx.report(node, "Use a literal boolean attribute.", "disabled");
        }
      }
    },
  },
});
const output = native.lintWithPlugins(source, [rule], {
  filename: "Button.vue",
  cache: true,
  validateDeterminism: true,
});
const fixed = applyFixes(source, output.fixes);

A report names a visited L2 node. Rust assigns its authored UTF-8 byte range; a fix replaces that whole range. applyFixes preserves other source, deduplicates identical suggestions and refuses conflicting edits atomically. Apply suggestions deliberately, then rerun the compiler and linter.

Rules receive immutable batches. ctx.facts(name) requires the group in the static demands list; no undeclared table crosses the boundary. Native groups include templateScopes and the registered Croquis producers bindings, undefined-refs, component-usages, reactivity, provide-inject and race-conditions and unused-bindings. The latter computes its registered bindings dependency. The bindings and unused-bindings spans are authored SFC UTF-8 byte ranges, mapped by the shared producer even for reordered split scripts. Other primary position fields retain their Croquis analysis frame: undefined references/component usage are template-relative; reactivity, provide/inject and race positions are script-relative (the merged normal/setup script when both exist). Reports and fixes always use the host's SFC ranges. Public component interface pages are component-signature, prop-types, emit-types, slot-types, reactivity-classes and component-references. Each interface value preserves the producer's schema, nullable unknowns and type dependency completeness. The signature's props_complete separately records whether all property names were enumerated. The source-only SDK route has no resolved catalog proof, so it reports false without reading imported files from disk. Imported types which this analysis cannot resolve remain explicit unknowns. Primary Croquis facts require a JavaScript or TypeScript script; other script dialects are refused explicitly.

Plugin visits require inline HTML templates. Authored foreign template dialects and external template blocks are refused before callbacks; the SDK does not pretend their source is HTML or invent source maps for a preprocessing pass.

TypeScript infers each native group's exact map keys and deeply readonly values from ctx.facts("prop-types"), including authored interface order and all emit overloads. The package exports FactGroups, FactKey, FactValue, FactEntry and the individual native contracts. Custom provider groups can use ctx.facts<MyFact>("design/labels"); unknown groups default to unknown.

Fact providers

import { defineFactProvider, definePlugin } from "@vizejs/plugin-sdk";
const labels = defineFactProvider({
  name: "design",
  version: "1",
  provides: ["design/labels"],
  visit: ["ui.element"],
  demands: ["bindings"],
  cacheInputs: [],
  provide(batch) {
    return { "design/labels": batch.nodes.map((node) => [node.id, node.name]) };
  },
});
const consumer = definePlugin({
  name: "labels",
  version: "1",
  demands: ["design/labels"],
  cacheInputs: [],
  rules: {
    check(ctx) {
      const labels = ctx.facts("design/labels");
    },
  },
});
native.lintWithPlugins(source, [consumer], { cache: true, factProviders: [labels] });

A provider declares every output group as provider-name/group, its required facts and node visits. The host computes the dependency closure and refuses cycles, collisions, undeclared output and malformed tables before using facts. Each table contains [stringOrNumericKey, jsonValue] rows with unique keys. Provider dependency stamps enter the consumer cache key automatically.

Native template transforms

import { defineTransformPlugin } from "@vizejs/plugin-sdk";
const design = defineTransformPlugin({
  name: "design-system",
  version: "1",
  cacheInputs: [],
  transform(batch) {
    return batch.nodes
      .filter((node) =>
        node.attrs.some((attr) => attr.name === "class" && attr.value === "legacy-btn"),
      )
      .map((node) => ({
        kind: "replace-static-attribute",
        node: node.id,
        name: "class",
        value: "ds-button",
      }));
  },
});
const transformed = native.compileWithTransformPlugins(template, [design], {
  filename: "Button.vue",
  sourceMap: true,
  cache: true,
  cacheDir: ".vize-cache",
});

The hook runs at the native pre-canonical L2 point. It can replace existing static HTML attributes from the host's allowed set; dynamic attributes, structural edits, template carriers and foreign namespaces refuse. Values are semantic text, not entity spelling. Unknown targets or duplicate edits refuse the entire response. The compiler applies typed edits to its real arena and emits code and authored maps; no guest AST JSON is accepted. The result is a module with prefixed identifiers. plugins contains per-plugin costs.

Formatter and output hooks

import { defineOutputPlugin } from "@vizejs/plugin-sdk";
const license = defineOutputPlugin({
  name: "license",
  version: "1",
  family: "output",
  cacheInputs: [],
  output() {
    return [{ placement: "prepend", comment: "Copyright Example" }];
  },
});
const compiled = native.compileWithOutputPlugins(template, [license], options);
const decorated = native.applyOutputPlugins(transformed.result, [license], { cache: true });

family: "formatter" returns {start,end,text} edits using UTF-8 byte offsets. Only whitespace formatting which preserves the parsed program and comments is accepted. family: "output" appends or prepends ordinary block comments; source-map directives and executable additions refuse. The host rebases existing map anchors and preserves source content. Each pipeline key includes preceding output. Function-mode results carry preamble separately from code.

Determinism, caches and costs

Declare every captured configuration or ambient input in cacheInputs, even when the list is empty. Names beginning @vize/ are host-reserved. Source, filename, native build, SDK runtime bytes, plugin version/code, static demands, visits and inputs participate in keys. A cached lint miss always audits two identical batches. Providers and transforms audit misses. Cached output hooks also always audit; uncached output hooks audit by default. Different validated outputs refuse before caching. All hook families have bounded in-process caches and opt-in cacheDir persistence. Disk hits validate their schema, keys and host-confined outputs before reuse.

Costs include elapsed host work and JavaScript call time, including audit calls. A cache hit executes no callback; its jsNs and batch crossing bytes are zero. A determinism audit observes repeated output. Trusted callbacks can conceal ambient reads; their declarations remain the author's responsibility.

Isolated callback sources

import {
  createSandboxRunner,
  SANDBOX_IMAGE,
  SandboxRuntimeError,
} from "@vizejs/plugin-sdk/sandbox";
const isolated = createSandboxRunner(
  {
    name: "isolated-rule",
    version: "1",
    cacheInputs: [],
    configuration: { message: "Use our design system" },
    rules: { check: "(ctx, config) => { ctx.report(ctx.nodes[0], config.message); }" },
  },
  { timeoutMs: 5000, maxBytes: 1048576 },
);
native.lintWithPlugins(source, [isolated], { cache: true });

Install Docker, start its daemon and pull the exported pinned SANDBOX_IMAGE before use. Callback source and explicit JSON configuration cross stdin; host closures and imports do not. Each invocation uses a nonroot, read-only container without host mounts, host environment, external network or capabilities, with memory/CPU/PID/file-descriptor limits. The host bounds request/output bytes and execution time, then confirms container removal. Runtime checking and cleanup each add at most five seconds to timeoutMs.

SandboxRuntimeError.code distinguishes runtime_unavailable, execution_stopped, execution_failed and cleanup_failed. Unavailable Docker or a missing image refuses execution; it never runs the guest as trusted code. The image's own filesystem remains readable. Docker supplies the isolation; this API does not claim virtual-machine isolation.

definePlugin and the other ordinary helpers execute trusted application code synchronously in the calling Node process and have no execution deadline. Freezing batches and auditing output do not isolate those callbacks. The measured proxy handle remains for spike compatibility. @vizejs/extension-sdk is the separate versioned WIT guest interface.

Virtual TypeScript visitors

defineVisitorPlugin descriptors with only the virtual-ts capability can be negotiated for the native type-check host. The ordinary typeCheck entrypoint remains plugin-free; callers opt in through the separate visitor entrypoint.

import { typeCheckWithVirtualTsVisitors } from "@vizejs/native";
import {
  VISITOR_API_VERSION,
  createVirtualTsVisitorHost,
  defineVisitorPlugin,
} from "@vizejs/plugin-sdk/visitor";

const observer = defineVisitorPlugin({
  apiVersion: VISITOR_API_VERSION,
  name: "virtual-ts-observer",
  version: "1",
  capabilities: ["virtual-ts"],
  cacheInputs: [],
  visitors: {
    "virtual-ts"({ payload }) {
      console.log(payload.virtualFile, payload.code, payload.mappings);
    },
  },
});

typeCheckWithVirtualTsVisitors(source, [createVirtualTsVisitorHost(observer)], {
  filename: "src/App.vue",
});

The callback receives immutable generated code and source/generated UTF-8 byte ranges after the real type-check projection is built. Returning a value, requesting another capability, or forging an incomplete host identity refuses the invocation.

Devtools trace visitors

devtools-trace descriptors attach explicitly to a request-local @vizejs/devtools recorder. The recorder serializes each committed trace event through the negotiated adapter; disabled recorders do not invoke configured hosts, and ordinary recorders without visitorHosts do not inspect plugin descriptors.

import { createDevtoolsTraceRecorder } from "@vizejs/devtools";
import {
  VISITOR_API_VERSION,
  createDevtoolsTraceVisitorHost,
  defineVisitorPlugin,
} from "@vizejs/plugin-sdk/visitor";

const observer = defineVisitorPlugin({
  apiVersion: VISITOR_API_VERSION,
  name: "trace-observer",
  version: "1",
  capabilities: ["devtools-trace"],
  cacheInputs: [],
  visitors: {
    "devtools-trace"({ file, payload }) {
      console.log(file, payload.events);
    },
  },
});

const trace = createDevtoolsTraceRecorder({
  sessionId: "request-1",
  visitorHosts: [createDevtoolsTraceVisitorHost(observer)],
});

Every delivered event has a deterministic session/sequence ID, a stage derived from its trace kind, millisecond timestamps normalized to nanoseconds, and the remaining recorder fields in immutable data. A visitor exception or non-void result refuses the record before it enters the snapshot.

版本与发布记录

@vizejs/plugin-sdk 共发布 195 个版本,首次发布于 2026/09/27;过去 12 个月发布了 195 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.650.0latest2026/11/03今天
0.649.02026/11/03今天
0.648.02026/11/021 天前
0.647.02026/11/021 天前
0.646.02026/11/021 天前
0.645.02026/11/021 天前

关于 @vizejs/plugin-sdk 的常见问题

@vizejs/plugin-sdk 是什么?

确定性编写和独立执行的Vize JavaScript插件

如何安装 @vizejs/plugin-sdk?

在项目目录运行 npm install @vizejs/plugin-sdk,或使用 pnpm add / yarn add / bun add。

@vizejs/plugin-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@vizejs/plugin-sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@vizejs/plugin-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@vizejs/plugin-sdk 可以商用吗?

@vizejs/plugin-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@vizejs/plugin-sdk 还在维护吗?

最新版本 0.650.0 发布于 2026/11/03(今天);至今共发布 195 个版本,其中过去 12 个月发布了 195 个;首次发布于 2026/09/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。