Status
ADOPTED — v0.6.2 架构决策,替代旧 roadmap 中的 v0.7~v0.10 线性递增方案。
Context
LessJS 当前的 roadmap 采用 0.7 → 0.8 → 0.9 → 0.10 的线性递增。这个方案有几个问题:
- SemVer 语义不精确:0.7(Island Manifest)到 0.8(Fullstack)是架构能力的质变,不是量变。Patch 级别的递增不能传达这种差异。
- 1.0 的门槛模糊:什么时候可以打 1.0?是 P2 审计修复完成后?还是 Compiler 完成后?没有明确的判定标准。
- Compiler 和 Fullstack 的归属不清:这两个能力会改变"LessJS 是什么"的定义。如果放在 1.0 之后,就是 Breaking Change 需要升 2.0;如果放在 1.0 之前,它们还是 0.x 的正常演化。
- 审计发现尚未纳入:2026-05-07 四维审计的 P0/P1/P2 修复任务没有对应的版本号。
Decision
采用三段式版本路线,以"能力域"划分 MAJOR/MINOR,而非线性递增:
版本总览
v0.7.0 ← P0 稳定基线(审计修复)
v0.8.0 ← P1 功能完善 + Island Manifest
v0.9.0 ← i18n + View Transitions + Speculation Rules ← 实际交付偏离原计划
v0.10.0 ← SSR Architecture Purification + API Boundary ← 实际交付偏离原计划
v0.11.0 ← ISR + PWA + Compiler Alpha ← 原 v0.10 ISR/PWA 内容后移至此
v1.0.0 ← API 稳定承诺
v1.x ← 增量演进(不破坏公共 API)
v2.0.0 ← Compiler 成为默认(如需 Breaking Change)
判定 1.0.0 的标准
1.0.0 不是一个"完成了所有功能"的版本号,而是一个公共 API 稳定承诺的信号。以下条件全部满足时,可以打 1.0.0:
| # | 条件 | 理由 |
|---|---|---|
| 1 | 核心模块测试覆盖率 ≥ 80% | 覆盖率是 API 稳定的前提——没有测试的 API 无法保证行为不变 |
| 2 | CI 全链路门禁就位(test + typecheck + lint + coverage gate) | 自动化防止退化 |
| 3 | runtime-shim 由 AST 生成,不再手工维护 | 最大技术债消除,SSR 输出一致性可验证 |
| 4 | 公共 API 列表明确文档化(exports、config options、CLI flags) | 用户知道哪些是承诺的,哪些是内部的 |
| 5 | 至少 3 个真实项目 dogfooding(含 docs 站) | 实际使用验证 |
| 6 | 无 P0/P1 级已知 Bug | 质量基线 |
1.0.0 不要求:Compiler 生产可用、ISR 生产可用、Fullstack 示例丰富。这些是 1.x 的增量工作。
各版本详细规划
v0.7.0 — 稳定基线(P0 审计修复)
定位:消除不可信行为,建立工程纪律。
| 任务 | 优先级 | 说明 |
|---|---|---|
| render-dsd.ts 单元测试 | P0 | 770 行核心渲染器零覆盖 |
| island.ts 单元测试 | P0 | 321 行 Island 系统零覆盖 |
| runtime-shim 一致性测试 + 修复 | P0 | serializeAttributes 缺失 escapeAttrValue |
| headExtras/headFragments XSS 修复 | P0 | 重命名 + 运行时警告 |
| 消除静默 catch → console.warn | P0 | 8+ 处错误吞没 |
| CI 补全 | P0 | adapter-lit/docs 测试任务、发布门禁 |
| pre-commit hooks | P0 | 格式化/lint/类型检查守卫 |
为什么是 0.7.0 而不是 0.6.3:XSS 修复和 catch 行为变更属于破坏性变更(部分用户可能依赖当前的静默行为)。SemVer 0.x 的 MINOR 升级允许破坏性变更,正好对应。
v0.8.0 — 功能完善 + Island Manifest
定位:补全测试覆盖 + Island 系统演进。
| 任务 | 优先级 | 说明 |
|---|---|---|
| signals 测试套件 | P1 | 749 行零覆盖 |
| dsd-hydration.ts 单元测试 | P1 | Mixin 核心逻辑验证 |
| Signal 原生切换 | P1 | npm 依赖 + globalThis.Signal 条件回退 |
| render-dsd.ts 拆分为 4 模块 | P1 | 可维护性 |
| UI 组件统一到 DsdLitElement | P1 | 3 个组件未使用 Mixin |
| insertAfterHead 去重 | P1 | ui → core |
| 包版本统一 | P1 | 6 包版本不一致 |
| 定位重写(避免"全栈"过度承诺) | P1 | 诚实营销 |
| Interactive Playground | P1 | StackBlitz 一键体验 |
| Playwright E2E 测试 | P1 | 浏览器级集成测试 |
| Island Upgrade Manifest | Roadmap | 页面级 island 清单替代全局入口 |
| Speculative Loading 可观测 | Roadmap | eager/visible/idle 策略浏览器测试 |
合并旧 roadmap 的 v0.7 内容:Island Manifest 和 Speculative Loading 与 P1 测试/组件统一工作属于同一能力域,合并到 0.8.0 合理。
v0.9.0 — i18n + View Transitions + Speculation Rules
定位:国际化 + 浏览器原生性能 API 集成。
⚠️ 实际交付偏离原计划:原计划为 Serverless Fullstack(FormData actions, Typed RPC, env/secrets),实际因 SSG 稳定性需求和浏览器 API 成熟度而调整。i18n 和 View Transitions 是用户更紧迫的需求。Serverless Fullstack 推迟到 v1.x。
| 能力 | 说明 |
|---|---|
| @openelement/i18n 独立包 | 从 content 拆出,lessI18n() 独立插件,SSG locale 展开 |
| 双语文档站 | 25/30 页面英文版 + language switcher + en/zh 路径 |
| View Transitions API | 跨页面 MPA 动画(Chrome 111+, Safari 18+, Firefox 129+) |
| Speculation Rules API | 浏览器 prefetch/prerender(Chrome 121+) |
| SSG 后处理管线重构 | 5 步管线:clientScript → viewTransition → speculation → CSP → DSD polyfill |
v0.10.0 — SSR Architecture Purification + API Boundary
定位:消除 SSR 层全部边界违规,建立干净的公共 API。
⚠️ 实际交付偏离原计划:原计划为 SSG + ISR + PWA(路由级 revalidation, Cache lock, Stale fallback),实际因 ADR 0008-0014 架构净化需求优先而调整。ISR/PWA 内容后移至 v0.11。
| 能力 | 说明 |
|---|---|
| 消除 globalThis 桥接(ADR 0008) | 消除 createServer() + 全部 globalThis 桥接 |
| 消除 .less/ 临时文件(ADR 0010) | 构建 ctx 替代文件系统中间态 |
| 消除 last globalThis(ADR 0011) | closeBundle 替代 globalThis 数据传递 |
| 提取 @openelement/app(ADR 0012) | umbrella 函数抽到独立包 |
| 消除 less-runtime barrel(ADR 0013) | 内联 shim 替代 barrel 重导出 |
| SSR Bundle 公共 API(ADR 0014) | renderRoute() + getStaticPaths() + routeInfo |
v0.11.0 — ISR + PWA + Compiler Alpha
定位:构建能力成熟 + 编译器实验性能力。
| 任务 | 来源 | 说明 |
|---|---|---|
| 路由级 revalidation | 原 v0.10 | 按需重新生成静态页面 |
| Cache lock | 原 v0.10 | 并发构建时的缓存锁 |
| Stale fallback | 原 v0.10 | 新内容构建中返回旧内容 |
| Service Worker 策略 | 原 v0.10 | NetworkFirst/CacheFirst 可配置 |
| CDN recipes | 原 v0.10 | Cloudflare/Netlify 缓存配置模板 |
| AST 代码生成替代手工 runtime-shim | P2 #19 | 根治 SSR 输出一致性 |
| 增量 SSG 构建 | P2 #20 | 文件哈希缓存 |
| 性能基准 | P2 #24 | 构建时间/产物体积/Lighthouse/竞品对比 |
| 覆盖率收集 + 门禁 | P2 #25 | CI 覆盖率强制 |
| 视觉回归测试 | P2 #26 | 组件截图对比 |
| 依赖安全审计自动化 | P2 #27 | CI 集成 npm audit / Snyk |
| .less Compiler Alpha | ADR 0002 | 可选零框架组件编译 |
为什么 Compiler 在 P2 之后:Compiler 需要稳定的 DSD renderer 作为编译目标。如果 renderer 还在手工同步(runtime-shim 问题),Compiler 的输出也无法保证正确。P2 的 AST 替换解决了这个问题。
v1.0.0 — API 稳定承诺
判定标准:见上文 6 条标准。
1.0.0 的含义:
- 公共 API(
less()配置、CLI 命令、@openelement/core导出、@openelement/rpc导出)视为稳定 - Breaking Change 必须升 MAJOR
- Lit adapter 继续作为一等公民支持
- Compiler 仍为 opt-in,不是默认
v1.x — 增量演进
| 版本 | 方向 | 说明 |
|---|---|---|
| v1.1 | Compiler Beta → Stable | .less 文件成为推荐组件编写方式 |
| v1.2 | Fullstack 示例丰富 | CRM/admin 级 demo |
| v1.x | 更多 adapter | Vue adapter、React island bridge 等 |
注:
@openelement/blog开发提前到 v0.8.0 后启动(SSG 插件形态,不依赖 Fullstack),在 v0.9 期间 dogfooding。v1.0 时 blog 包随主框架一起稳定。
v2.0.0 — Compiler 成为默认(如果需要)
只有当以下条件同时满足时才考虑 2.0:
.lessCompiler 生产可用且经过大量项目验证- 社区已自然迁移到
.less组件 - 将 Compiler 设为默认会破坏现有 Lit 用户的工作流
如果 Lit 兼容模式可以无缝共存,则不需要 2.0。
Monorepo 包版本策略
LessJS 是 Deno workspace monorepo,7 个包发布到 JSR。仓库 Release tag 和包版本号不需要一致。
规则:谁改了谁升级,仓库 tag 取本次发布中最大的包版本号。
仓库 tag: v0.9.0
包版本变更:
@openelement/core 0.8.1 → 0.9.0 (SSR 属性绑定行为变更)
@openelement/adapter-lit 0.6.4 → 0.7.0 (interpolate 属性绑定输出变更)
@openelement/content 0.1.0 → 0.2.0 (新增 nav/sitemap 模块)
@openelement/ui 0.6.2 → 不变 (无变更)
@openelement/rpc 0.6.1 → 不变 (无变更)
@openelement/signals 0.6.2 → 不变 (无变更)
@openelement/create 0.6.1 → 不变 (无变更)
具体规则:
- 仓库 Release tag ≠ 任何单个包的版本。tag 是这批变更的整体发布代号。
- 包独立发版:JSR 按包发版,不要求所有包版本一致。
- 版本号判断标准:
- 有 Breaking Change 或行为变更 → MINOR bump(0.x 阶段)
- 纯 Bug fix / 新增非 Breaking 功能 → PATCH bump
- 没改 → 不动
- Release Note 按包分组,列出每个包的变更版本,末尾列出未变更的包。
- 跨包依赖版本引用:必须引用已发布到 JSR 的版本(
jsr:@scope/package@^version/subpath),不能引用本地路径。
为什么不用固定版本(Fixed/Locked):
- Babel/Angular 的做法要求所有包版本号相同,但在 JSR 生态中不必要——JSR 天然按包发版。
- 新增包(如 content 0.1.0)如果被强制跳到 0.9.0,版本号含金量稀释。
- 没改的包浪费版本号,changelog 虚胖。
为什么不用完全独立版本(不加仓库 tag):
- 用户需要知道"这一批变更的整体节奏"——仓库 tag 提供叙事感。
- GitHub Release 是用户发现更新的入口,不能省略。
版本号语义总结
| 版本段 | 含义 | Breaking Change 规则 |
|---|---|---|
| 0.7–0.10 | 框架在定义自己 | MINOR 升级允许破坏性变更 |
| 0.11 | 过渡版本:基础设施成熟 + 新能力引入 | 仍允许,但应尽量减少 |
| 1.0 | 公共 API 冻结 | 绝不允许(必须升 MAJOR) |
| 1.x | 增量扩展 | 公共 API 不破坏 |
| 2.0 | 范式转换(如果需要) | 允许,但需迁移指南 |
Consequences
正面:
- 每个版本号有清晰的语义——用户看到版本号就能判断升级风险
- 1.0.0 有明确的判定标准,不再是"感觉差不多了"的主观判断
- Compiler 和 Fullstack 在 0.x 阶段完成,不用担心 1.0 后的 Breaking Change
- 审计发现有了正式的版本归属
负面:
- 0.x 阶段拉长(0.7 → 0.11 共 5 个 MINOR 版本),可能给人"不够稳定"的印象
- 需要在 1.0 前维护公共 API 列表,增加了文档工作量
- 如果 0.11 的 Compiler Alpha 发现需要调整 DSD renderer 输出格式,可能影响 1.0 的时间线
缓解:
- 每个 0.x 版本都应该有明确的里程碑文档,传递"渐进成熟"而非"还不稳定"的信号
- 公共 API 列表在 v0.8 就开始文档化(标记为 "Stable API Candidate"),让 1.0 的门槛更具体
- 如果 0.11 Compiler Alpha 进展顺利,可以合并到 1.0 直接发布
参考
- ADR 0002: .less Compiler
- ADR 0005: WithDsdHydration Mixin
- SemVer 2.0.0
- LessJS 四维审计报告 (2026-05-07)
决策日期: 2026-05-07 | 版本: v0.6.2