@arrow-js/sandbox

中文文档
1.0.6发布于 2026/04/01MITES Module构建来源已验证近 30 天下载 3240

QuickJS/WASM-backed sandbox runtime for executing Arrow templates outside the host window realm.

原始使用文档
文档目录13 节
  1. What this package does
  2. Install
  3. Basic usage
  4. Multi-file modules
  5. API
  6. Host bridge modules
  7. Sandbox output bridge
  8. Security model
  9. Sandboxed fetch
  10. Supported subset
  11. Unsupported or partial
  12. Current limitations
  13. Development

@arrow-js/sandbox

ArrowJS

ArrowJS sandbox executes user-authored Arrow JavaScript or TypeScript inside an async QuickJS/WASM VM while rendering through trusted host DOM code.

Docs · API Reference · Playground

What this package does

@arrow-js/sandbox lets you run untrusted Arrow code without executing that code in the page's window realm.

It provides:

  • an async QuickJS/WASM runtime for user-authored modules
  • AST-based preprocessing for implicit Arrow imports and template extraction
  • a sandbox-specific @arrow-js/core shim
  • a host DOM renderer and delegated event bridge

Install

pnpm add @arrow-js/sandbox

Basic usage

import { sandbox } from '@arrow-js/sandbox'

const view = sandbox({
  source: {
    'main.ts': `
      const state = reactive({ count: 0 })

      export default html\`<button @click="\${() => state.count++}">
        Clicked \${() => state.count}
      </button>\`
    `,
  },
})

view(document.getElementById('app')!)

Arrow identifiers such as html, reactive, component, pick, props, nextTick, and onCleanup can be auto-injected when they are used as free identifiers. Explicit user imports are preserved, including aliased imports from @arrow-js/core.

Multi-file modules

const view = sandbox({
  source: {
    'main.ts': `
      import App from './App.ts'

      export default App
    `,
    'state.ts': `
      import { reactive } from '@arrow-js/core'
      export const state = reactive({ count: 0 })
    `,
    'App.ts': `
      import { html } from '@arrow-js/core'
      import { state } from './state.ts'

      export default html\`
        <button @click="\${() => state.count++}">
          Clicked \${() => state.count}
        </button>
      \`
    `,
  },
})

view(mountPoint)

Supported virtual imports:

  • relative imports between provided virtual files
  • .ts, .js, .mjs, and index.* fallback resolution
  • @arrow-js/core, resolved to the sandbox shim

Unsupported imports fail fast. There is no network fetch fallback.

API

export interface SandboxProps {
  source: Record<string, string>
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}

export interface SandboxEvents {
  output?: (payload: unknown) => void
}

export type HostBridgeFn = (...args: unknown[]) => unknown | Promise<unknown>
export type HostBridgeModule = Record<string, HostBridgeFn>
export type HostBridge = Record<string, HostBridgeModule>

export function sandbox<T extends {
  source: object
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}>(
  props: T,
  events?: SandboxEvents,
  hostBridge?: HostBridge
): ArrowTemplate

sandbox() returns an Arrow template. You can mount it directly, or compose it inside a larger Arrow template:

html`<section>${sandbox({ source })}</section>`

The rendered host element is always <arrow-sandbox>.

Source requirements:

  • exactly one entry file: main.ts or main.js
  • optional main.css, injected into the sandbox host root
  • all other entries are virtual JS/TS/MJS modules

shadowDOM defaults to true. When enabled, the sandbox mounts into an open shadow root on <arrow-sandbox>. When disabled, it mounts into the element’s light DOM instead.

Host bridge modules

The optional third hostBridge argument exposes host-owned functions as importable sandbox modules:

const view = sandbox(
  {
    source: {
      'main.ts': `
        import { getGreeting } from 'host-bridge:greetings'

        const payload = await getGreeting('Arrow')
        export default html\`<div>\${payload.message}</div>\`
      `,
    },
  },
  undefined,
  {
    'host-bridge:greetings': {
      getGreeting(name) {
        return { message: \`Hello \${String(name)}\` }
      },
    },
  }
)

Each top-level hostBridge key is imported directly as a module specifier. Bridge exports are named functions only, and sandbox code always receives Promise-returning functions. Bridge arguments and return values must be plain serializable data.

Sandbox output bridge

The optional events.output callback receives values emitted from inside QuickJS through the global output(payload) function:

const view = sandbox(
  {
    source: {
      'main.ts': `
        output({ status: 'ready' })
        export default html\`<div>Sandbox Ready</div>\`
      `,
    },
  },
  {
    output(payload) {
      console.log(payload)
    },
  }
)

output(payload) accepts one payload value. The payload is serialized to plain data before it crosses from the VM into the host.

Security model

  • User-authored logic runs inside QuickJS/WASM.
  • The host page mutates the real DOM through trusted renderer code only.
  • Event listeners on the real DOM forward sanitized payloads back to the VM.
  • The sandbox does not receive direct access to window, document, DOM nodes, storage, or arbitrary browser APIs.
  • html templates are preprocessed into descriptors. The host never evaluates user expressions.
  • DOM listeners in the host never attach raw user callbacks from sandbox code.
  • Synthetic sandbox events preserve common access patterns such as event.target.value and event.currentTarget.checked without exposing live host DOM nodes.

Explicitly bridged globals currently include setTimeout, clearTimeout, setInterval, clearInterval, and a restricted fetch() proxy. The host owns the real timers and networking, but the registered callbacks and response handling still execute inside QuickJS.

Event payloads are forwarded as plain data. The VM receives a narrow snapshot, not a live DOM event object.

The sandbox exposes event.target, event.currentTarget, and event.srcElement as plain data snapshots with a deliberately small surface: value, checked, id, and tagName. Compatibility shortcuts such as event.value and event.checked are still present, but event.target.value is the preferred shape.

Sandboxed fetch

The sandbox fetch() bridge is intentionally narrower than browser window.fetch:

  • only absolute https: URLs are allowed, plus http: for localhost addresses
  • no Request objects, no relative URLs, and no inherited browser request context
  • credentials are always forced to omit
  • referrerPolicy is always forced to no-referrer
  • mode is always forced to cors
  • request headers are user-supplied only and sensitive ambient headers such as authorization, cookie, origin, referer, and user-agent are blocked
  • responses are exposed as a small Response-like object with ok, status, statusText, url, redirected, headers, text(), json(), and arrayBuffer()
  • requests time out after 15 seconds and responses are capped at 1 MB

This bridge is designed to avoid ambient page credentials and host DOM access. It is still routed through the browser networking stack, so browser-controlled metadata such as Origin or User-Agent may still exist at the HTTP layer.

Supported subset

  • text interpolation
  • attribute interpolation
  • event bindings such as @click
  • nested elements
  • sync component() composition
  • component emits via component((props, emit) => ...) and parent listeners via Child(props, { eventName })
  • async component() composition with VM-owned fallback/render/error handling
  • pick() / props() narrowing for component props
  • global output(payload) host bridge
  • direct host bridge modules passed through the third sandbox() argument
  • reactive updates inside the VM
  • restricted bridged fetch() requests and JSON/text response handling
  • bridged timer callbacks via setTimeout and setInterval
  • arrays and conditional child regions
  • multi-root templates without a wrapper element

Unsupported or partial

  • keyed list diffing
  • direct DOM refs or real DOM node access
  • arbitrary external imports
  • browser API access without an explicit bridge
  • hard CPU and memory isolation

Current limitations

  • This is not yet a hard boundary against CPU or memory exhaustion.
  • Memory limits are applied to the QuickJS runtime, but denial-of-service hardening still needs more work.
  • TypeScript support uses typescript.transpileModule, not full semantic type-checking.
  • Template support is intentionally narrower than the standard Arrow host runtime.

Development

pnpm --filter @arrow-js/sandbox sync:vm
pnpm --filter @arrow-js/sandbox demo
pnpm exec vitest run packages/sandbox/src/index.spec.ts
pnpm exec playwright test -c playwright.sandbox.config.ts

The demo includes a weather mini-app that fetches current conditions from the public Open-Meteo forecast API.

版本与发布记录

@arrow-js/sandbox 共发布 9 个版本,首次发布于 2026/03/20;过去 12 个月发布了 9 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.0.6latest2026/04/017 个月前
1.0.52026/04/017 个月前
1.0.42026/03/277 个月前
1.0.32026/03/257 个月前
1.0.22026/03/257 个月前
1.0.12026/03/257 个月前

其他发布通道:dev → 0.0.0-dev.1,next → 1.0.1-next.1f4a0f5。

关于 @arrow-js/sandbox 的常见问题

@arrow-js/sandbox 是什么?

QuickJS/WASM-backed sandbox runtime for executing Arrow templates outside the host window realm.

如何安装 @arrow-js/sandbox?

在项目目录运行 npm install @arrow-js/sandbox,或使用 pnpm add / yarn add / bun add。

@arrow-js/sandbox 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/arrow-js__sandbox 是否存在。

@arrow-js/sandbox 支持 ESM 还是 CommonJS?

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

@arrow-js/sandbox 可以商用吗?

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

@arrow-js/sandbox 的下载量有多少?

根据 npm 官方下载统计,@arrow-js/sandbox 在 2026/10/07 – 2026/11/05 这 30 天内被下载 3,240 次,日均约 108 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arrow-js/sandbox 还在维护吗?

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