Files
TrueGrowth/docs/MILKDOWN_NODE_SCHEMA_CONTEXT_LESSONS.md

3.3 KiB
Raw Blame History

Milkdown 自定义节点上下文经验

更新日期2026-04-30

背景

知识库正文区域打开后白屏,控制台报错:

MilkdownError: Context "nodes" not found, do you forget to inject it?

调用栈指向 KnowledgeBaseContent 打开知识库工具后的正文渲染链路。实际故障点不在知识库数据加载,而在 MarkdownEditor 挂载 Crepe/Milkdown 时注册自定义节点插件。

根因

Milkdown 的 $nodeSchema() 返回的是一组插件:

  • schema.ctx:注入节点 schema factory 所需的上下文
  • schema.node:读取 schema.ctx,再把节点写入 Milkdown 的 nodesCtx

因此如果手动拆开注册,顺序必须是:

schema.ctx,
schema.node,
view,

本次两个自定义插件曾写成 nodectx 前:

  • markdownImageBlockSchema.node
  • assetEmbedSchema.node

当 Milkdown 初始化 schema 时,node 插件会先尝试读取尚未注入的 ctx导致初始化链路异常知识库正文默认开启 enableAssetEmbeds,因此会稳定走到这条路径。

修复思路

最小修复只调整插件注册顺序,不扩大重构:

export const markdownImageBlockPlugins = [
  markdownImageBlockSchema.ctx,
  markdownImageBlockSchema.node,
  markdownImageBlockView,
].flat();

export const assetEmbedPlugins = [
  remarkAssetEmbed,
  assetEmbedSchema.ctx,
  assetEmbedSchema.node,
  assetEmbedView,
].flat();

这样保持了现有 Markdown 图片和素材嵌入能力,不改变知识库编辑器的外部 API。

验证

本次做了三类轻量验证:

pnpm nx run drawnix:typecheck
  • 类型检查通过
  • 用 JSDOM 直接创建 Crepe + 自定义 $nodeSchema 插件,确认 ctx -> node -> view 顺序可正常初始化
  • 在本地 localhost:7200 打开知识库,控制台未再出现 Context "nodes" 错误

.npmrc 中缺少 NPM_TOKEN 只会产生 pnpm warning不影响类型检查结论。

经验

1. 不要随手拆散组合插件

Milkdown 的 $nodeSchema()$remark() 这类 helper 通常返回带依赖顺序的插件组合。能整体 .use(schema) 时优先整体使用;必须拆开时,要确认每个子插件的上下文依赖。

2. 自定义节点注册顺序要固定

推荐约定:

remarkPlugin,
nodeSchema.ctx,
nodeSchema.node,
nodeView,
inputRules,
commands,

其中 ctx 永远在读取它的 nodeviewcommand 之前。

3. 编辑器白屏优先查插件链

遇到 Milkdown/Crepe 白屏时,排查顺序:

  1. 是否重复注册同名 node/mark
  2. $nodeSchema.ctx 是否在 $nodeSchema.node
  3. view 是否等待 schema ready 后才读取 node type
  4. 是否存在多份 @milkdown/core@milkdown/ctx
  5. 是否在 editor created 前调用 editorViewCtx

4. 同名扩展要单独立项

当前自定义 image-block 与 Crepe 内置 ImageBlock 存在同名扩展关系。最小修复只处理初始化顺序;后续若继续治理,建议单独评估:

  • 扩展内置 imageBlockSchema
  • 或关闭 CrepeFeature.ImageBlock 后由自定义插件完整接管

这类变更会影响图片上传、caption、resize、素材库插图等体验不应混入白屏热修。

一句话结论

Milkdown 自定义节点不是普通数组拼接,ctx 是 schema 初始化的地基;先注入上下文,再注册节点,最后挂 view。