Files
note2any/render-service-blueprint.md
2025-09-22 16:49:33 +08:00

209 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)
```ts
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)
```ts
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)
```ts
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 设计
```ts
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 草案
```ts
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. 性能基线脚本:统计渲染 + 上传耗时。
---
后续若需要,我可以直接生成骨架代码与测试样例,请指示。