Runtime 基础
Runtime 基础
先创建运行边界,再在其中创建、查询和销毁 Instance;同一个 Runtime 可共享 App、资源池与宿主能力。
创建一个新的 Runtime 控制面,用于配置并管理一组 Instance 的注册表、App 复用、资源池与宿主能力。
createFlamoRuntime(options?: FlamoRuntimeOptions): FlamoRuntimeRuntime 是什么
Runtime 是 Flamo 在宿主应用中的长期运行边界。一个编辑器、画布区域或具有共同生命周期的页面区域,通常对应一个 Runtime。它负责把可持久化的 Artifact 变成可交互内容,并统一管理这一运行域里的资源与对象。
通过 createFlamoRuntime(options),可以配置该运行域使用的 canvas、App 默认参数、组件解析器、宿主服务、模块策略和 ResourcePool。创建之后,Runtime 提供一组完整的控制能力:
- 使用
runtime.create()创建 Instance,并可通过其中的target在创建阶段绑定宿主; - 使用
runtime.mount(target, artifact)创建并挂载新的 Instance; - 使用
runtime.get()/runtime.list()查询当前运行域中的对象; - 使用
runtime.inspect()获取可观测快照; - 使用
runtime.destroy()结束对象生命周期并释放其引用; - 使用
runtime.probeCapabilities()检查当前宿主支持的能力。
Runtime 还决定一组内容如何共享 App、ECS world、渲染循环和资源缓存。它适合由宿主持有,并随着编辑器、画布或页面区域的整体生命周期创建和释放。
mount() 属于 Runtime,不属于返回的 FlamoInstance。如果先调用 runtime.create({ artifact }) 且未提供目标,不能再调用 instance.mount(target);应在创建选项中传入 target,或直接使用 runtime.mount()。
同一个 Runtime 内共享 App 的优势
默认情况下,Runtime 会让其 Instance 共享同一个底层 Flamo App。这个层级复用的是 ECS world、SceneRuntime、渲染循环、Canvas surface 管线和 App 使用的资源池,而不是把所有 Instance 合并成一个实例。
具体收益是:
- 创建多个 Instance 时不需要为每个实例重复初始化 App、ECS world 和渲染循环。
- 相同图片或视频可通过内容寻址 registry 复用已解码、已上传的 GPUTexture,减少重复加载与显存占用。
- App 默认在最后一个 Instance 销毁后继续保活,后续重新挂载可复用已有 world,避免重复初始化并提高 remount 稳定性。
- Instance 仍保留独立的 idMap、selector、props、bindings 和生命周期;共享资源不会让同名节点或交互串到其他实例。
shareApp: false 会让每次 runtime.create() 使用独立 App。只有确实需要独立 ECS world、渲染循环或故障边界时才这样配置。
多个 Runtime 共享 ResourcePool
多个画布需要独立 Runtime/App 时,可以只共享更底层的 ResourcePool:
import { createResourcePool, createFlamoRuntime,} from '@flamo/runtime';
const resourcePool = createResourcePool();
const leftRuntime = createFlamoRuntime({ canvas: leftCanvas, shareApp: false, resourcePool,});
const rightRuntime = createFlamoRuntime({ canvas: rightCanvas, shareApp: false, resourcePool,});这时左右画布拥有独立的 App、ECS world、SceneRuntime、selector 和事件状态,但共同复用:
- 同一次
GPUAdapter/GPUDevice初始化; - 按 hash 缓存的图片解码结果与 GPUTexture;
- 视频元素、视频帧纹理和媒体注册表;
image:、font:、module:、html:等资源引用的解析结果;- 按 Runtime / Instance / source node 记录的资源 retain/release 所有权。
这比“每个画布完整创建一套 GPU 和媒体资源”更省初始化成本与内存,同时又比共享整个 App 提供更强的场景隔离。
外部传入的 ResourcePool 由宿主持有;应在所有使用它的 Runtime 和 Instance 都结束后再调用 resourcePool.destroy()。资源复用会延长缓存存活时间,因此它是在初始化成本、内存驻留和隔离强度之间做出的明确选择。
最小用法
const runtime = createFlamoRuntime();const instance = await runtime.mount(container, artifact);
// 宿主卸载时await runtime.destroy(instance);常见误区
- 不要为了渲染每个组件都新建 Runtime;同一生命周期域的一组 Instance 通常应由同一个 Runtime 管理。
shareApp与resourcePool不是同一层:前者共享 App/world,后者可以在 App/world 隔离时只共享 GPU 与媒体资源。- 不要在仍有 Runtime 使用 ResourcePool 时销毁它。
参数
options?FlamoRuntimeOptions
拥有 Instance 注册表、资源边界、组件解析和检查能力的运行时接口。
export interface FlamoRuntimeRuntime 是一组 Instance 的 owner。通过同一 Runtime 创建、查询和销毁实例;需要画布或租户隔离时,使用不同 Runtime。
runtime.mount(target, artifact, options) 等价于 runtime.create({ ...options, target, artifact }):两者都返回新 Instance,区别是前者把目标作为独立的第一个参数。挂载发生在创建阶段;返回的 FlamoInstance 不提供 mount(target)。
const first = await runtime.mount('#first', artifact);const second = await runtime.create({ target: '#second', artifact });runtime.get() / list() / inspect() 只读取这个 Runtime 的注册表,runtime.destroy() 只销毁由它登记的 Instance。父子图的 mountChild() 和 mountComposition() 属于 Instance API,不是 Runtime 的 DOM 挂载入口。
配置 Runtime 的默认 App、资源池、组件解析器、服务与共享策略。
export interface FlamoRuntimeOptions一般示例可以使用默认值。只有在多画布、离线资源、精确组件解析或宿主服务治理场景下,才应逐项覆盖这些选项。
两种共享策略
| 选项 | 共享层级 | 保持隔离的内容 | 主要收益 |
|---|---|---|---|
shareApp: true |
同一 Runtime 内共享 App、ECS world、SceneRuntime、渲染循环和资源池 | 每个 Instance 的节点映射、props、bindings 与生命周期 | 多实例避免重复初始化整套 App |
多个 Runtime 使用同一 resourcePool |
只共享 GPU device、图片/视频 registry 和资源解析缓存 | 各 Runtime 的 App、world、selector、事件与实例注册表 | 多画布保留场景隔离,同时减少 GPU 与媒体资源重复 |
未自定义 createApp 时,shareApp 默认为 true。destroySharedAppOnIdle 默认为 false,所以最后一个 Instance 销毁后 App/world 仍保活,便于稳定、快速地重新挂载;它不会阻止 Instance 自身的 scene、selector、binding 和 surface 引用被清理。
当 Runtime 接收外部 resourcePool 时,Runtime 不拥有它的最终销毁权。宿主应在所有使用者结束后统一调用 resourcePool.destroy()。
生成指定宿主平台与 Runtime 版本对应的能力清单。
probeFlamoPlatformCapabilities(platform?: "web" | "weixin" | "ssr" | "worker" | "worklet" | "native" | string & { ... }, runtimeVersion?: string): FlamoPlatformCapabilityManifest在加载需要特定平台能力的组件或组合前进行探测。结果是能力声明,不等同于实际业务资源或权限已经可用。
参数
platform?"web" | "weixin" | "ssr" | "worker" | "worklet" | "native" | string & { ... }runtimeVersion?string
Artifact 与组件
Artifact 与组件
Artifact 是可序列化定义;Component 是生成并封装 Artifact 的可复用代码 API;二者都要交给 Runtime 才会产生 Instance。
对象模型
Artifact 不等于 Component
Artifact 是可以保存和传输的结构化定义;Component 是接收 props、生成 Artifact 的可复用代码 API; Runtime 把 Artifact 创建或挂载后,才会得到一次运行所对应的 Instance。
Artifact 内的 exports 表示它公开的组件边界;JavaScript 的 export 则用于导出 Component API。两者不是同一层的“导出”。
| 对象 | 如何定义与导入 | 如何导出 | 如何实例化 |
|---|---|---|---|
Artifact | JSON、ES Module 或生成器输入,经 defineFlamoArtifact() 规范化 | 保存为 JSON,或作为普通 JavaScript 值导出 | runtime.create({ artifact }) 或 runtime.mount(target, artifact) |
Component | 用 createFlamoComponentApi(factory) 定义,再通过 ES Module 导入 | export const Card = ... | Card.create(props)、Card.mount(target, props),或调用 runtime.create({ artifact: Card(props), props }) |
Instance | 由 Runtime 返回;同一 JavaScript realm 内可通过 ES Module 共享活引用,但不是可导入的组件定义 | 可以导出活对象引用;不能序列化、跨进程传输或作为组件发布物,应另存 Artifact、Component 模块和 props | 由上述 create() / mount() 调用产生 |
规范化结构化输入,得到可由 Runtime 挂载的 Flamo Artifact。
defineFlamoArtifact(input: FlamoArtifactLike, options?: DefineFlamoArtifactOptions): FlamoArtifact它是手写、导入或代码生成 Artifact 的入口。输入应只包含可持久化事实,不要写入 DOM、ECS Entity 或某次运行产生的对象引用。
导入 Artifact
Artifact 可以来自 JSON 文件、网络接口或 ES Module。导入后先经过 defineFlamoArtifact(),可统一补齐 schema、metadata、exports 等规范字段:
import artifactJson from './card.flamo.json' with { type: 'json' };import { defineFlamoArtifact } from '@flamo/runtime';
export const cardArtifact = defineFlamoArtifact(artifactJson);从网络读取时同样处理:
const source = await fetch('/artifacts/card.flamo.json').then(response => response.json());const cardArtifact = defineFlamoArtifact(source);导出 Artifact
Artifact 是普通的可序列化数据,可以保存成 JSON,也可以作为 JavaScript 值导出:
const json = JSON.stringify(cardArtifact, null, 2);export default cardArtifact;导出的 Artifact 仍只是定义。要运行它,需要调用 runtime.create({ artifact: cardArtifact }) 或 runtime.mount(target, cardArtifact)。
参数
inputFlamoArtifactLikeoptions?DefineFlamoArtifactOptions
Flamo 可持久化设计、组件、行为和资源事实的规范化容器。
export interface FlamoArtifactArtifact 可以版本化、传输和验证;Instance 才拥有运行态状态。
Artifact 不是 Component 的同义词
Artifact 是结构化数据容器,可以描述一个组件,也可以描述完整页面、场景或其他可运行内容。它本身不要求具备 props factory、代码模块或组件级发布身份。
Component 则是在 Artifact 之上建立的可复用代码入口:它接收 props、生成 Artifact,并提供创建、挂载和公共契约查询能力。使用 createFlamoComponentApi(factory) 可以把 Artifact factory 定义为 Component API。
因此三者的关系是:
Component(props) → Artifact → Runtime.create()/mount() → Instance如果手上已有 Artifact,可以直接交给 Runtime,不必先把它包装成 Component。只有需要 props、重复使用、模块导入导出或稳定公共接口时,才需要定义 Component。
完整概念边界见 Artifact、Component 与 Instance。
需要把多个子组件按父子层级封装为新的 Artifact / Component 时,使用 Composition IR,而不是保存运行中的 Instance。完整步骤见 组合多个组件并封装为新组件。
把接收 props 并生成 Artifact 的 factory 定义为可导入、可创建、可挂载的 Component API。
createFlamoComponentApi(factory: FlamoArtifactFactory<P>, defaults?: { ... }): FlamoComponentApi<P>定义一个 Component
Artifact 负责描述内容,Component API 负责把这份描述变成可复用的代码入口。先准备一个 Artifact 或 Artifact factory,再用 createFlamoComponentApi() 包装:
import { createFlamoComponentApi } from '@flamo/runtime';import cardSource from './card.flamo.json' with { type: 'json' };
export const Card = createFlamoComponentApi( ({ title = 'Card' } = {}) => ({ ...cardSource, metadata: { ...cardSource.metadata, name: title, }, }),);factory 返回的仍是 Artifact 数据。createFlamoComponentApi() 会负责规范化它,并返回同时支持调用、创建、挂载和契约查询的 FlamoComponentApi。
如果 Artifact 内部声明了一个或多个组件边界,artifact.exports 记录这些公开边界,artifact.metadata.mainExport 指定默认边界。这里的 artifact.exports 是 Artifact 内的组件公开表,不要和 JavaScript 的 export 语法混淆。
导入并实例化
Component API 是 JavaScript 模块导出,可以像普通模块一样导入:
import { Card } from './Card.js';
// 只生成 Artifact,不启动运行态const artifact = Card({ title: '账户卡片' });
// 使用 @flamo/runtime 的默认 Runtime 创建或挂载 Instanceconst created = await Card.create({ title: '账户卡片' });const mounted = await Card.mount('#preview', { title: '账户卡片' });需要使用指定 Runtime 时,不调用 Component API 自带的 .create() / .mount(),而是先生成 Artifact,再交给目标 Runtime:
const props = { title: '账户卡片' };const artifact = Card(props);const instance = await runtime.create({ artifact, props });
// 或直接挂载const mounted = await runtime.mount(container, artifact, { props });runtime.create() 与 Card.create() 的区别
| 调用 | Artifact 从哪里来 | 使用哪个 Runtime | props 如何进入运行态 |
|---|---|---|---|
Card.create(props, options) |
Card 的 factory 自动生成 |
模块内置的默认 Runtime | 自动写入创建选项 |
runtime.create({ artifact, props, ...options }) |
调用方显式提供 | 当前这个 runtime |
调用方显式提供 |
Card.create() 适合使用默认 Runtime 的组件消费代码;runtime.create() 适合编辑器、多画布或需要独立资源与实例注册表的宿主。使用自定义 Runtime 时,不要先调用 Card.create(),而应调用 Card(props) 取得 Artifact,再交给目标 Runtime。
导出边界
- 导出 Artifact:保存组件定义的数据,可用于传输、验证和版本化。
- 导出 Component API:导出 factory 与便捷方法,适合在 JavaScript/TypeScript 工程中复用。
- 导出 Instance 活引用:仅适合同一页面、同一 JavaScript realm 内的模块协作,必须由 owner 管理创建与销毁。
- 不把 Instance 当作发布物:它不能序列化、跨环境传输或供其他项目再次实例化;跨环境复用必须由目标 Runtime 根据 Artifact 或 Component API 重新创建。
参数
factoryFlamoArtifactFactory<P>defaults?{ ... }
把 Artifact 的公开 surface 投影成 Custom Element wrapper 契约。
createFlamoWrapperContract(artifact: FlamoArtifact, options?: CreateFlamoWrapperContractOptions): FlamoWrapperContract用于打包或宿主适配阶段生成 attributes、properties、events 等 W3C 接口描述。它描述契约,不负责注册元素或创建 Runtime Instance。
参数
artifactFlamoArtifactoptions?CreateFlamoWrapperContractOptions
Artifact public surface 到 W3C Custom Element 的稳定投影结果。
export interface FlamoWrapperContract契约区分 attribute 可表达的字符串值与 property 可承载的结构化值,并记录公开事件。它是包装器生成事实,不是框架适配层。
把组件 Artifact factory、创建、挂载和契约查询收敛为一个调用接口。
FlamoComponentApi(props?: P): FlamoArtifact它适合为代码创作的组件提供一致入口:
| 调用 | 结果 |
|---|---|
Card(props) |
生成一个规范化 Artifact |
Card.artifact(props) |
显式生成同一个 Artifact |
Card.create(props, options) |
使用模块默认 Runtime 创建 Instance |
Card.mount(target, props, options) |
使用模块默认 Runtime 创建并挂载 Instance |
Card.contract(props) |
读取组件面向宿主的 wrapper contract |
.create() 和 .mount() 使用 @flamo/runtime 的默认 Runtime。需要指定 Runtime 时,先生成 Artifact,再显式交给目标 Runtime:
const props = { title: '账户' };const artifact = Card(props);const instance = await runtime.create({ artifact, props });Card.mount() 属于 Component API:它先生成 Artifact,再委托默认 Runtime 创建并挂载一个新 Instance。它不是返回对象上的方法;const instance = await Card.create(...) 之后不能调用 instance.mount(target)。
Component API 是普通 JavaScript 值,可以使用 ES Module 导入导出并作为可复用创建入口。Instance 则是调用后产生的运行态句柄:同一 JavaScript realm 内可以导入导出它的活引用,但不能随组件模块一起序列化、发布或跨环境恢复。
createFlamoComponentApi() 负责把一个 Artifact factory 变成便捷调用入口,但不会把若干 live Instance 自动编译成新组件。多组件层级应先使用 defineFlamoComponent() 构造 Composition IR,再生成新的 Artifact。参见 组合多个组件并封装为新组件。
参数
props?P
Instance 生命周期
Instance 生命周期
Instance 是 Artifact 或 Component 在某个 Runtime 中运行后的临时句柄,用于交互、检查与销毁。
通过模块内置的默认 Runtime 创建一个 Flamo Instance;它是 flamo.create(options) 的快捷入口,不会新建 Runtime。
create(options: FlamoCreateOptions): Promise<FlamoInstance>它实际做了什么
顶层 create(options) 等价于:
return flamo.create(options);其中 flamo 是 @flamo/runtime 在模块加载时创建的默认 Runtime 单例。因此每次调用 create() 只会创建新的 Instance,不会创建新的 Runtime。
import { create, destroy } from '@flamo/runtime';
const instance = await create({ target: '#preview', artifact,});
await destroy(instance);同一页面中所有顶层 create()、mount()、get()、destroy() 和 inspect() 都操作这个默认 Runtime。默认 Runtime 会复用底层 App 与资源生命周期,而每个 Instance 仍拥有自己的 id、props、节点映射和交互作用域。
target 与挂载时机
create({ artifact, target }) 会在创建 Instance 的同一步绑定目标,等价于调用默认 Runtime 的 mount(target, artifact)。如果没有 target、没有默认 canvas,Instance 不会自动附着到 DOM。
返回的 FlamoInstance 没有 mount(target) 方法,不能先无目标创建,再调用 instance.mount() 补挂载。需要 DOM/Canvas 宿主时,应在 create() 的选项中提供 target,或直接调用 mount()。
与 createFlamoRuntime() 的区别
| API | 创建什么 | 谁管理 Instance | 适合场景 |
|---|---|---|---|
create(options) |
一个 Instance | 模块内置的默认 Runtime | 单一预览、简单页面、无需自定义 Runtime 策略 |
createFlamoRuntime(options) |
一个新的 Runtime 控制面 | 由你持有的 Runtime | 编辑器、多画布、独立实例注册表、自定义资源池或宿主服务 |
如果只需要“把这个 Artifact 创建出来”,使用 create()。如果需要决定这些 Instance 与谁共享 App、由谁查询和销毁、使用哪个资源池,先调用 createFlamoRuntime(),再使用 runtime.create()。
const runtime = createFlamoRuntime({ canvas });const instance = await runtime.create({ artifact });
runtime.get(instance.id); // 只查询这个 Runtime 的实例await runtime.destroy(instance);与 Card.create() 的区别
假设 Card 是 createFlamoComponentApi() 返回的组件 API:
const instance = await Card.create(props, options);它会自动执行 Card(props) 生成 Artifact,并使用模块内置的默认 Runtime 创建 Instance。显式的 runtime.create() 不绑定某个组件 factory,调用方需要提供 Artifact 与运行态 props:
const artifact = Card(props);const instance = await runtime.create({ ...options, artifact, props,});因此,使用默认 Runtime 且希望少写样板代码时用 Card.create();需要指定 Runtime 的实例注册表、App、ResourcePool 或宿主服务时用 runtime.create()。
参数
optionsFlamoCreateOptions
把 Artifact 挂载到目标元素,并返回默认 Runtime 创建的 Instance。
mount(target: FlamoMountTarget, artifact: FlamoArtifact, options?: FlamoInstanceOptions): Promise<FlamoInstance>这是最短的页面接入方式。顶层 mount() 使用模块内置的默认 Runtime;需要明确 Runtime 所有权、共享资源或多实例隔离时,使用 runtime.mount(target, artifact, options)。
import { mount } from '@flamo/runtime';
const instance = await mount('#preview', artifact);mount(target, artifact, options) 会创建一个新 Instance,并在创建时绑定目标。它不是对已有 Instance 的二次挂载;FlamoInstance 没有 mount(target) 方法。
目标要求
- selector 字符串在浏览器中通过
document.querySelector()解析,找不到时抛出flamo.mount: target not found; HTMLCanvasElement会直接作为渲染表面;- 其他
Element会收到 Runtime 创建的子<canvas>,容器应能接收子节点并具有可见尺寸; Document、Window、ShadowRoot、null不属于公开目标类型。
不要与以下接口混淆:Card.mount() 是 Component API 使用默认 Runtime 的便捷方法;instance.mountChild()、mountComposition()、mountSlot() 修改当前 Instance 内部的 child/composition/slot,不会重设顶层 DOM 目标。
参数
targetFlamoMountTargetartifactFlamoArtifactoptions?FlamoInstanceOptions
创建 Instance 时同时指定 Artifact、目标、props、资源和宿主上下文。
export interface FlamoCreateOptions这是 runtime.create() 的完整选项。只有 artifact 是创建事实;target、canvas、host 和资源解析器属于本次运行环境。
target 接受 selector 字符串、Element 或 HTMLCanvasElement。它必须在创建 Instance 时提供;返回的 FlamoInstance 没有稍后补挂载的 mount(target) 方法。没有 target 时,Runtime 只会使用 appConfig.canvas 或 Runtime 默认 Canvas;两者也没有配置时,不会自动向 DOM 追加 Canvas。
挂载 Artifact 时控制 Instance id、初始 props、宿主和运行策略。
export interface FlamoInstanceOptions它用于 runtime.mount() 的第三个参数,也构成 FlamoCreateOptions 除 artifact / target 外的公共部分。多个实例共存时请显式规划稳定且不冲突的 id;宿主与 canvas 仍是运行态资源。
target 不在这个接口中:调用 runtime.mount() 时它是第一个参数,调用 runtime.create() 时它属于扩展接口 FlamoCreateOptions。两种写法都在创建阶段绑定目标,不存在后续 instance.mount(target)。
Artifact 在某个 Runtime 中运行后的可查询、可交互、可销毁对象。
export interface FlamoInstanceInstance 持有 props、事件、方法、slot 和运行态引用。它只能在创建它的 JavaScript 进程与 Runtime 生命周期内使用。
需要等待 Variant 与普通绑定以一个事务完成时,使用 setProps();它返回 FlamoPropsCommitReceipt,失败或取消不会发布部分属性状态。
Instance 能否跨模块引用
可以。在同一个页面、同一个 JavaScript realm 和同一份模块图内,Instance 是普通活对象,可以由一个 TS/JS 模块导出引用,再由另一个模块导入使用。导出的不是副本或组件定义,导入方必须处理尚未创建、已经销毁和 owner teardown 等生命周期状态。
不能做的是把 Instance 保存为 JSON、写进 Artifact、通过 structured clone / Worker / iframe / 进程边界传递,或把它当作可再次实例化的 Component Release。跨环境恢复必须保存定义与输入,再创建新的 Instance。
需要持久化或传输时,请保存以下信息:
- 用于创建它的 Artifact;
- 或 Component 模块的稳定标识、版本和 props;
- 如涉及创作变更,再保存明确的 authoring transaction。
恢复时重新导入 Artifact 或 Component,并在目标 Runtime 中再次调用 create() / mount():
const instance = await runtime.create({ artifact: importedArtifact });
// 当前生命周期结束时只销毁句柄;需要恢复的定义已另行保存await runtime.destroy(instance);Instance 没有宿主 mount()
宿主 DOM/Canvas 挂载属于 FlamoRuntime 或 FlamoComponentApi:
const instance = await runtime.mount('#preview', artifact);// 或 runtime.create({ target: '#preview', artifact })FlamoInstance 没有 mount(target)。它名称中带 mount 的方法操作的是当前实例内部:mountChild() 挂 child graph,mountComposition() 挂组合 IR,mountSlot() 分配 slot 内容;这些方法都不会把当前 Instance 重新绑定到另一个 DOM 目标。
父子 Instance 图
组合组件运行后,父 Instance 可以通过 children、child(key) 与 publicChild(key) 读取层级,也可以用 mountChild()、swapChild()、reconcileChildren() 临时修改当前运行树。
这些方法不会修改原始 Artifact,也不会生成新的 Component Release。需要把当前结构长期保存并封装为新组件时,应使用稳定 child key、精确 Component Ref、parentKey、mount、props、slots 和 routes 重建 Composition IR,再编译为新 Artifact。参见 组合多个组件并封装为新组件。
| 需要 | API |
|---|---|
| 保留现有 children,只新增一个 key | mountChild() |
| 声明完整目标列表并批量复用、新增、移除 | reconcileChildren() |
| 保留 key,替换该位置的 child 定义 | swapChild() |
Runtime 对父子 Instance 图中一个节点提供的只读快照。
export interface RuntimeInstanceGraphNodeinstance.children、instance.child(key) 与 instance.publicChild(key) 返回该快照,而不是可直接操作的 FlamoInstance 本体。
const node = panel.child('submit');if (node) { console.log(node.instanceId, node.lifecycleState, node.ownership); const childInstance = runtime.get(node.instanceId); childInstance?.set({ disabled: true });}children 字段保存直接后代的 Instance id。要遍历完整图,可从 runtime.get(id) 继续读取其 children,或使用 Inspector snapshot。不要修改快照字段;父子关系只能通过 composition transaction API 改变。
字段
childKeystring | nullchildrenreadonly string[]componentRef?{ ... }generationnumberinstanceIdstringlayout?FlamoBoundaryLayoutSnapshotlifecycleState"mounted" | "disposed" | "preparing" | "disposing" | "failed"logicalMount?{ ... }ownerBoundaryIdstringownerCountsReadonly<Record<RuntimeOwnerKind, number>>ownership"runtime" | "caller"parentIdstring | nullphysicalMount?{ ... }
通过 id 或宿主元素查询默认 Runtime 已登记的 Instance。
get(idOrElement: string | Element): FlamoInstance | undefined找不到目标时返回 undefined。自建 Runtime 的实例不在默认注册表中,应调用对应 Runtime 的 get()。
参数
idOrElementstring | Element
获取默认 Runtime 当前所有 Instance 的只读诊断快照。
inspect(): FlamoInspectorSnapshot适合调试和自动化证据采集。快照可能在下一帧变化;不要把它当作写入 API 或长期事实源。
某一时刻 Runtime 中所有公开实例状态的只读检查快照。
export interface FlamoInspectorSnapshot用于 DevTools、Studio inspector 和自动化诊断。每个 FlamoInspectorInstanceSnapshot 还可提供 descendant ownership、Node Script disposer、listener、pending task/mutation、slot surface、resource reference、ECS entity 与 limbo component 计数,适合在销毁测试前后验证资源归零。快照不是 durable Artifact,也不能作为修改运行状态的命令接口。
Public state and resource-lifecycle snapshot for one live Runtime instance.
export interface FlamoInspectorInstanceSnapshot字段
artifactId?stringartifactName?stringcomponentGraph?RuntimeInstanceGraphNodecomponentSetSelections?Record<string, FlamoComponentSetSelection>componentTransactions?readonly RuntimeCompositionReceipt[]idstringlifecycle?{ ... }Live ownership and cleanup counters used to verify complete teardown.
nodeArtifacts?Record<string, FlamoInspectorNodeArtifactSnapshot>nodeScripts?FlamoNodeScriptMountSnapshot[]overridesreadonly FlamoOverride[]propsRecord<string, unknown>publicApi{ ... }rootId?string | nullruntimeScene?unknownslotsRecord<string, unknown>slotStates?Record<string, FlamoSlotState>wrapperFlamoWrapperContract
Observable Node Script route, listener, exercise, and failure lifecycle.
export interface FlamoNodeScriptLifecycleSnapshot字段
cleanupStatus?"disposed" | "not-registered" | "active"entry?stringerror?stringhandlerError{ ... } | nullimportedAt?numberinheritedFrom?stringinstanceId?stringkeystringlastEventAtnumber | nulllastTargetstring | nulllifecycleFlamoNodeScriptLifecycleStagelistenersstring[]listenerTargetsRecord<string, string>Legacy single-target projection retained for compatibility.
listenerTargetSetsRecord<string, string[]>Every distinct authoring/runtime target registered for each event.
mountedAt?numberreason?stringresolvedAt?numberresolvedListenerTargetsRecord<string, { ... }>resolvedListenerTargetSetsRecord<string, { ... }[]>routeReadybooleanruntimeNodeId?stringscriptId?stringsourceNodeIdstringstatusFlamoNodeScriptMountStatusupdatedAtnumber
Versioned interaction evidence emitted for local preview and build receipts.
export interface FlamoInteractionLifecycleSnapshot字段
eventRoutesRecord<string, unknown> | nullinstanceIdstringnodeArtifactsRecord<string, FlamoInspectorNodeArtifactSnapshot>nodeScriptsFlamoNodeScriptLifecycleSnapshot[]runtimeAbi{ ... }schemaVersion1summaryRecord<string, number>traceNodeScriptInteractionTraceEntry[]
通过 id、宿主元素或 Instance 销毁默认 Runtime 中的运行实例。
destroy(idOrElementOrInstance: string | Element | FlamoInstance): Promise<boolean>返回值表示是否找到并销毁了目标。使用自建 Runtime 时,应调用该 Runtime 自己的 destroy(target),避免跨隔离边界操作实例。
参数
idOrElementOrInstancestring | Element | FlamoInstance
动态组件与事务
动态组件与事务
Node Script 可从当前 Artifact export 或精确外部引用创建组件;创建、批量回滚、属性提交和销毁都受 Runtime 所有权与事务边界约束。
以一次可取消、可回滚的事务原子提交 Instance 公共属性。
FlamoInstance.setProps(patch: Record<string, unknown>, options?: { ... }): Promise<FlamoPropsCommitReceipt>setProps() 会先验证完整 patch,并在每个 Instance 内串行执行。Variant 变更先完成,普通绑定与公共更新事件随后才可见;失败或取消会恢复之前的 selection,不发布部分 props。
当 Component Set 根脚本继承到页面中的具体 INSTANCE 时,ctx.instance.setProps() 会使用该脚本合并后的有效 surface,并自动把 Variant 绑定定向到当前实例。未知属性、无 Runtime binding 的属性和跨 Component Set 的绑定会返回 rejected;Variant 轴提交成功时,variantReceipts 至少包含当前实例的一条 applied 回执,不会再把页面级无绑定 patch 误报为成功。
const receipt = await instance.setProps({ disabled: false, variant: { Size: 'Large', State: 'Active' },}, { signal });
if (receipt.status !== 'committed') console.warn(receipt.error);组件根脚本可以直接提交当前实例的状态轴:
const receipt = await ctx.instance.setProps({ state: 'hover' });if (receipt.status !== 'committed' || receipt.variantReceipts[0]?.status !== 'applied') { throw new Error(receipt.error?.message ?? 'Button state was not applied');}需要同步返回旧式链式句柄时可继续使用 set();需要等待最终一致状态、取消或审计时使用 setProps()。
参数
patchRecord<string, unknown>options?{ ... }
描述原子属性事务的提交、拒绝或取消结果。
export interface FlamoPropsCommitReceipttransactionId 可与 interaction trace 对齐;patch 是请求值的冻结副本;variantReceipts 保存同一事务内完成的 Variant commit。只有 status: 'committed' 表示完整 patch 已经对外可见,rejected 与 cancelled 都不会留下部分提交。
字段
error?{ ... }Structured failure information when status is rejected or cancelled.
patchReadonly<Record<string, unknown>>Frozen copy of the requested property patch.
status"committed" | "rejected" | "cancelled"Whether the complete patch committed, failed validation, or was cancelled.
transactionIdstringStable id for correlating the mutation with Runtime traces.
variantReceiptsreadonly FlamoVariantCommitReceipt[]Variant transactions completed as part of the same atomic mutation.
向当前父 Instance 的运行树新增一个具有稳定 key 的 child,并以 composition transaction 提交。
FlamoInstance.mountChild(spec: RuntimeChildSpec, options?: ReconcileRuntimeChildrenOptions): Promise<RuntimeCompositionReceipt>新增一个 child
mountChild() 保留现有 children,并在完整 desired set 中加入一个新的 RuntimeChildSpec。同一个父级不能重复使用相同 key。
这里的 mount 是“把 child 加入当前父 Instance 的运行图”,不是宿主 DOM 挂载。它不会改变父 Instance 的 target 或 canvas,也不是不存在的 instance.mount(target)。
const receipt = await panel.mountChild({ key: 'empty-state', artifact: EmptyState({ message: '暂无结果' }), parentKey: null, mount: { kind: 'root' }, props: { compact: true }, statePolicy: 'preserve',});
if (receipt.status !== 'committed') { throw new Error(receipt.error?.message ?? `mountChild: ${receipt.status}`);}
const child = panel.child('empty-state');spec 必须且只能提供 ref、artifact、instance 其中一种:
ref:通过 Runtime 的componentResolver解析精确 Component Release;artifact:创建 Runtime-owned 的本地 child;instance:挂接已经存在的 live Instance,默认保持 caller-owned。
挂接 caller-owned Instance 时,父级销毁只会解除父子关系,不会替调用方销毁 child:
const preview = await runtime.create({ artifact: PreviewCard() });
await panel.mountChild({ key: 'preview', instance: preview, ownership: 'caller', parentKey: null, mount: { kind: 'root' },});
await runtime.destroy(panel);await runtime.destroy(preview); // 仍由调用方负责该方法只修改当前 Runtime,不会写回 Artifact。需要保存层级时,应编译 Composition IR。
参数
specRuntimeChildSpecoptions?ReconcileRuntimeChildrenOptions
用传入的完整列表更新父 Instance 下手动添加的子 Instance:可复用的保留,缺少的新增,未列出的移除;更新失败时全部恢复原状。
FlamoInstance.reconcileChildren(specs: readonly RuntimeChildSpec[], options?: ReconcileRuntimeChildrenOptions): Promise<RuntimeCompositionReceipt>声明完整目标树
reconcileChildren(specs) 接收的不是增量 patch,而是当前父 Instance 完整的手动 child 目标集合。未出现在新列表中的手动 child 会被移除;传入空数组会清空手动 children,Slot 运行时拥有的 child 不包含在这份列表中。
const specs = rows.map(row => ({ key: `row:${row.id}`, ref: resultRowRef, parentKey: null, mount: { kind: 'root' }, props: { row, selected: row.id === selectedId }, statePolicy: 'preserve',}));
const receipt = await resultList.reconcileChildren(specs, { latestWins: true, timeoutMs: 10_000,});
if (receipt.status !== 'committed') { console.error(receipt.error);} else { console.log('mounted', receipt.mounted); console.log('reused', receipt.reused); console.log('removed', receipt.removed);}构造嵌套层级
parentKey 引用同一份 specs 中另一个 child 的 key:
await panel.reconcileChildren([ { key: 'toolbar', artifact: Toolbar(), parentKey: null, mount: { kind: 'root' }, }, { key: 'submit', artifact: Button({ label: '提交' }), parentKey: 'toolbar', mount: { kind: 'slot', slotId: 'actions', outletId: 'primary' }, },]);协调规则:
key必须在整份列表中唯一,parentKey必须存在且不能形成循环;- 相同
key、相同 exact ref / Artifact object / Instance 且statePolicy !== 'reset'时复用原 child;上例使用 exact ref,因此重新生成specs不会破坏 row identity; - 默认 latest-wins,新 transaction 会 supersede 同一父级尚未提交的旧 transaction;
- prepare 或 commit 失败时返回非
committedreceipt,并恢复调用前的手动 child specs; mounted、reused、removed中记录的是 Instance id,不是 child key。
这是运行态协调接口。需要生成可持久化的新组件时,将相同 key / parentKey 结构写入 Composition IR。
参数
specsreadonly RuntimeChildSpec[]options?ReconcileRuntimeChildrenOptions
保留 child key 并替换其 ref、Artifact、Instance 或创建选项,然后重新协调父级运行树。
FlamoInstance.swapChild(key: string, spec: Omit<RuntimeChildSpec, "key">, options?: ReconcileRuntimeChildrenOptions): Promise<RuntimeCompositionReceipt>替换一个稳定位置
swapChild(key, spec) 的第二个参数不再包含 key。父级对该 key 的查询和 route 可以保持稳定,而实际 child 定义会被替换。
const receipt = await panel.swapChild('submit', { artifact: DangerButton({ label: '删除' }), parentKey: null, mount: { kind: 'root' }, props: { disabled: false }, statePolicy: 'reset',});
if (receipt.status === 'committed') { console.log(panel.child('submit')?.instanceId);}使用相同 exact ref 或同一个 Artifact object 时,statePolicy: 'preserve' 可能复用现有 Instance 并只 patch props、slots 与 layout。需要强制重新创建时使用 statePolicy: 'reset'。
await panel.swapChild('submit', { ref: nextButtonRef, parentKey: null, mount: { kind: 'root' }, props: { label: '继续' }, statePolicy: 'preserve',}, { signal: abortController.signal, timeoutMs: 15_000,});如果 transaction 没有提交,swapChild() 会恢复调用前保存的 spec。与该 key 关联的其他 parentKey、公开引用和 route 无需改名;其嵌套 children 仍按完整 desired set 参与本次 reconcile。
该方法不会修改原始 Artifact,也不会发布新的 Component Release。
参数
keystringspecOmit<RuntimeChildSpec, "key">options?ReconcileRuntimeChildrenOptions
描述一个运行时 child 的稳定身份、来源、父级、挂载位置、输入和所有权策略。
export interface RuntimeChildSpec三种 child 来源
每个 spec 必须且只能提供以下一种来源:
| 字段 | 用途 | 默认所有权 |
|---|---|---|
ref |
精确 Component Ref,由 componentResolver 加载 |
Runtime-owned |
artifact |
已在当前进程取得的本地 Artifact | Runtime-owned |
instance |
已经存在的 live Instance | caller-owned |
持久化 Composition IR 应优先保存带 hash 与 ABI 的 exact ref。artifact 适合本地运行组合;instance 只能用于当前 Runtime 生命周期,不能写入 Artifact。
key 在一次 reconcile 的完整列表中必须唯一。parentKey 负责父子所有权层级;mount 负责逻辑挂载位置。对于 artifact,Runtime 以对象 identity 判断是否可复用,而不是对 Artifact 做深比较。
mount 不是 DOM target
这里的 mount 类型是 FlamoMountPoint,描述 child 在父实例图中的逻辑位置:
{ kind: 'root' }:挂到父 boundary 根;{ kind: 'node', nodeId }:挂到父级指定节点;{ kind: 'slot', slotId, outletId }:挂到指定 slot outlet;{ kind: 'portal', portalTarget }:逻辑 owner 仍是父实例,但物理位置由 portal target 决定。
它不同于 runtime.mount(target, artifact) 的 FlamoMountTarget,不会把 child 或父 Instance 重新绑定到浏览器 DOM 元素。
字段
artifact?unknownRuntime-local Artifact input. Durable graphs should prefer exact ref.
instance?RuntimeComponentInstancekeystringlayout?{ ... }mount?{ ... }ownership?"runtime" | "caller"parentKey?string | nullprepareRetries?numberprepareTimeoutMs?numberprops?Record<string, unknown>ref?{ ... }serviceAllowlist?readonly string[]slots?Record<string, unknown>statePolicy?"preserve" | "reset" | "migrate"
控制 child graph transaction 的取消、超时、重试、并发取胜规则与变更回调。
export interface ReconcileRuntimeChildrenOptionssignal:取消当前 transaction;返回状态通常为cancelled。timeoutMs:单个 child prepare 的默认超时时间。retries:child prepare 失败后的默认重试次数。latestWins:默认为 latest-wins;设为false时,同一父级已有活动 transaction 会拒绝新的协调。onChildrenChanged:供mountComposition()/hotUpdateComposition()的 composition controller 在初始化或动态结构 commit 后报告最新 specs。直接调用mountChild()、swapChild()、reconcileChildren()时,应读取 receipt 和instance.children,不要依赖该回调。
child spec 自己的 prepareTimeoutMs / prepareRetries 会覆盖这里的默认值。
字段
latestWins?booleanonChildrenChanged?{ ... }retries?numbersignal?AbortSignaltimeoutMs?number
记录一次 child graph transaction 的最终状态以及新增、复用、移除的 Instance id。
export interface RuntimeCompositionReceipt调用方至少应检查 status,不能仅以 Promise fulfilled 判断提交成功:
const receipt = await panel.reconcileChildren(specs);
switch (receipt.status) { case 'committed': console.log('mounted', receipt.mounted); console.log('reused', receipt.reused); console.log('removed', receipt.removed); break; case 'cancelled': case 'superseded': case 'rolled_back': case 'rejected': console.error(receipt.error?.code, receipt.error?.message); break;}mounted、reused、removed 都是 Instance id 数组。业务层需要稳定 child identity 时,继续使用传入的 RuntimeChildSpec.key 或 instance.child(key) 查询。
字段
error?{ ... }mountedreadonly string[]parentIdstringremovedreadonly string[]reusedreadonly string[]status"committed" | "rejected" | "cancelled" | "rolled_back" | "superseded"transactionIdstring
Dynamic component creation surface available inside a Node Script.
export interface NodeScriptComponentApi从 owning Artifact export 或精确外部引用创建 parent-owned 组件。
NodeScriptComponentApi.create(component: string | { ... }, options?: ComponentCreateOptions): Promise<ComponentInstanceHandle<TEvents>>字符串只解析当前 owning Artifact 的 export 及其完整 definition closure;跨 Artifact 创建必须传完整的 FlamoComponentRefV1,并通过 revision、hash、Runtime ABI 与 component ABI 校验。
const badge = await ctx.components.create('Badge', { parent: ctx.scene.root, props: { label: 'New' }, placement: { kind: 'append' }, key: 'dynamic-badge', signal,});准备、挂载或初始化失败时会回滚,不留下半挂载 entity。一个 owner 最多保留 1000 个动态 child。
参数
componentstring | { ... }options?ComponentCreateOptions
批量创建最多 100 个组件,并在任一失败时逆序回滚。
NodeScriptComponentApi.createBatch(requests: readonly ComponentCreateRequest[], options?: { ... }): Promise<readonly ComponentInstanceHandle<Record<string, unknown>>[]>批量级 AbortSignal 应用于全部请求,单个 request 自己的 signal 优先。任何请求失败时,已经创建的实例会按逆序销毁,调用方不会观察到部分成功的结果。
const handles = await ctx.components.createBatch([ { component: 'Avatar', props: { userId: 'a' } }, { component: 'Avatar', props: { userId: 'b' } },], { signal });参数
requestsreadonly ComponentCreateRequest[]options?{ ... }
Options for NodeScriptComponentApi.create.
export interface ComponentCreateOptions字段
key?stringStable child key within the owning instance. Runtime creates one when omitted.
parent?ComponentInstanceHandle<Record<string, unknown>> | RuntimeNodePhysical parent. Defaults to the current script boundary.
placement?ComponentPlacementSibling-relative placement. Defaults to append.
props?Record<string, unknown>Initial public properties validated before the child becomes observable.
signal?AbortSignalCancels preparation and rolls back any partially prepared child.
One entry accepted by NodeScriptComponentApi.createBatch.
export interface ComponentCreateRequest字段
componentstring | { ... }Owning-Artifact export name or an exact external component reference.
key?stringStable child key within the owning instance. Runtime creates one when omitted.
parent?ComponentInstanceHandle<Record<string, unknown>> | RuntimeNodePhysical parent. Defaults to the current script boundary.
placement?ComponentPlacementSibling-relative placement. Defaults to append.
props?Record<string, unknown>Initial public properties validated before the child becomes observable.
signal?AbortSignalCancels preparation and rolls back any partially prepared child.
Placement of a dynamically created component under its physical parent.
export type ComponentPlacement控制一个动态 child 的属性、事件、检查与销毁生命周期。
export interface ComponentInstanceHandleComponentInstanceHandle<TEvents> 属于一个 live child Instance:id 与 root 给出当前身份,setProps() 走原子属性事务,on() 返回幂等 disposer,inspect() 暴露公共状态与清理计数,destroy() 幂等释放当前实例和 parent-owned 后代。
句柄不能被序列化、写入 Artifact 或跨 realm 传递;需要恢复时应保存组件引用和输入,再创建新实例。
字段
idstringRuntime instance id.
rootRuntimeNodeLive root node of this component instance.
Atomically validate and commit a public property patch.
ComponentInstanceHandle.setProps(patch: Record<string, unknown>, options?: { ... }): Promise<FlamoPropsCommitReceipt>参数
patchRecord<string, unknown>options?{ ... }
Subscribe to a typed business event and receive an idempotent disposer.
ComponentInstanceHandle.on(event: K, handler: { ... }): { ... }参数
eventKhandler{ ... }
Read the current public and lifecycle inspector snapshot.
ComponentInstanceHandle.inspect(): FlamoInspectorInstanceSnapshotIdempotently destroy this instance and every parent-owned descendant.
ComponentInstanceHandle.destroy(): Promise<void>Identity and lifecycle surface for the Node Script's owning FlamoInstance.
export interface NodeScriptSelfApi字段
idstringOwning FlamoInstance id; this is not necessarily the current mounted node id.
rootRuntimeNodeLive root node of the whole owning FlamoInstance.
从 Node Script 内幂等销毁 owning Instance 及其所有权子树。
NodeScriptSelfApi.destroy(): Promise<void>await ctx.self.destroy() 会递归释放 parent-owned descendants、listeners、scheduled tasks、HTML surfaces、pointer captures 与资源引用。重复调用不会重复 teardown;销毁开始后不要再排入新的 mutation。
场景与节点查询
场景与节点查询
从 Instance 或 Node Script 的 scene 控制面查询稳定语义树;按名查询保留重复节点,并始终用数组表达零个、一个或多个结果。
Instance-scoped control surface for querying and mutating the semantic scene. Mounted ECS clones are intentionally excluded. Node Scripts that need the current physical materialization use `ctx.mounted` instead.
export interface SceneApi在指定语义 Scene 范围内按精确节点名查找全部稳定节点,并始终返回 RuntimeNode 数组。
SceneApi.findByName(name: string, options?: SceneNameQueryOptions): RuntimeNode[]查询区分大小写,保留重复名字,并按确定性的语义场景树顺序返回。没有匹配或 scope 无法解析时返回 [],不会返回 null。
const icons = ctx.scene.findByName('tag-close-icon', { scope: ctx.nodeId,});省略 scope 时从当前 Scene 根查询;scope 可以是节点 ID、RuntimeNode,或当前 Flamo Instance 内的 'global'。结果只包含 Artifact、instance-scoped 和 Scene 创建节点,不会遍历或返回当前 ECS clone。需要当前 Slot/Variant 的物理内容时使用 ctx.mounted;如果目标只用于响应事件,优先使用 ctx.delegate(),它会把动态查询与 canonical 事件路径匹配起来。
参数
namestringoptions?SceneNameQueryOptions
Controls the scene subtree searched by SceneApi.findByName.
export interface SceneNameQueryOptions字段
scope?NodeRefRoot node of the search. Omit it to search the owning scene root, or use `global` to search every root owned by the current Flamo instance.
Live handle for one node in the owning Flamo instance scene graph.
export interface RuntimeNode在一个已解析的 RuntimeNode 上直接监听统一事件。
RuntimeNode.on(event: UnifiedEventType, handler: UnifiedEventHandler, options?: EventListenerOptions): { ... }适合宿主代码先通过 instance.scene.get()、queryAll() 或 findByName() 获得 live node handle 后绑定事件。返回的函数用于注销监听,Instance 销毁后 handle 不应继续复用。
Node Script 内优先使用 ctx.onNode() 或 ctx.delegate(),以获得脚本生命周期清理、role 解析和动态命名节点语义。
参数
eventUnifiedEventTypehandlerUnifiedEventHandleroptions?EventListenerOptions
Flamo Runtime 的公开类型与调用合同。
RuntimeNode.emit(event: string, payload?: unknown): void参数
eventstringpayload?unknown
Any stable node reference accepted by the scene API. Entity handles returned by node-script helpers such as `ctx.role()` expose an `id` but are intentionally not `RuntimeNode` instances. Accepting the small structural contract keeps the documented `ctx.scene.mount(node, { parent: ctx.role('body') })` form type-safe.
export type NodeRefRuntimeNode 与事件桥接层使用的跨 Canvas、HTML Surface 和平台输入统一事件对象。
export interface UnifiedEvent该对象使用 canonical Runtime entity id 表达 target、currentTarget 和 composedPath(),并统一坐标、修饰键、pointer、wheel、keyboard、drag、gesture 与 DOM 输入字段。
组件 Node Script 通常通过 NodeScriptEventMap 获得更精确的事件类型;只有宿主直接使用 RuntimeNode.on() 或编写平台适配时才需要面向 UnifiedEvent。
事件处理器
export type UnifiedEventHandler统一事件类型(跨平台)- 扩展 FlamoEventType 以支持更多平台事件
export type UnifiedEventTypeMounted 实体与连续交互
Mounted 实体与连续交互
显式读取当前物理挂载实体、live probe 和 direct children,并按帧批量提交位置;这些瞬时 handle 不进入 Artifact 或语义查询。
身份域
语义树用于查找,Mounted 树用于当前物理实体
ctx.scene 始终返回 Artifact 节点、instance-scoped 引用和 Scene 创建节点组成的稳定语义树。 Variant、Slot 或重挂载产生的当前 ECS 实体通过 ctx.mounted.resolve/children/probe 显式读取。
mounted handle 只在当前 materialization 与 revision 内有效,不能持久化或解析其 id。 连续拖动时在 pointerdown 解析一次,并通过 ctx.setTransformsBatch() 每帧提交一批位置。
Semantic role/ref or an already-resolved mounted handle.
export type NodeScriptMountedRefOne entity in the current mounted ECS tree, paired with semantic identity when available.
export interface NodeScriptMountedNodeSnapshot字段
entityEntityHandleOpaque handle for the current physical entity.
instanceRootIdstring | nullOwning semantic instance root for an instance-scoped reference.
revisionnumberScene mutation revision at which this snapshot was resolved.
semanticRefstring | nullPreferred stable semantic reference, when Runtime can recover one.
snapshotNodeProbeSnapshot | nullLive geometry and visibility probe captured at `revision`.
sourceNodeIdstring | nullAuthoring source node id, when the entity comes from Artifact materialization.
Direct mounted children of one currently materialized parent entity.
export interface NodeScriptMountedChildrenSnapshot字段
childrenreadonly NodeScriptMountedNodeSnapshot[]Direct physical children in current ECS relation order.
parentNodeScriptMountedNodeSnapshotResolved mounted parent.
revisionnumberScene mutation revision shared by parent and children.
Flamo Runtime 的公开类型与调用合同。
export interface NodeScriptMountedContentSnapshot字段
contentsreadonly EntityHandle[]Current direct physical children in ECS relation order.
contentSnapshotsreadonly NodeProbeSnapshot | null[]Live probes aligned by index with `contents`.
primaryEntityHandle | nullFirst current direct child, retained for compatibility.
primarySnapshotNodeProbeSnapshot | nullLive probe for `primary`, when one exists.
revisionnumberScene mutation revision shared by every field in this snapshot.
viewportEntityHandleCurrent physical viewport handle. Do not persist or parse its id.
viewportSnapshotNodeProbeSnapshot | nullLive probe captured for the viewport at `revision`.
Explicit read boundary for the current physical ECS materialization.
export interface NodeScriptMountedApiResolve a semantic role/ref or mounted handle to the current physical entity.
NodeScriptMountedApi.resolve(ref: NodeScriptMountedRef): NodeScriptMountedNodeSnapshot | null参数
refNodeScriptMountedRef
读取一个当前 mounted parent 及其 direct physical children 的同 revision 快照。
NodeScriptMountedApi.children(ref: NodeScriptMountedRef): NodeScriptMountedChildrenSnapshot | null该方法按 ECS relation order 返回当前 direct children,不递归搜索名称,也不会改变 ctx.scene 的语义层级。它适合 Scroll viewport、Slot 当前内容和 Variant 的实时物理测量。
返回 null 表示 ref 当前无法 materialize/resolve;返回空 children 表示 parent 已解析但没有可探测的 direct child。缓存结果时同时保存 revision,scene revision 变化后重新读取。
参数
refNodeScriptMountedRef
Read the current live probe for the resolved physical entity.
NodeScriptMountedApi.probe(ref: NodeScriptMountedRef): NodeProbeSnapshot | null参数
refNodeScriptMountedRef
显式读取当前 ECS 物理挂载实体;SceneApi 继续只负责稳定语义树。
export const NodeScriptContext.mounted: NodeScriptMountedApi使用 resolve(ref) 取得当前 canonical mounted entity,使用 children(ref) 读取 direct physical children,使用 probe(ref) 读取 live geometry/visibility。ref 可以是 role、semantic ref 或已有 mounted handle。
返回 snapshot 的 revision 用于判断缓存是否过期。Variant、Slot 或 rematerialization 改变 revision 后重新解析;不要持久化或按字符串格式解析 entity.id。
const tree = ctx.mounted.children('viewport');if (!tree) return;
for (const child of tree.children) { ctx.trace('mounted-child', { semanticRef: child.semanticRef, revision: child.revision, });}按名称、role 或组件语义查询仍使用 ctx.scene。两棵树的完整区别见语义 Scene Tree 与 Mounted Entity Tree。
Convert a Flamo event world coordinate into the current node or target node's live local coordinates.
NodeScriptContext.eventPoint(event: BaseFlamoEvent, ref?: string): { ... } | null参数
eventBaseFlamoEventref?string
兼容旧 Scroll 脚本的 mounted children 投影;新代码使用 ctx.mounted.children()。
NodeScriptContext.mountedContent(ref: string): NodeScriptMountedContentSnapshot | null该方法保留 viewport / contents / primary / snapshots / revision 结构,只读取当前 direct physical children。它不会通过 SceneApi 搜索 mounted clone,也不允许调用方持久化 physical id。
新脚本使用 ctx.mounted.children(ref),以统一的 mounted node snapshot 读取 semantic ref、source、instance、live probe 和 revision。
参数
refstring
One position write in a frame-coalesced mounted transform batch.
export interface NodeScriptTransformBatchUpdate字段
refstring | EntityHandleSemantic role/ref or mounted handle resolved inside the owning SceneRuntime.
xnumberParent-local x position.
ynumberParent-local y position.
Result of accepting one mounted transform batch into the fast channel.
export interface NodeScriptTransformBatchReceipt字段
acceptedreadonly string[]Resolved entity ids accepted by the batch.
missingreadonly string[]Input refs that could not be resolved or contained non-finite coordinates.
在 owning SceneRuntime 的 fast channel 中合并一帧的位置写入,并返回 accepted/missing refs。
NodeScriptContext.setTransformsBatch(updates: readonly NodeScriptTransformBatchUpdate[]): NodeScriptTransformBatchReceipt用于 Scroll、Slider、拖拽面板等连续交互,把 content 与 thumb 的位置放在同一批中提交。每个 animation frame 最多调用一次;Runtime flush 后只发布一次 scene mutation revision,使 renderer 与 picker 观察同一几何状态。
const receipt = ctx.setTransformsBatch([ { ref: content.entity, x: contentX, y: contentY }, { ref: 'vertical-thumb', x: thumbX, y: thumbY },]);
if (receipt.missing.length > 0) { ctx.trace('scroll.targets.missing', receipt.missing);}accepted 表示已经进入 fast channel,不表示已经渲染;需要读取提交后的状态时等待下一帧并重新 probe。
参数
updatesreadonly NodeScriptTransformBatchUpdate[]
实体句柄(高频操作用)
export interface EntityHandle字段
children?EntityHandle[]获取子节点
idstringname?stringparent?EntityHandle获取父节点
transform{ ... }
配置节点的权威交互模式、pointer policy、透明命中面、cursor 与 z-index。
EntityHandle.setInteractive(value: { ... }): voidmode: 'disabled'、inert 和 pointerEvents: 'none' 是更强的禁用规则。box-only 只允许当前节点命中,box-none 只允许后代命中。
自动隐藏的 scrollbar gutter 可以在 paint opacity 为 0 时继续作为显式命中面:
gutter.setInteractive({ mode: 'enabled', pointerEvents: 'box-only', hitTestWhenTransparent: true, cursor: 'pointer',});hitTestWhenTransparent 不会让 visible: false 的节点恢复交互;它只解除 paint opacity 与 hit-test membership 的隐式绑定。
参数
value{ ... }
脚本与事件
脚本与事件
Node Script 只声明监听、代理和业务事件;命中测试、动态 clone、目标链、传播、坐标与输入事务由 Runtime 统一负责。
使用边界
脚本声明语义,Runtime 负责命中与传播
组件脚本使用 ctx.on()、ctx.onNode() 或 ctx.delegate() 订阅事件; 不需要读取包围盒、pick color、clone id,也不需要手工遍历父节点。按节点名查找使用instance.scene.findByName() 或 ctx.scene.findByName(),结果始终为数组。
event.target 是本次派发中稳定的 canonical Runtime entity id,currentTarget 与 phase 随 capture / target / bubble 更新;composedPath() 返回从目标到当前 Instance 根的完整 ID 路径。
定义由 Runtime 挂载并随节点生命周期自动清理的 Node Script。
defineNodeScript(config: DefineNodeScriptConfig<Props>): NodeScriptDefinition<Props>setup(props, ctx) 与 Vue 3 使用相同的参数顺序,并对普通 Component、Component Set 和普通图形节点统一有效。第一个参数是当前具体 FlamoInstance 的只读响应式输入;Component Set 继承脚本会收到当前 concrete INSTANCE 的有效 surface 与 Variant 参数。第二个参数负责状态、监听、节点访问、代理和公开方法。
节点的 x / y / fill / opacity / text 等视觉字段不会自动进入 props。普通图形不消费实例输入时使用 _props,通过 ctx.node().design() / current() 读取节点,通过语义 setter 或动画 API 修改节点。defineNodeScript<Props> 只增加 TypeScript 类型检查,不会创建 Artifact public surface。
export default defineNodeScript({ setup(props, ctx) { ctx.on('click', event => { ctx.emit('onClick', { targetNodeId: event.target, disabled: props.disabled }); }); },});普通图形脚本:
export default defineNodeScript({ setup(_props, ctx) { const shape = ctx.node(); if (!shape) return;
const initialOpacity = shape.design().compositing.opacity; ctx.on('pointerenter', () => shape.setOpacity(0.7)); ctx.on('pointerleave', () => shape.setOpacity(initialOpacity)); },});通过 ctx.on()、ctx.onNode()、ctx.delegate()、ctx.dispose() 登记的资源都属于当前 Instance 生命周期;脚本不需要自行管理 ECS、命中缓冲或 runtime clone。完整概念见脚本,事件工作流见事件、节点查询与动态代理。
参数
configDefineNodeScriptConfig<Props>
Author input accepted by defineNodeScript.
export interface DefineNodeScriptConfig字段
setupNodeScriptSetup<Props>surface?FlamoArtifactControlSurface
Normalized, mountable Node Script definition.
export interface NodeScriptDefinitionLifecycle-owned authoring and interaction context passed to a Node Script.
export interface NodeScriptContext在当前脚本根上监听统一事件,并参与 Runtime 的 capture、target、bubble 传播。
NodeScriptContext.on(event: K, handler: { ... }, options?: EventListenerOptions): { ... }默认监听 at-target 与 bubble 阶段,因此后代事件可以冒泡到组件根。pointerenter / pointerleave 不冒泡;需要监听稳定内部节点时使用 ctx.onNode(),按动态节点名匹配时使用 ctx.delegate()。
返回值是显式取消函数;即使不手动调用,Runtime 也会在脚本释放时清理监听。
参数
eventKhandler{ ... }options?EventListenerOptions
把统一事件监听直接绑定到当前脚本作用域内的稳定 role 或节点引用。
NodeScriptContext.onNode(ref: string, event: K, handler: { ... }, options?: EventListenerOptions): { ... }ref 优先解析 Artifact 中声明的 role,也可以是当前 Scene 能解析的节点引用。它适合语义稳定且基数明确的内部节点;variant 中可能动态出现、消失或重复的命名节点应使用 ctx.delegate()。
注册监听不会把显式 interactive: false 或 mode: 'disabled' 改回可交互。
参数
refstringeventKhandler{ ... }options?EventListenerOptions
直接监听当前同名后代,并在节点被替换后由 Runtime 自动重绑。
NodeScriptContext.onChild(event: K, query: NodeScriptEventDelegate, handler: { ... }, options?: EventListenerOptions): { ... }onChild() 适合确实需要逐元素直接监听语义的动态后代,包括 pointerenter、pointerleave、focus 与 blur。Runtime 会在 Variant、组件属性或 Artifact rematerialization 后刷新 exact-name 结果和 listener,不会继续依赖已经被替换的元素。
ctx.onChild('pointerenter', { name: 'tag-close-icon' }, event => { ctx.emit('onCloseHover', { targetNodeId: event.target });});默认优先考虑 ctx.delegate():冒泡事件走 canonical 路径,非冒泡事件走稳定祖先捕获,通常具有更低的绑定维护成本。
参数
eventKqueryNodeScriptEventDelegatehandler{ ... }options?EventListenerOptions
按精确节点名把事件代理给动态后代,非冒泡事件使用稳定祖先捕获。
NodeScriptContext.delegate(event: K, query: NodeScriptEventDelegate, handler: { ... }, options?: EventListenerOptions): { ... }Runtime 在每次事件发生时重新执行节点名查询,并把所有当前匹配与 canonical composedPath() 比较。variant 切换后目标暂时不存在是正常的空匹配;重复同名节点不会让同一次事件重复调用 handler。
ctx.delegate('click', { name: 'tag-close-icon' }, event => { ctx.emit('onClose', { targetNodeId: event.target });});用户脚本不应为此读取包围盒、pick color、clone id 或手工遍历父链。省略 scope 时限制在当前脚本边界,只有明确需要时才扩大到当前 Instance 的 'global' 范围。
pointerenter/leave、focus/blur 等非冒泡事件不会退化为一次性元素绑定。Runtime 在稳定祖先的捕获阶段代理,并在事件到达时匹配当前 canonical 路径,所以 variant 替换目标元素后监听仍然有效。只有必须保持直接监听语义时才改用会自动重绑的 ctx.onChild()。
参数
eventKqueryNodeScriptEventDelegatehandler{ ... }options?EventListenerOptions
用同一个入口处理 click、Enter 与 Space 激活。
NodeScriptContext.onActivate(ref: string | undefined, handler: { ... }, options?: NodeScriptActivationOptions): { ... }onActivate() 忽略重复 keydown,并让指针与键盘共享同一个 disabled 判断。Enter/Space 默认阻止平台默认行为;只有宿主确实需要保留默认动作时才设置 preventDefault: false。
export default defineNodeScript({ setup(props, ctx) { ctx.onActivate('submit-button', () => ctx.emit('submit'), { disabled: () => Boolean(props.disabled), }); },});参数
refstring | undefinedhandler{ ... }options?NodeScriptActivationOptions
从当前节点脚本发出一个组件或节点级业务事件。
NodeScriptContext.emit(event: string, payload?: T): voidemit() 用于把输入事件转换为组件 surface 中声明的业务语义,例如 onClick、onClose 或 submit。payload 应保持可验证、可序列化,不要暴露 ECS 对象、DOM 对象或短生命周期 clone handle。
它不会伪造 pointer 命中或重新派发原生输入;输入目标、传播与默认行为仍由 Runtime 事件系统负责。
参数
eventstringpayload?T
Shared pointer and keyboard policy for NodeScriptContext.onActivate.
export interface NodeScriptActivationOptions字段
disabled?boolean | { ... }Static or reactive guard applied equally to pointer and keyboard activation.
preventDefault?booleanPrevent Enter/Space platform defaults unless explicitly false.
Exact-name query used by NodeScriptContext.delegate.
export interface NodeScriptEventDelegate字段
namestringExact Figma/Flamo node name. Every current match is considered.
scope?NodeRefDefaults to the current script boundary; use global only deliberately.
Strongly typed event-name to payload map used by NodeScriptContext listeners.
export interface NodeScriptEventMap字段
beforeinputNodeScriptEvent<FlamoDomEvent>blurNodeScriptEvent<FlamoFocusEvent>changeNodeScriptEvent<FlamoDomEvent>clickNodeScriptEvent<FlamoClickEvent>compositionendNodeScriptEvent<FlamoDomEvent>compositionstartNodeScriptEvent<FlamoDomEvent>compositionupdateNodeScriptEvent<FlamoDomEvent>contextmenuNodeScriptEvent<FlamoClickEvent>copyNodeScriptEvent<FlamoDomEvent>cutNodeScriptEvent<FlamoDomEvent>doubleclickNodeScriptEvent<FlamoClickEvent>dragNodeScriptEvent<FlamoDragEvent>dragendNodeScriptEvent<FlamoDragEvent>dragstartNodeScriptEvent<FlamoDragEvent>focusNodeScriptEvent<FlamoFocusEvent>focusinNodeScriptEvent<FlamoFocusEvent>focusoutNodeScriptEvent<FlamoFocusEvent>gotpointercaptureNodeScriptEvent<FlamoPointerEvent>inputNodeScriptEvent<FlamoDomEvent>keydownNodeScriptEvent<FlamoKeyboardEvent>keyupNodeScriptEvent<FlamoKeyboardEvent>longpressNodeScriptEvent<FlamoGestureEvent>lostpointercaptureNodeScriptEvent<FlamoPointerEvent>panNodeScriptEvent<FlamoGestureEvent>panendNodeScriptEvent<FlamoGestureEvent>panstartNodeScriptEvent<FlamoGestureEvent>pasteNodeScriptEvent<FlamoDomEvent>pinchNodeScriptEvent<FlamoGestureEvent>pinchendNodeScriptEvent<FlamoGestureEvent>pinchstartNodeScriptEvent<FlamoGestureEvent>pointercancelNodeScriptEvent<FlamoPointerEvent>pointerdownNodeScriptEvent<FlamoPointerEvent>pointerenterNodeScriptEvent<FlamoPointerEvent>pointerleaveNodeScriptEvent<FlamoPointerEvent>pointermoveNodeScriptEvent<FlamoPointerEvent>pointeroutNodeScriptEvent<FlamoPointerEvent>pointeroverNodeScriptEvent<FlamoPointerEvent>pointerupNodeScriptEvent<FlamoPointerEvent>rotateNodeScriptEvent<FlamoGestureEvent>rotateendNodeScriptEvent<FlamoGestureEvent>rotatestartNodeScriptEvent<FlamoGestureEvent>tapNodeScriptEvent<FlamoGestureEvent>wheelNodeScriptEvent<FlamoWheelEvent>
Node Script 收到的统一事件投影,使用 canonical Runtime entity id 表达目标和传播路径。
export type NodeScriptEventtarget 在一次派发中保持稳定;同一个 pointer transaction 的逻辑目标 identity 也不会因 Variant rematerialization 改变。currentTarget 和 phase 随 capture / target / bubble 更新;composedPath() 返回从实际目标到当前 Instance 根的 ID 数组。coordinates 同时提供 screen、view、world 与可用时的 local 坐标。
事件族的附加字段由 NodeScriptEventMap 决定:pointer 有 pointer,wheel 有 delta,keyboard 有 key/code,gesture 有 delta/scale/rotation,HTML Surface 输入包含 data、value 或 clipboardData。
配置统一事件监听器的捕获阶段、单次执行和被动默认行为语义。
export interface EventListenerOptionscapture: true:在事件到达目标前的捕获阶段执行。once: true:第一次执行后自动注销。passive: true:声明 handler 不会取消平台默认行为;此时preventDefault()不生效。
消费 wheel、触控板或浏览器默认行为的监听器不能声明为 passive。stopPropagation() 与 stopImmediatePropagation() 属于事件对象,不属于 listener options。
字段
capture?boolean是否在捕获阶段触发
once?boolean是否只触发一次
passive?boolean是否为被动监听器
Event names emitted by the canonical Flamo event route.
export type EventTypeShared event payload before a concrete event family adds pointer, wheel, keyboard, drag, gesture, or DOM-specific data. Node scripts receive a NodeScriptEvent projection whose target, currentTarget, and composed path are canonical Runtime entity IDs.
export interface BaseFlamoEventUnion of every typed Flamo event family.
export type FlamoEventPointer input, pointer boundary, and pointer-capture event payload.
export interface FlamoPointerEventWheel or trackpad delta payload.
export interface FlamoWheelEventClick, double-click, and context-menu activation payload.
export interface FlamoClickEventKeyboard payload routed to the focused Runtime entity.
export interface FlamoKeyboardEventFocus-transition payload for focus, blur, focusin, and focusout.
export interface FlamoFocusEventDrag payload with the originating pointer and movement delta.
export interface FlamoDragEventTap, long-press, pan, pinch, and rotate gesture payload.
export interface FlamoGestureEventInput, composition, and clipboard payload bridged from an HTML Surface.
export interface FlamoDomEvent