跳到正文

模块与扩展

在已熟悉 核心概念组件基础浏览器模块 的前提下,本篇说明如何把 BrowserModule、协作包、自定义 providers拼进同一个 Textbus 实例Module / imports / Plugin 的职责、启动与销毁顺序,以及 Registry 同名注册providers 覆盖 的常见结论与排错。

若当前需求只是增加块类型、格式或属性,请优先查阅 入门组件高级;本篇侧重 配置合并与扩展点时机

TextbusConfigModule

TextbusConfignew Textbus({ ... }) 的参数类型)继承 Module:根配置本身就可以带 components / formatters / attributes / providers / plugins 以及 Module 的生命周期钩子。另有 imports?: Module[],用于把 BrowserModuleCollaborateModule 等预打包模块与根配置 合并进同一编辑器实例

Module普通对象(类实例或字面量均可),描述「一批注册项 + 可选钩子」;不是必须继承某个基类。

常用配置字段

字段作用
components文档中会出现的 组件类componentName 唯一)。
formatters / attributes编辑器内可用的 格式 / 属性;可写 实例,或 (textbus) => 实例 延迟创建。
imports多个 Module 依次合并进当前编辑器。
providers@viewfly/coreProvider[],向 IoC 容器注册或 覆盖 实现(如替换 Adapter 需明确 provide 符号)。
plugins在主视图 render 完成之后 执行 setup(见下文 Plugin)。
readonly / historyStackSize / zenCoding / additionalAdapters只读、历史栈、语法糖、附加 Adapter 等全局项。

浏览器环境必须提供可用的 AdapterNativeSelectionBridge(一般由 BrowserModule 注册);否则 render 会失败。

imports 与列表合并

效果上可记住(组件 / 格式 / 属性providers / plugins 规则不同):

  • components / formatters / attributes:根配置里的项 先于import 模块 参与合并;同名时,根配置里的实现优先;若只在 imports 之间 有冲突,imports 数组里靠前的模块优先
  • providers:根与各模块的 providers根在前、随后按 imports 顺序 参与合并;同一 token 多次提供时,通常后出现的覆盖先前的(若与预期不符,用最小配置验证或调整 imports 顺序)。
  • plugins:根与各模块的 plugins 按顺序 排成一条,在 主视图就绪之后 依次执行 setup

formatters / attributes 使用 (textbus) => 实例 时,工厂会在 绑定到当前编辑器实例 后再得到最终实例列表。

Module 生命周期(顺序要点)

beforeEach

创建编辑器实例 时调用:先按 imports 顺序 调用各模块的 beforeEach,再调用 config.beforeEach(若有)。适合在 正式注册前 做轻量准备。

setup

render 流程中 await:先按 imports 顺序 调用各模块的 setup,再调用 config.setup。可返回 销毁回调(或返回 Promise,解析值为回调);destroy() 时会执行这些回调以释放你在 setup 里挂接的资源。

多个 setupPromise.all 等待,彼此之间没有固定先后顺序(仅保证全部完成后再继续后续启动步骤)。

onAfterStartup

编辑器完成初始化且主视图已就绪 之后,先按 imports 顺序、再调用根 onAfterStartup。适合 依赖 DOM 已存在或编辑循环已运转 的逻辑(例如自动聚焦、埋点)。

onDestroy

textbus.destroy() 中,会先走 根配置与插件的 onDestroy,再通知各 import 模块,随后执行 setup 返回的清理函数,最后 结束文档视图与内核相关服务。编写 onDestroy不要假设插件仍可用,按 由外到内 释放自定义资源。

页面卸载时务必调用 destroy(),避免输入层与订阅泄漏。

PluginModule.plugins

Plugin 只有 setup(textbus) 与可选 onDestroy()没有 components / providers 等字段。

执行时机Plugin.setupAdapter 渲染完成之后 才执行,晚于所有 Module.setup。适合 只依赖已就绪 DOM / 已挂载视图 的扩展(例如挂载工具栏、调试浮层)。与 Module 的分工:Module 负责「注册模型与平台依赖、搭好容器」;Plugin 负责「在视图就绪后挂接 UI 或副作用」。

Registry 与同名解析

textbus.get(Registry) 根据 componentName、格式名、属性名 解析字面量并创建组件或插槽。同名项 最终生效的是哪一份,由上一节 components / formatters / attributes 合并顺序」 决定:需要 覆盖 某内置块或格式时,把 自己的类或实例 写在 new Textbus 根配置 上,或把 Module 放在 imports 更前(在 仅调整 imports 顺序 时)。

providers 自定义与覆盖

providers@textbus/core 使用的 @viewfly/core Provider 形态一致(provide / useClass / useFactory / useValue / deps 等)。用于:

  • BrowserModule 提供 AdapterNativeSelectionBridge 等浏览器默认实现;
  • 由协作等模块 替换 History 等 token;
  • 由业务 注册 MessageBusCustomUndoManagerConfig 等(见 协作编辑)。

覆盖某能力时,provide 必须与目标 token 完全一致;不确定时以 类型定义协作编辑 等专题文档中的 provide 写法 为准。

示例:自定义 ModulePlugin

ts
import type { Module, Textbus } from '@textbus/core'

export const featureModule: Module = {
  setup(textbus: Textbus) {
    const sub = textbus.onReady.subscribe(() => {
      // 就绪后再访问 DOM / Commander 等
    })
    return () => sub.unsubscribe()
  },
}
ts
import type { Plugin, Textbus } from '@textbus/core'

export const toolbarPlugin: Plugin = {
  setup(textbus: Textbus) {
    // 主视图已挂载,可安全 querySelector、挂外部 UI
    void textbus
  },
  onDestroy() {},
}
ts
import { Textbus } from '@textbus/core'
import { featureModule } from './feature-module'
import { toolbarPlugin } from './plugin-toolbar'

const editor = new Textbus({
  imports: [featureModule],
  plugins: [toolbarPlugin],
})

实际工程须再并入 BrowserModule(或自行提供 AdapterNativeSelectionBridge),见 浏览器模块

排错摘要

  • 未配 BrowserModule(或等价 Adapter + NativeSelectionBridgerender 报错,提示缺少 NativeSelectionBridge / Adapter
  • 同名组件不生效:检查 components 合并顺序(根与 imports 先后),确认 componentName 与数据字面量一致。
  • providers 未覆盖预期:检查 同名 provide 是否被更晚的 Module 覆盖;必要时调整 imports 顺序 或把绑定挪到 最后加载的模块
  • destroy():监听与 setup 返回的清理函数 不会执行,易造成泄漏或二次挂载异常。

接下来

基于 MIT 许可发布