Files
note2any/docs/render-service-blueprint.md
2025-10-09 12:39:24 +08:00

8.5 KiB
Raw Permalink Blame History

RenderService Blueprint

目的:规划下一代渲染服务以替换(或包装)现有 ArticleRender + MarkedParser 组合,降低耦合,支持插件化与增量演进。 关联文档:architecture.md, image-pipeline.md, detaildesign.md

1. 现状问题 (Current Pain Points)

问题 说明 影响
逻辑集中 ArticleRender 同时负责读取、解析、样式拼接、延迟元素、上传协调 难测试 / 难替换子环节
隐式状态 originalMarkdown, cachedElements 内部字段耦合流程顺序 外部无法复用或二次解析
图片收集耦合 Marked 扩展 依赖前置 wikilink 转换 新语法添加需改内核
缺少中间 IR 无法对 Token/AST 做多阶段转换 功能扩展如统计、Lint困难
发布耦合渲染 上传/发布 API 与渲染混合 交互模式无法独立测试
单线程串行 图形生成、图片上传均串行 性能受限

2. 设计目标 (Design Goals)

目标 描述 衡量指标
分层解耦 渲染、转换、资源收集、发布分离 新增图片语法无需改核心类
插件式中间件 可在 parse → transform → render 各阶段注入 插件注册 API 清晰
可测试 纯函数/无副作用阶段隔离 单元测试覆盖率提升
IR 标准化 生成统一 AST / 节点类型 后续可序列化/缓存
并发能力 图形/上传并发控制 大图文耗时下降
可观测性 事件 & 钩子 before/after metrics 输出

3. 拟议分层 (Proposed Layers)

RenderPipeline
  ├─ Loader          (读取源 Markdown / 资源定位)
  ├─ Frontmatter     (解析 + Meta Store)
  ├─ Preprocessors[] (文本级转换:图片语法、短代码、宏)
  ├─ Parser          (Markdown -> AST 统一节点树)
  ├─ Transformers[]  (AST 级:节点重写、图片规范化、封面推断)
  ├─ ResourceIndex   (图片/图形/嵌入引用索引构建)
  ├─ Renderer        (AST -> HTML Fragment)
  ├─ Postprocessors[] (HTML 级:内联样式、链接修复、统计)
  └─ Exporters       (HTML/Text/JSON/WeChatDraft)

4. 关键接口 (Core Interfaces - Draft)

interface RenderContext {
  file: TFile;
  raw: string;            // 原始 Markdown
  frontmatter?: Record<string, any>;
  meta: WeChatArticleMeta; // 标题/作者/封面等
  ast?: MdAstRoot;         // 标准化 AST
  resources: ResourceIndex; // 图片/图形等
  html?: string;           // 渲染产物
  diagnostics: Diagnostic[];
  flags: Record<string, any>;
}

interface PipelineStage {
  name: string;
  run(ctx: RenderContext): Promise<void> | void;
  order?: number; // 可选执行排序
}

interface ResourceIndex {
  images: ImageCandidate[];
  diagrams: DiagramCandidate[];
  embeds: EmbedRef[];
}

interface ImageCandidate {
  id: string;          // 稳定标识
  kind: 'local'|'remote'|'base64'|'generated';
  original: string;    // 原始文本或路径
  basename?: string;
  vaultPath?: string;
  position: number;    // 在 raw 中的位置,用于封面决策
  meta?: Record<string, any>; // 尺寸/对齐等
}

5. 执行模型 (Execution Model)

class RenderService {
  private stages: PipelineStage[] = [];
  use(stage: PipelineStage) { this.stages.push(stage); }
  async render(file: TFile): Promise<RenderContext> {
    const ctx = createInitialContext(file);
    for (const s of sortByOrder(this.stages)) {
      try { await s.run(ctx); } catch (e) { ctx.diagnostics.push({stage: s.name, error: e}); if (isFatal(e)) break; }
    }
    return ctx;
  }
}

6. 示例阶段实现草稿

Stage 说明 是否必须
loader 读取文件与 raw
frontmatter 解析 YAML -> ctx.frontmatter / ctx.meta 预填
markdownImageCompat Markdown 图片 -> 统一节点形式 可选
shortcodeGallery 解析 gallery → 多个 image 节点 可选
parser Marked/Remark → AST
imageCollect AST 遍历收集图片,写入 ctx.resources.images
coverInfer 若无封面,从 images 按 position 选首图 可选
renderHTML AST -> HTML 字符串
styleInline 合成主题/高亮,自定义 CSS 注入 可选
finalize 产物整理 / hash / 缓存存储 可选

7. AST 规范 (Simplified)

type MdNode = Paragraph | Heading | Code | Image | Link | List | Blockquote | Html | ThematicBreak | Container;
interface Image { type:'image'; id:string; alt:string; url:string; title?:string; meta?:{width?:number;height?:number;align?:string;} }

可基于 remark/unified 生态替换当前 marked或构造最小抽象后桥接。

8. 事件 & Hook 设计

interface RenderHooks {
  on(stage: string, fn: (ctx: RenderContext) => void): void;
  emit(stage: string, ctx: RenderContext): void;
}

预定义事件:before:stageName / after:stageName / error:stageName

9. 并发策略

  • 图片上传与图形生成移出核心渲染,进入 PublisherService
  • PublisherService.publish(ctx, opts):接受 RenderContext内部
    1. ctx.resources.images 并发池(PromisePool)上传。
    2. 替换 ctx.html 中引用。
    3. 构建 DraftArticle / DraftImages

10. 迁移计划 (Phased Migration)

阶段 内容 验证点
P0 搭建 RenderService 空管线 + 复用旧 ArticleRender 结果 构建无回归
P1 拆出 loader/frontmatter/parser/coverInfer 旧行为对比 snapshot
P2 新 AST + imageCollect弃用 wikilink 转换 hack 图片计数稳定
P3 发布逻辑重构到 PublisherService 草稿发布一致
P4 Hook/插件系统开放 外部扩展示例
P5 并发上传 + 缓存 性能基线下降

11. 回滚策略

  • 每阶段保留配置开关:useLegacyRenderer
  • 出现渲染差异:比较 ctx.html 与 legacy.html diff 提示。
  • 发布失败回退:走旧 ArticleRender.postArticle

12. 测试策略

测试 说明
Snapshot 渲染 同一 Markdown 输入旧 vs 新 HTML 对比
AST 结构 断言图片/标题节点数量与顺序
封面选择 多组合frontmatter + 混合图片顺序)
Hook 调用 注册 mock 钩子计次
并发上传 人工注入延迟 → 顺序与最终替换正确

13. 指标与可观测

  • ctx.diagnostics 数量与类型统计。
  • 阶段耗时:performance.now() 差值注入 ctx.flags。
  • 上传耗时 / 失败率。

14. 安全考量

  • Stage 插件沙箱:限制访问仅上下文公开字段。
  • 阶段超时(可选):超过阈值标记 warning。
  • HTML 输出再次 sanitize。

15. API 草案

const rs = new RenderService();
rs.use(loader());
rs.use(frontmatter());
rs.use(markdownImageCompat({ enable: settings.enableMarkdownImageToWikilink }));
rs.use(parser({ engine:'remark' }));
rs.use(imageCollect());
rs.use(coverInfer());
rs.use(renderHTML({ themeProvider, highlightProvider }));
rs.use(styleInline());

const ctx = await rs.render(activeFile);
const draft = await publisher.publish(ctx, { type:'article', coverMode:'auto' });

16. 依赖与选型比较

方向 方案 A 方案 B 选择建议
Markdown AST remark/unified 继续 marked + 自建 AST 映射 remark 更标准;初期可混合
Hook 实现 事件总线 (mitt) 简单数组回调 先内建数组回调,后期引入库
并发控制 p-limit 自写 PromisePool p-limit 简洁可靠

17. 风险 & 缓解

风险 缓解
AST 转换差异导致样式变化 Snapshot + 逐阶段灰度
性能倒退 阶段耗时基线监控;必要时跳过冗余阶段
插件滥用 Hook 权限白名单 / 文档规范
并发上传触发限流 设置最大并发 + 429 重试

18. 成功判定 (Success Criteria)

  • 同一输入 50 篇示例笔记,新旧 HTML 差异行 <= 2%(忽略动态 id
  • 图片收集数量一致,封面判定一致率 100%。
  • 20+ 图片大文档总发布耗时下降 ≥25%。
  • 可插拔 demo增加一个统计字数的 Stage 无需改核心。

19. 下一步行动 (Action Items)

  1. 建立 RenderService 目录与最小类骨架。
  2. 搬迁读取/frontmatter/封面逻辑并保留旧 API 外壳。
  3. 引入 AST先轻量仅 Image/Heading/Paragraph
  4. Snapshot 测试脚手架。
  5. 性能基线脚本:统计渲染 + 上传耗时。

后续若需要,我可以直接生成骨架代码与测试样例,请指示。