1
0
Files
PPTX/openspec/changes/archive/2026-03-05-refactor-external-template-system/design.md
lanyuanxiaoyao f1aae96a04 refactor: 重构外部模板系统,改为单文件模板库模式
主要变更:
- 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件
- 添加模板库 YAML 验证功能
- 为模板添加 base_dir 支持,正确解析相对路径资源
- 内联模板与外部模板同名时改为警告(内联优先)
- 移除模板缓存机制,直接使用模板库字典
- 更新所有相关测试以适配新的模板加载方式

此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
2026-03-05 13:27:12 +08:00

7.1 KiB
Raw Blame History

Context

当前状态

当前项目有两种模板系统:

  1. 内联模板:在文档 YAML 的 templates 字段定义,使用字典结构
  2. 外部模板:通过 --template-dir 指定文件夹,每个模板是单独的 .yaml 文件

两种系统的查询逻辑不一致:

  • 内联模板:字典键查找 data['templates'][name]
  • 外部模板:文件系统查找 (templates_dir / name).yaml.exists()

约束条件

  • 必须保持中文注释和文档
  • 使用 uv 运行 Python 脚本
  • 不能修改主机环境配置
  • 需要更新 README.md 和 README_DEV.md

相关利益方

  • 使用外部模板的现有用户(需要迁移)
  • 模板开发者(需要了解新的模板库格式)

Goals / Non-Goals

Goals:

  1. 统一内联模板和外部模板的数据结构和查询逻辑
  2. 支持模板库元数据description、version、author
  3. 统一资源路径解析,避免相对路径错误
  4. 简化模板的分发和管理(单个文件 vs 多个文件)
  5. 优化错误提示,区分不同类型的错误

Non-Goals:

  1. 不支持向后兼容 --template-dir 参数
  2. 不支持模板库文件的热重载(文件监听仅在文档级别)
  3. 不改变模板的 vars 和 elements 结构

Decisions

1. 命令行参数命名:使用 --template

决策: 命令行参数从 --template-dir 改为 --template

理由:

  • --template 更简洁,直接表示指定模板文件
  • 与内联模板的概念更一致(都是指定模板来源)
  • 避免与 slide 中的 template 字段混淆(上下文清晰区分)

考虑的替代方案:

  • --template-file: 更明确但冗长
  • --template-lib: 表示模板库,但不如 --template 直观

2. 模板库文件格式:包含 templates 字典

决策: 模板库文件使用以下格式:

# 元数据字段(可选)
description: "模板库描述"
version: "1.0.0"
author: "作者"

# 必需字段
templates:
  template-name-1:
    vars: ...
    elements: ...
  template-name-2:
    vars: ...
    elements: ...

理由:

  • templates 作为必需字段,验证时检查其存在性
  • 元数据字段放在顶层,便于人类阅读和工具解析
  • 与内联模板的结构保持一致

考虑的替代方案:

  • 将所有内容放在 templates 下:会增加嵌套层级
  • 使用不同的必需字段名:templates 语义最清晰

3. 资源路径解析:提前解析为绝对路径

决策: 在模板渲染阶段就将图片相对路径解析为绝对路径

实现位置:

  • 外部模板:在 Template.render() 方法中解析
  • 自定义元素:在 Presentation.render_slide() 中解析

路径规则:

  • 外部模板元素:base_dir = 模板库文件所在目录
  • 内联模板元素:base_dir = 文档 YAML 所在目录
  • 自定义元素:base_dir = 文档 YAML 所在目录

理由:

  • 避免在渲染器中传递多个 base_path
  • 元素数据在渲染时就是绝对路径,避免后续错误
  • 符合"尽早解析"的设计原则

考虑的替代方案:

  • 在渲染时根据元素来源动态选择 base_path需要在元素对象上标记来源增加复杂度
  • 在渲染器中检查元素路径是否已解析:增加运行时开销

4. 移除外部模板缓存机制

决策: 移除 Presentation.template_cache,每次都从模板库创建新模板

理由:

  • 模板库文件是单个 YAML加载开销小
  • 简化代码逻辑,减少状态管理
  • 避免缓存一致性问题(模板库文件修改后)

考虑的替代方案:

  • 保留缓存并添加失效机制:增加复杂度,收益不大
  • 使用 LRU 缓存:对于少量模板没有必要

5. 同名冲突处理WARNING + 优先内联

决策:

  • 检测到同名冲突时,返回 ValidationIssue 级别 WARNING
  • 优先使用内联模板
  • 记录警告信息到验证结果

理由:

  • 内联模板是文档的一部分,优先级更高
  • WARNING 不会阻止验证,但提醒用户注意
  • 保持向后兼容(之前的行为是 ERROR

考虑的替代方案:

  • 抛出 ERROR过于严格与之前行为不一致
  • 优先外部模板:内联模板更贴近文档,应该优先

6. 错误类型区分

决策: 区分三种错误类型

错误类型 错误代码 级别 条件
模板库文件不存在 TEMPLATE_LIBRARY_FILE_NOT_FOUND ERROR --template 指定的文件不存在
缺少 templates 字段 TEMPLATE_LIBRARY_MISSING_TEMPLATES_FIELD ERROR 模板库文件没有 templates 字段
模板名称不存在 TEMPLATE_NOT_FOUND_IN_LIBRARY ERROR 模板库中找不到指定的模板名称
同名冲突 TEMPLATE_NAME_CONFLICT WARNING 内联和外部模板同名

理由:

  • 帮助用户快速定位问题
  • 错误消息更精确,减少调试时间

Risks / Trade-offs

Risk 1: 现有用户迁移成本

风险: 使用 --template-dir 的用户需要手动迁移模板

缓解措施:

  • 错误消息中提示使用新的参数格式

Risk 2: 模板库文件格式错误

风险: 用户创建的模板库文件格式不正确

缓解措施:

  • 递归验证每个模板的结构
  • 提供详细的错误消息,指出具体位置
  • 在文档中提供完整示例

Risk 3: 资源路径解析错误

风险: 相对路径解析后,模板库文件移动导致路径失效

缓解措施:

  • 错误消息中显示解析后的绝对路径
  • 建议用户使用绝对路径或将资源放在相对稳定的位置

Trade-off 1: 不支持向后兼容

权衡: 简化代码 vs 用户迁移成本

决策: 选择简化代码,不向后兼容

理由:

  • 项目处于活跃开发阶段,破坏性变更可接受
  • 统一的架构带来长期收益
  • 迁移成本可控(单个模板库文件)

Trade-off 2: 移除缓存 vs 性能

权衡: 简化代码 vs 略微的性能损失

决策: 选择简化代码

理由:

  • 模板数量通常不多(< 100
  • YAML 加载开销很小
  • 代码简洁性更重要

Migration Plan

迁移步骤

  1. 代码变更

    • 按照任务列表依次修改各模块
    • 每个模块修改后运行对应测试
  2. 测试更新

    • 移除旧模板系统的相关测试
    • 设计使用新模板系统的测试用例
    • 添加模板库文件的测试用例
    • 添加资源路径解析的测试用例
  3. 文档更新

    • 更新 README.md新的命令行参数格式
    • 更新 README_DEV.md模板库文件格式说明
    • 添加迁移指南

回滚策略

如果发现严重问题:

  1. 回滚代码到变更前的 commit
  2. 重新发布旧版本
  3. 修复问题后再次发布

Open Questions

  1. 是否需要提供模板库文件生成工具?

    • 当前未计划,手动创建 YAML 文件即可
    • 如果有大量需求,未来可考虑
  2. 是否需要支持模板库文件的远程 URL

    • 当前未计划,仅支持本地文件
    • 未来可通过 HTTP 下载支持
  3. 模板库元数据字段是否需要验证?

    • 当前不验证,用户可自定义
    • 未来可考虑定义标准字段(如 license、homepage