Releases: lessjs-run/lessjs
v0.9.2
- Add injectViewTransitionMeta() for cross-page MPA animations (Chrome 111+, Safari 18+, Firefox 129+)
- Add buildSpeculationRulesJson() + injectSpeculationRules() for browser prefetch/prerender (Chrome 121+)
- Add SpeculationRulesOptions interface with heuristic + explicit rule modes
- Fix build-metadata.json: pass viewTransition + speculation from Phase 1 to Phase 3
- Fix scanPackageIslands test signal listener leak (sanitizeResources: false)
- Add FrameworkOptions.viewTransition (default: true) + speculation (boolean | SpeculationRulesOptions)
- Add 18 new tests for View Transitions + Speculation Rules (39 total ssg-postprocess tests)
- Bump @lessjs/core version 0.9.1 → 0.9.2
e2e Coverage (92 tests, 7 new spec files):
- view-transitions-speculation.spec.ts: v0.9.2 View Transitions + Speculation Rules
- navigation-routing.spec.ts: URL access, link navigation, 404, blog pages
- seo-meta.spec.ts: Open Graph, Twitter Cards, meta tags, sitemap, robots
- theme-system.spec.ts: dark/light toggle, localStorage persistence, cycling
- islands-reactivity.spec.ts: counter island, island script loading, upgrades
- i18n-locale.spec.ts: locale routes, locale switcher, SSG output
- accessibility-performance.spec.ts: a11y, load times, console errors, PWA
- helpers.ts: added getDocumentTheme, getMetaContent, countShadowRoots
Bug Fix (ssg-postprocess.ts):
- injectViewTransitionMeta: changed check from 'view-transition' to
'<meta name="view-transition"' to prevent content-text collision
(e.g. changelog page mentioning "view-transition" in body text) - injectSpeculationRules: changed check from 'speculationrules' to
'<script type="speculationrules"' for same reason
Regression Tests Added (ssg-postprocess.test.ts):
- injectViewTransitionMeta still injects when body text mentions view-transition
- injectSpeculationRules still injects when body text mentions speculationrules
Previous v0.9.2 audit fixes also included:
- LICENSE files for all packages (M1)
- docs vite.config.ts viewTransition + speculation (M2)
- README.md for JSR publish (L6)
- ADR 0007: View Transitions + Speculation Rules
- Changelog, roadmap, architecture doc updates
v0.9.1
LessJS v0.9.1 Release Notes
发布日期: 2026-05-09 | 42 commits 自 v0.8.0 以来
概述
v0.9.0 是一次重大架构升级:@lessjs/blog 重构为 @lessjs/content(统一博客+导航+站点地图),新增 @lessjs/i18n 独立包,实现全站双语(中/英),SSR 属性绑定保留,代码高亮 Shadow DOM 兼容,以及全面的文档站重构。净减 2,738 行代码。
核心变更
🌍 @lessjs/i18n — 国际化独立包
新增 @lessjs/i18n 包(v0.1.0),从 @lessjs/content 中拆分。i18n 是跨切面功能,不应耦合在内容管理模块中。
import { lessI18n } from '@lessjs/i18n';
export default defineConfig({
plugins: [
less({ routesDir: 'app/routes' }),
lessContent({ nav: { routesDir: 'app/routes' } }),
lessI18n({ locales: ['en', 'zh'], defaultLocale: 'en' }),
],
});- SSG Locale 展开: 构建时自动生成
dist/en/和dist/zh/两套完整页面(42 → 126 HTML) - Language Switcher:
<less-layout>内置语言切换,一键切换整站语言 - 路由辅助:
i18nStaticPaths()和switchLocale()简化路由级别的 i18n 逻辑 - 16 个单元测试覆盖全部 API
📦 @lessjs/blog → @lessjs/content — 统一内容插件
@lessjs/blog 已删除,被更强大的 @lessjs/content(v0.2.0)替代:
import { lessContent } from '@lessjs/content';
lessContent({
blog: { contentDir: 'content/blog', basePath: '/blog' },
nav: { routesDir: 'app/routes' },
sitemap: { hostname: 'https://example.com' },
});- 三合一: 博客 + 导航自动生成 + 站点地图,一个插件按需启用
- 导航自动生成: 路由文件导出
meta = { section, label, order },构建时聚合为侧边栏 - 站点地图: 自动生成
sitemap.xml,支持 changefreq 和 priority - 无向后兼容:0.x 版本允许破坏性变更,直接迁移即可
🔗 SSR 属性绑定保留
v0.8.0 的 SSR 会剥离所有属性绑定(.prop="${val}")。v0.9.0 改为保留属性绑定,转换为 kebab-case HTML 属性并序列化值:
<!-- 之前:属性绑定被剥离 -->
<less-layout>...</less-layout>
<!-- 现在:属性绑定转换为 HTML 属性 -->
<less-layout nav-items="[{...}]" header-nav="[{...}]">...</less-layout>这让数据驱动组件(如 <less-layout .navItems="${data}">)在 SSG 输出中保留数据,解决了移除 DEFAULT_NAV 后首页侧边栏为空的问题。
🎨 代码高亮 — Shadow DOM 兼容
代码高亮系统完全重写,解决 Shadow DOM 隔离导致 Prism.js 无法工作的问题:
<less-code-block>组件自包含高亮,将 tokenized 代码渲染进 shadow root- 移除外部
prism-init.js对 shadow root 的遍历(不可靠) - Prism CSS token 颜色注入组件内联样式
- 预加载 6 种语法(TypeScript、JavaScript、JSON、Bash、CSS、HTML)
文档站重构
删除的页面(不可运行的 mock 展示)
| 删除 | 行数 | 原因 |
|---|---|---|
/demo + /examples |
1,029 | mock 展示页不可运行,误导用户 |
/guide/api-design |
131 | 内容空洞,无实质指导 |
/guide/api-routes |
127 | 与 /guide/api 合并 |
/guide/blog-system |
155 | 重命名为 /guide/content-system |
/guide/design-philosophy |
109 | 与 /guide/positioning 合并 |
/guide/less-compiler |
162 | 未实现功能,删除避免误导 |
/styling/less-ui |
162 | 与 /ui 合并 |
新增的页面
| 新增 | 内容 |
|---|---|
/guide/comparison |
与 Astro/Next.js/Nuxt/SvelteKit 的对比分析 |
/guide/content-system |
@lessjs/content 使用指南(替代 blog-system) |
/guide/api |
Hono API 路由指南(合并 api-design + api-routes) |
/reference/core |
@lessjs/core API 参考 |
/community |
社区、贡献指南、团队信息 |
双语文档
25/30 页面已添加英文版本(_renderEn() 方法),SSG 构建输出 126 个 HTML 文件(3× 原始 42),每个路由 × 2 种语言 + 默认语言路径。
其他文档改进
- OG 社交图片:
/public/assets/og-image.svg自动注入<meta og:image> - 全文搜索: FlexSearch 客户端搜索 + 预建索引
- "Edit this page": 指南页底部添加 GitHub 编辑链接
- Changelog/Roadmap: 完整更新至 v0.9.0 状态
Bug 修复
| Bug | 修复 |
|---|---|
路由 scanner index/index.ts → /index |
filePathToRoutePath() 对 index 的处理逻辑修复,现在正确返回 / |
| 首页 language switcher 缺尾斜杠 | <less-layout> 添加 current-path 属性,修复 /en → /en/ |
| Prism 语法加载顺序 | prism-typescript 依赖 prism-javascript,必须先加载,否则 TypeScript 高亮永久失效 |
hello@kissjs.org 域名残留 |
UI 组件示例邮箱改为 hello@lessjs.org |
| 24 个文件含 UTF-8 BOM | 全部清除,消除 diff 干扰 |
| Roadmap 英文版数据不一致 | 测试计数 378+ → 414,补充缺失的 i18n 行 |
| 空 catch 块缺少注释 | ssg-postprocess.ts DSD polyfill 添加注释说明 |
lessBlog() 注释过时 |
blog-data.ts 注释更新为 lessContent() |
代码质量
| 指标 | v0.8.0 | v0.9.0 | 变化 |
|---|---|---|---|
| 测试 | 393 + 10 E2E | 430 | +37(含 i18n 16) |
| 包数量 | 8 | 9(+i18n) | +1 |
| 源码行 | — | — | -2,738 净减 |
@kissjs 残留 |
1 处 | 0 | 清零 |
| BOM 字符 | 24 处 | 0 | 清零 |
| TODO/FIXME | 0 | 0 | 保持 |
any 类型滥用 |
0 | 0 | 保持 |
扫描确认无问题
- ✅ 零
@kissjs/ KISSJS 域名残留(packages 源码) - ✅ 零
any类型滥用 - ✅ 零 TODO/FIXME/HACK
- ✅ 零循环导出
- ✅ 零 BOM 字符a
- ✅ 包间依赖一致,9 个包版本策略符合 ADR 0006
- ✅ 产品定位一致:SSG + DSD + Islands
包版本
| 包 | v0.8.0 | v0.9.0 | 变更 |
|---|---|---|---|
@lessjs/core |
0.8.0 | 0.9.0 | SSR 属性绑定、SSG locale 展开、路由修复 |
@lessjs/content |
— | 0.2.0 | 新包(替代 @lessjs/blog),+导航 +站点地图 |
@lessjs/i18n |
— | 0.1.0 | 新包,lessI18n() 插件 + 路由辅助 |
@lessjs/adapter-lit |
0.7.0 | 0.7.0 | SSR 属性绑定保留测试更新 |
@lessjs/ui |
0.6.2 | 0.6.2 | less-code-block 自包含高亮、language switcher |
@lessjs/signal |
0.6.2 | 0.6.2 | 无变更 |
@lessjs/rpc |
0.6.1 | 0.6.1 | 无变更 |
@lessjs/create |
0.6.1 | 0.6.1 | 模板版本更新 |
删除的包:
@lessjs/blog(被@lessjs/content替代,无向后兼容)
文件变更统计
132 files changed, 5,884 insertions(+), 8,622 deletions(-)
- Packages: 55 files changed, +2,176 / -661
- Docs: 63 files changed, +3,914 / -6,638
- 删除 7 个死路由页面 + demo/examples 目录
- 删除 deliverables/ 一次性审计报告
破坏性变更
0.x 版本允许破坏性变更。以下是迁移注意点:
@lessjs/blog→@lessjs/content: 插件入口从lessBlog()→lessContent({ blog: {...}, nav: {...}, sitemap: {...} }),子模块按需启用- i18n 从 content 拆出: 原先
lessContent({ i18n: {...} })→ 独立的lessI18n({ locales, defaultLocale })插件 - 删除的路由:
/demo、/examples、/guide/api-design、/guide/api-routes、/guide/blog-system、/guide/design-philosophy、/guide/less-compiler、/styling/less-ui - 删除的包:
@lessjs/blog已从 monorepo 移除,功能合并到@lessjs/content
下一步
- 完成剩余 5 个页面的英文翻译(changelog/ui/community/contributing/blog)
- Interactive Playground 实时组件演示
- Speculative Loading 可观测性
- @lessjs/content 扩展:Markdown 增强(Shiki 高亮、callout 容器)
LessJS v0.9.0 Release Notes
Released: May 9, 2026 | 42 commits since v0.8.0
Summary
v0.9.0 is a major architectural upgrade: @lessjs/blog is replaced by @lessjs/content (unified blog + nav + sitemap), a new @lessjs/i18n package enables full bilingual support (zh/en), SSR property bindings are preserved in output, code highlighting is Shadow DOM-compatible, and the docs site gets a comprehensive overhaul. Net reduction of 2,738 lines of code.
Core Changes
🌍 @lessjs/i18n — Standalone Internationalization
New @lessjs/i18n package (v0.1.0), extracted from @lessjs/content. i18n is a cross-cutting concern that doesn't belong in a content management module.
import { lessI18n } from '@lessjs/i18n';
export default defineConfig({
plugins: [
less({ routesDir: 'app/routes' }),
lessContent({ nav: { routesDir: 'app/routes' } }),
lessI18n({ locales: ['en', 'zh'], defaultLocale: 'en' }),
],
});- SSG Locale Expansion: Automatically generates
dist/en/anddist/zh/page sets at build time (42 → 126 HTML files) - Language Switcher: Built into
<less-layout>, one-click full-site language switching - Route Helpers:
i18nStaticPaths()andswitchLocale()simplify route-level i18n logic - 16 unit tests covering all API
📦 @lessjs/blog → @lessjs/content — Unified Content Plugin
@lessjs/blog is deleted, replaced by the more capable @lessjs/content (v0.2.0):
import { lessContent } from '@lessjs/content';
lessContent({
blog: { contentDir: 'content/blog', basePath: '/blog' },
nav: { routesDir: 'app/routes' },
sitemap: { hostname: 'https://example.com' },
});- Three-in-one: Blog + auto-generated navigation + sitemap, one plugin with opt-in modules
- Auto-generated Navigation: Route files export
meta = { section, label, order }, aggregated into sidebar at build time - Sitemap: Auto-generates
sitemap.xmlwith changefreq and priority support - No backward compat: 0.x allows breaking changes — migrate directly
🔗 SSR Property Bindings Preserved
v0.8.0's SSR stripped all property bindings (.prop="${val}"). v0.9.0 preserves them, converting to kebab-case HTML attributes with serialized values:
<!-- Before: property bindings stripped -->
<less-layout>...</less-layout>
<!-- Now: property bindings converted to HTML attributes -->
<less-layout nav-items="[{...}]" header-nav="[{...}]">...</less-layout>This allows data-driven components (like <less-layout .navItems="${data}">) to retain their data in SSG output, fixing the empty sidebar issue after DEFAULT_NAV removal.
🎨 Code Highlighting — Shadow DOM Compatible
The code highlighting system was completely rewritten to solve Shadow DOM isolation breaking Prism.js:
<less-code-block>component is self-contained, rendering tokenized code into its shadow root- Removed unreliable external
prism-init.jsshadow root traversal - Prism CSS token colors injected as component inline styles
- Pre-loaded 6 grammars (TypeScript, JavaScript, JSON, Bash, CSS, HTML)
Docs Site Overhaul
Removed Pages (non-runnable mock showcases)
| Removed | Lines | Reason |
|---|---|---|
/demo + /examples |
1,029 | Mock showcase pages not runnable, misleading |
/guide/api-design |
131 | Empty content, no substantive guidance |
/guide/api-routes |
127 | Merged into /guide/api |
/guide/blog-system |
155 | Renamed to /guide/content-system |
/guide/design-philosophy |
109 | Merged into /guide/positioning |
/guide/less-compiler |
162 | Unimplemented feature, removed to avoid confusion |
/styling/less-ui |
162 | Merged into /ui |
New Pages
| Added | Content |
|---|---|
/guide/comparison |
Competitive analysis vs Astro/Next.js/Nuxt/SvelteKit |
/guide/content-system |
@lessjs/content usage guide (replaces blog-system) |
/guide/api |
Hono API routes guide (me... |
v0.9
fix: resolve JSR publish failures + serializeAttributes camelToKebab (#1)
P0 fix — serializeAttributes() camelToKebab:
- Added camelToKebab() to core/render-dsd.ts
- serializeAttributes() now converts camelCase prop keys to kebab-case
(e.g. currentPath -> current-path) so Lit's attribute observer can
read them back on client-side upgrade - Regenerated runtime-shim.ts to include the new function
- Fixes GitHub Issue #1
P1 fix — JSR publish workspace path dependencies:
- adapter-lit/deno.json: removed workspace path imports
(@lessjs/core/render-dsd, less-runtime, logger) — Deno workspace
resolution handles these automatically - ui/deno.json: removed workspace path import of @lessjs/core
- core/deno.json: version 0.9.0-alpha-1 -> 0.9.0 so ^0.9.0 resolves
(semver pre-release rules exclude alpha from ^ ranges)
Full Changelog: v0.8.1...v0.9
0.9.0-alpha1
LessJS 0.9.0-alpha1
@lessjs/content — 全新内容插件
Breaking Change: @lessjs/blog 已被 @lessjs/content 替代,不再提供向后兼容。
@lessjs/content 是一个统一的构建时内容插件,集成三个独立模块,按需启用:
| 模块 | 功能 | 状态 |
|---|---|---|
| Blog | Markdown 解析 + 前端元数据 + 动态路由生成 | 从 @lessjs/blog 迁移 |
| Nav | 自动扫描路由文件的 meta 导出,生成侧边栏导航数据 |
🆕 |
| Sitemap | 从 SSG 产物自动生成 sitemap.xml + robots.txt |
🆕 |
import { lessContent } from '@lessjs/content';
export default defineConfig({
plugins: [
less(),
lessContent({
blog: { contentDir: 'content/blog', basePath: '/blog' },
nav: { routesDir: 'app/routes', headerNav: [...] },
sitemap: { hostname: 'https://lessjs.org' },
}),
],
});Blog 模块
与 @lessjs/blog 相同的 Markdown 解析能力(gray-matter + marked),但通过统一的 lessContent() 插件配置。支持自定义 Markdown 渲染器。
Nav 模块 🆕
自动从路由文件的 meta 导出提取导航数据,生成 virtual:less-nav 虚拟模块供 <less-layout> 侧边栏使用。无需手动维护 nav-data.ts。
// app/routes/guide/getting-started.ts
export const meta = { section: 'Guide', label: 'Getting Started', order: 10 };Sitemap 模块 🆕
SSG 构建完成后自动扫描 dist/ 目录,生成 sitemap.xml 和 robots.txt。支持排除路径、自定义 changefreq/priority。
SSR 属性绑定保留
Breaking Change: @lessjs/adapter-lit 的 interpolate() 函数现在将 Lit 属性绑定(.prop="${val}")序列化为 kebab-case HTML 属性,而不是直接丢弃。
| 之前 (v0.8.0) | 之后 (v0.9.0) |
|---|---|
.navItems="${data}" → 被丢弃 |
.navItems="${data}" → nav-items="[{...}]" |
| 嵌套组件收不到数据 | 嵌套组件在 SSR 阶段获得完整数据 |
renderNestedCustomElements() 中的 parseAttrsToProps() 也已更新:自动检测 JSON 格式的属性值并解析回 JS 对象/数组。
新增 camelToKebab() 辅助函数(adapter-lit/ssr.ts),将 navItems → nav-items 等属性名转换。
islandEffect() 泄漏修复
@lessjs/signal 的 islandEffect() 修复了三个资源泄漏问题:
- MutationObserver 未断开:组件移除后 MO 仍监听
- setInterval 未清除:轮询回调持续执行
- 重复清理:多个清理路径可能重复调用
dispose()
新实现使用统一的 teardown() 函数,保证 MO + interval + effect 三者只被清理一次。
SSG 管道增强
- virtual:less-nav 修复:Phase 3 SSG server 现在包含
less:ssg-virtual-nav插件,确保路由文件中import 'virtual:less-nav'正确解析 - @lessjs/content 初始化:Phase 3 自动检测并初始化 content 插件的 blog 数据存储
- Sitemap 生成:SSG 构建完成后自动调用
@lessjs/content/sitemap生成sitemap.xml
文档与仓库清理
- 删除
demo/目录(v0.4.0 遗留代码,无 CI、无引用) - 删除
/docs/routes/demo/和/docs/routes/examples/(mock 展示页) - 合并
/ui+/styling/less-ui→ 单一/ui页面 @lessjs/blog→@lessjs/content全局替换- 删除
deliverables/(审计报告 + PRD,不属于源码仓库) - 删除
docs/package.json、docs/CNAME(遗留配置) - 迁移
e2e/→docs/e2e/(Playwright 测试定位到 docs 站点) - 删除
playwright-report/、test-results/(构建产物,已加入.gitignore)
包版本
| 包 | 旧版本 | 新版本 |
|---|---|---|
| @lessjs/core | 0.8.1 | 0.9.0 |
| @lessjs/adapter-lit | 0.6.4 | 0.7.0 |
| @lessjs/content | 0.1.0 (原 @lessjs/blog) | 0.2.0 |
| @lessjs/rpc | 0.6.1 | — |
| @lessjs/signal | 0.6.1 | 0.6.2 |
| @lessjs/ui | 0.6.2 | — |
| @lessjs/create | 0.6.1 | — |
已知问题
serializeAttributes()在@lessjs/core/render-dsd.ts中缺少camelToKebab转换,currentPath被输出为currentpath而非current-path(#1)route-scanner.ts中await import(pkg)变量动态导入触发 JSRunanalyzable-dynamic-import警告(不影响功能,仅影响发布提示)
升级指南
-
替换 @lessjs/blog → @lessjs/content:
- import { lessBlog } from '@lessjs/blog'; + import { lessContent } from '@lessjs/content'; - lessBlog({ contentDir: 'posts' }) + lessContent({ blog: { contentDir: 'posts' } })
-
侧边栏导航迁移:删除手动维护的
nav-data.ts,在路由文件中添加meta导出,配置lessContent({ nav: { routesDir: 'app/routes', headerNav: [...] } }) -
Sitemap 启用:添加
lessContent({ sitemap: { hostname: 'https://yoursite.com' } }) -
SSR 属性绑定:如果之前有依赖属性绑定被丢弃的行为(不太可能),现在这些绑定会以 kebab-case 属性形式保留在 HTML 中
LessJS v0.9.0-alpha1
@lessjs/content — New Unified Content Plugin
Breaking Change: @lessjs/blog has been replaced by @lessjs/content with no backward compatibility.
@lessjs/content is a unified build-time content plugin that integrates three independent modules, each opt-in:
| Module | Purpose | Status |
|---|---|---|
| Blog | Markdown parsing + frontmatter + dynamic route generation | Migrated from @lessjs/blog |
| Nav | Auto-scans route files for meta exports, generates sidebar navigation data |
🆕 |
| Sitemap | Auto-generates sitemap.xml + robots.txt from SSG output |
🆕 |
import { lessContent } from '@lessjs/content';
export default defineConfig({
plugins: [
less(),
lessContent({
blog: { contentDir: 'content/blog', basePath: '/blog' },
nav: { routesDir: 'app/routes', headerNav: [...] },
sitemap: { hostname: 'https://lessjs.org' },
}),
],
});Blog Module
Same Markdown parsing capabilities as @lessjs/blog (gray-matter + marked), but configured through the unified lessContent() plugin. Supports custom Markdown renderer via the markdown option.
Nav Module 🆕
Automatically extracts navigation data from route file meta exports, generating a virtual:less-nav virtual module consumed by <less-layout> sidebar. No more manually maintained nav-data.ts.
// app/routes/guide/getting-started.ts
export const meta = { section: 'Guide', label: 'Getting Started', order: 10 };Sitemap Module 🆕
Automatically scans dist/ after SSG build completes, generating sitemap.xml and robots.txt. Supports path exclusion, custom changefreq/priority.
SSR Property Binding Preservation
Breaking Change: @lessjs/adapter-lit's interpolate() now serializes Lit property bindings (.prop="${val}") as kebab-case HTML attributes instead of stripping them entirely.
| Before (v0.8.0) | After (v0.9.0) |
|---|---|
.navItems="${data}" → stripped |
.navItems="${data}" → nav-items="[{...}]" |
| Nested components receive no data | Nested components get full data during SSR |
parseAttrsToProps() in renderNestedCustomElements() also updated: auto-detects JSON attribute values and parses them back to JS objects/arrays.
Added camelToKebab() helper in adapter-lit/ssr.ts for navItems → nav-items style conversions.
islandEffect() Leak Fix
@lessjs/signal's islandEffect() fixed three resource leaks:
- MutationObserver not disconnected: MO continued observing after element removal
- setInterval not cleared: Polling callback kept running indefinitely
- Duplicate cleanup: Multiple cleanup paths could call
dispose()more than once
New implementation uses a unified teardown() function guaranteeing MO + interval + effect are cleaned exactly once.
SSG Pipeline Enhancements
- virtual:less-nav fix: Phase 3 SSG server now includes
less:ssg-virtual-navplugin, ensuringimport 'virtual:less-nav'in route files resolves correctly - @lessjs/content initialization: Phase 3 auto-detects and initializes content plugin's blog data store
- Sitemap generation: After SSG build completes, automatically invokes
@lessjs/content/sitemapto generatesitemap.xml
Docs & Repository Cleanup
- Removed
demo/directory (v0.4.0 legacy, no CI, no references) - Removed
/docs/routes/demo/and/docs/routes/examples/(mock showcase pages) - Merged
/ui+/styling/less-ui→ single/uipage @lessjs/blog→@lessjs/contentglobal replacement- Removed
deliverables/(audit reports + PRDs, not source code) - Removed
docs/package.json,docs/CNAME(legacy configs) - Relocated
e2e/→docs/e2e/(Playwright tests target docs site) - Removed
playwright-report/,test-results/(build artifacts, added to.gitignore)
Package Versions
| Package | Old | New |
|---|---|---|
| @lessjs/core | 0.8.1 | 0.9.0 |
| @lessjs/adapter-lit | 0.6.4 | 0.7.0 |
| @lessjs/content | 0.1.0 (was @lessjs/blog) | 0.2.0 |
| @lessjs/rpc | 0.6.1 | — |
| @lessjs/signal | 0.6.1 | 0.6.2 |
| @lessjs/ui | 0.6.2 | — |
| @lessjs/create | 0.6.1 | — |
Known Issues
serializeAttributes()in@lessjs/core/render-dsd.tslackscamelToKebabconversion —currentPathis output ascurrentpathinstead ofcurrent-path(#1)await import(pkg)variable dynamic import inroute-scanner.tstriggers JSRunanalyzable-dynamic-importwarning (does not affect functionality, publish advisory only)
Upgrade Guide
-
Replace @lessjs/blog → @lessjs/content:
- import { lessBlog } from '@lessjs/blog'; + import { lessContent } from '@lessjs/content'; - lessBlog({ contentDir: 'posts' }) + lessContent({ blog: { contentDir: 'posts' } })
-
Sidebar navigation migration: Delete manually maintained
nav-data.ts, addmetaexports to route files, configurelessContent({ nav: { routesDir: 'app/routes', headerNav: [...] } }) -
Enable sitemap: Add
lessContent({ sitemap: { hostname: 'https://yoursite.com' } }) -
SSR property bindings: If you previously relied on property bindings being stripped (unlikely), they are now preserved as kebab-case HTML attributes
Full Changelog: v0.8.1...0.9.0-alpha1
v0.8.0
v0.8.0 — Audit Resolution + Structured Logging + Blog Plugin
v0.8.0 resolves all five audit items, introduces structured logging, auto-generates the runtime shim, optimizes nested DSD rendering, and ships the @lessjs/blog plugin with full dogfooding.
Audit Item Resolution
| # | Priority | Item | Status |
|---|---|---|---|
| 1 | P0 | Eliminate runtime-shim.ts manual sync risk | ✅ Done |
| 2 | P0 | Clarify error classification guide | ✅ Done |
| 3 | P1 | Optimize nested DSD rendering O(n²) | ✅ Done |
| 4 | P1 | Improve advanced feature docs | ✅ Done |
| 5 | P1 | Add integration tests | ✅ Done |
P0-1: Runtime Shim Auto-Generation
packages/core/scripts/generate-runtime-shim.ts uses TypeScript AST to extract functions from source files and auto-generate runtime-shim.ts. No more manual sync between source and shim — run deno task generate:runtime-shim to regenerate, verify with git diff --exit-code.
P0-2: Structured Logging + Error Classification
New @lessjs/core/logger module with createLogger(scope):
import { createLogger } from '@lessjs/core/logger';
const log = createLogger('ssg');
log.warn('Client build failed:', err.message);
log.info('Routes: 5 page(s), 2 API route(s), 8 island(s)');
log.debug('customElements.define("my-counter") skipped: already defined');All framework internals migrated from raw console.* to structured logging. Scopes: core → [LessJS], ssg → [LessJS/SSG], blog → [LessJS/Blog], signal → [LessJS/Signal]. Four log levels (DEBUG/INFO/WARN/ERROR) plus SILENT. The runtime shim includes a lightweight log stub that maps to console.* with the [LessJS] prefix.
P1-3: parse5 Nested DSD Optimization
Replaced regex-based O(n²) nested custom element rendering with a parse5 AST O(n×d) approach in render-nested.ts. Bottom-up traversal, proper DSD template detection, and attribute inference. Supports complex nesting scenarios the regex approach couldn't handle.
P1-4: Advanced Feature Docs
DSD guide (/guide/dsd), deep Islands guide (/guide/islands-deep), and RPC guide (/guide/rpc) — covering the three-layer DSD model, four island strategies, and cross-island communication patterns.
P1-5: Playwright E2E Tests
10 end-to-end tests covering DSD layers and nested custom elements against the built docs site:
e2e/dsd-layers.spec.ts— 5 tests: HTML structure, shadow roots, styles, no raw DSD text, custom element discoverye2e/nested-ce.spec.ts— 5 tests: custom elements in DOM, no raw text, LessJS components, shadow root upgrade, navigation
Core Features
Signal Native Switch
@lessjs/signal adds isNativeSignal() detection. When the browser supports TC39 Signals, it uses globalThis.Signal and falls back to polyfill automatically.
Island Upgrade Manifest
@lessjs/core/island-manifest generates per-page island manifests, replacing the global island entry. Each SSG page only loads the islands it actually uses.
@lessjs/blog Package
New Vite plugin package for blog/content sites. lessBlog() integrates into Vite build, parseMarkdownFile() handles Markdown + frontmatter, scanPosts() + generateBlogRoutes() auto-generate routes. The docs site itself dogfoods this plugin — 5 blog posts served from /blog.
v0.8 scope: .md → routes → list/post pages. No MDX, comments, or tags system.
Architecture Improvements
- render-dsd.ts split: 770-line monolith → 4 focused modules (core, escape, nested, barrel export). Backward-compatible.
- UI unified to DsdLitElement: 3 components migrated to the Mixin pattern. All UI components now share the same DSD hydration mechanism.
- insertAfterHead dedup: Moved from @lessjs/ui to @lessjs/core. Shared utility functions belong in core.
Bug Fixes
- Runtime shim
logundefined: Generated shim code referencedlogbut had no definition. Addedvar log = {...}stub to SHIM_BOILERPLATE. - island-effect cleanup leak: islandEffect() didn't properly clean up Signal effects in disconnectedCallback.
- buildIslandChunkMap double prefix: Re-prepended
islands/prefix, causing 404s in SSG HTML. - Full console. migration*: All raw
console.warn/error/debugcalls replaced withcreateLogger()across core, adapter-lit, and signals packages.
Test Coverage
393 unit tests passing. 10 Playwright E2E tests passing. Zero lint warnings. Zero type errors.
What's Next
v0.8 audit resolution is complete. Next focus areas:
- Interactive Playground for live component demos
- Speculative Loading observability (prefetch metrics)
- Continued @lessjs/blog dogfooding and feature expansion
v0.8.0 — Audit Resolution + Structured Logging + Blog Plugin
All five audit items resolved. Structured logging unifies framework diagnostics. Runtime shim is auto-generated. @lessjs/blog ships with full dogfooding.
Highlights
- Structured Logging:
createLogger(scope)with unified prefixes, four levels, SILENT mode. All internals migrated. - Runtime Shim Auto-Gen: TypeScript AST-based generation eliminates manual sync risk.
- parse5 Nested DSD: O(n²) → O(n×d) for complex nesting scenarios.
- Playwright E2E: 10 browser-level tests against SSG output.
- @lessjs/blog: Vite plugin for markdown-driven blogs, dogfooded on the docs site.
Bug Fixes
- Runtime shim
logundefined: Added log stub to generated shim boilerplate. - island-effect cleanup leak: Proper Signal effect cleanup on disconnect.
- buildIslandChunkMap double prefix: Fixed 404s in SSG HTML island scripts.
- Full console. migration*: No raw console calls remain in framework internals.
Test Coverage
393 unit + 10 E2E tests passing. Zero lint warnings. Zero type errors.
Full Changelog: v0.7.0...v0.8.0
v0.7.0
LessJS v0.7.0 — 稳定基线(P0 审计修复)
发布日期:2026-05-07
v0.7.0 是一次稳定化发布,修复了 2026-05-07 四维审计的全部 P0 发现。核心目标是消除不可信行为、建立工程纪律。本版本包含破坏性变更(XSS 修复、catch 行为变更),因此按 SemVer 0.x 约定升 MINOR。
变更概览
测试覆盖(新增 73 个测试)
| 模块 | 测试数 | 覆盖行数 |
|---|---|---|
render-dsd.ts |
44 | 770 行(此前零覆盖) |
island.ts |
29 | 321 行(此前零覆盖) |
render-dsd.ts — 覆盖 escapeHtml、escapeAttr、escapeAttrValue、serializeAttributes、renderDSD 全路径、L2 Nested DSD、XSS 安全、DSD options(delegatesFocus/serializable/slotAssignment/customElementRegistry)、pure-island layer、adapter protocol、边界情况。
island.ts — 覆盖 tagName 验证、元数据标记(__island/__tagName/__layer)、DSD opt-out、四种升级策略(eager/lazy/idle/visible)、幂等注册、connectedCallback 包装、getSSRProps、lessBind。
Bug 修复
- runtime-shim 一致性修复:
runtime-shim.ts的serializeAttributes()改用escapeAttrValue(),与render-dsd.ts保持一致。此前 null/undefined 值处理不一致。 - headExtras/headFragments XSS 警告:添加
@security/@dangerousJSDoc 标注。当注入内容包含<script>标签时,运行时打印console.warn提醒开发者注意 XSS 风险。 - 静默 catch 消除:修复 6 处残余静默 catch 块,改为
console.debug/console.warn,使错误可观测。涉及文件:island.ts、render-dsd.ts、cli/build-ssg.ts、cli/build-client.ts。
基础设施
- Pre-commit Hooks:
.githooks/pre-commit自动运行deno fmt --check+deno lint+deno check,通过deno task hooks:install启用。 - CI adapter-lit 测试:test.yml 新增
test-adapter-litjob。 - CI 发布门禁:publish.yml 添加
needs: [test]依赖,测试不通过不能发布。 - Cloudflare Pages 迁移:从 GitHub Pages 迁移到 Cloudflare Pages Connect GitHub 模式。main → Production(lessjs.com),dev → Preview(每次推送自动分配 URL)。
破坏性变更
- runtime-shim
serializeAttributes:现在通过escapeAttrValue处理 null/undefined,而非直接传给escapeAttr。如果你之前依赖 null 被字符串化的行为,现在会输出空字符串。 - 静默 catch → 可观测错误:此前吞没错误的代码现在会打印
console.warn或console.debug(带[LessJS]前缀)。如果错误监控将这些视为噪音,请调整日志过滤规则。
测试结果
354 passed, 0 failed
版本策略
完整的 v0.7 → v2.0 路线图详见 ADR 0006: 版本号策略。
下一个版本:v0.8.0 — P1 功能完善 + Island Manifest + Blog 开发启动。
升级方式
# 更新项目依赖
deno run -A jsr:@lessjs/create
# 安装 pre-commit hooks(推荐)
deno task hooks:installLessJS v0.7.0 — Stable Baseline (P0 Audit Fixes)
Release Date: 2026-05-07
v0.7.0 is a stability release that addresses all P0 findings from the four-dimensional audit (2026-05-07). The focus is on eliminating untrusted behaviors and establishing engineering discipline. This release contains breaking changes (XSS fix, catch behavior change), hence the MINOR bump per SemVer 0.x conventions.
What Changed
Testing (73 new tests)
| Module | Tests | Lines Covered |
|---|---|---|
render-dsd.ts |
44 | 770 (was 0) |
island.ts |
29 | 321 (was 0) |
render-dsd.ts — Covers escapeHtml, escapeAttr, escapeAttrValue, serializeAttributes, renderDSD (all paths), L2 Nested DSD, XSS safety, DSD options (delegatesFocus/serializable/slotAssignment/customElementRegistry), pure-island layer, adapter protocol, and edge cases.
island.ts — Covers tag name validation, metadata markers (__island/__tagName/__layer), DSD opt-out, four upgrade strategies (eager/lazy/idle/visible), idempotent registration, connectedCallback wrapping, getSSRProps, and lessBind.
Bug Fixes
- runtime-shim consistency:
serializeAttributes()inruntime-shim.tsnow usesescapeAttrValue()instead ofescapeAttr, matchingrender-dsd.ts. Previously, null/undefined values were not handled consistently. - headExtras/headFragments XSS warnings: Added
@security/@dangerousJSDoc annotations. Runtimeconsole.warnis emitted when injected content contains<script>tags. - Silent catch elimination: 6 remaining silent catch blocks replaced with
console.debug/console.warn, making errors observable. Affected files:island.ts,render-dsd.ts,cli/build-ssg.ts,cli/build-client.ts.
Infrastructure
- Pre-commit hooks:
.githooks/pre-commitrunsdeno fmt --check+deno lint+deno check. Enable withdeno task hooks:install. - CI adapter-lit tests: New
test-adapter-litjob in test.yml. - CI publish gate: publish.yml now requires
needs: [test]— no publish without passing tests. - Cloudflare Pages migration: Deployed from GitHub Pages to Cloudflare Pages (Connect GitHub mode).
main→ Production (lessjs.com),dev→ Preview (auto-assigned URL per push).
Breaking Changes
- runtime-shim
serializeAttributes: Now handles null/undefined viaescapeAttrValueinstead of passing throughescapeAttr. If you were relying on the old behavior of stringifying null, this will now output an empty string. - Silent catch → observable errors: Code that previously swallowed errors silently will now log
console.warnorconsole.debugwith[LessJS]prefix. If your error monitoring treats these as noise, adjust your log filters.
Full Test Suite
354 passed, 0 failed
Version Strategy
See ADR 0006: Version Strategy for the full roadmap from v0.7 → v2.0.
Next: v0.8.0 — P1 feature improvements + Island Manifest + Blog development kickoff.
Upgrade
# Update your project's dependency
deno run -A jsr:@lessjs/create
# Install pre-commit hooks (recommended)
deno task hooks:installFull Changelog: v0.6.1...0.7.0
Full Changelog: v0.6.1...v0.7.0
v0.6.2
更新日志
LessJS 的所有重要变更均记录在此文件中。
[0.6.2] - 2026-05-07
新功能
WithDsdHydration Mixin + 三层 DSD 模型
引入声明式 DSD Hydration Mixin,替代旧的 LitDsdElement 基类,并建立三层组件模型。
| 层级 | 类型 | 说明 |
|---|---|---|
| Layer 1 | dsd-static |
SSG 预渲染,无客户端交互 |
| Layer 2 | dsd-interactive |
DSD 预渲染 + 声明式事件绑定(WithDsdHydration) |
| Layer 3 | pure-island |
纯客户端 Island,无 DSD |
-
@lessjs/adapter-lit:新增
WithDsdHydrationMixin 和DsdLitElement预组合基类- 检测 DSD 预填充的 shadow root,自动跳过重复渲染
static hydrateEvents声明式事件绑定- AbortController 自动清理
- 新增
DsdHydration和DsdHydrationMixin类型接口
-
@lessjs/core:新增类型与接口
ComponentLayer类型(dsd-static/dsd-interactive/pure-island)HydrateEventDescriptor接口(声明式事件绑定)island()新增dsd选项(默认true,设false= pure-island)__layer元数据标记
移除
Hydratable接口和RenderAdapter.hydrate死代码- Lit 专属的
requestUpdate检测逻辑(从lessBind()移除) @lessjs/adapter-lit中_extractCeTag()死函数injectLayoutStyles从@lessjs/core移至@lessjs/ui/ssg-inject(消除 core→ui 循环依赖)
UI 组件迁移
less-theme-toggle、less-code-block、less-layout→ 迁移至 WithDsdHydration Mixinless-dialog→ 修复缺失的 DSD hydrationless-hero-ping→ 标记为 Layer 3 pure-island
JSR 发布修复
| 问题 | 修复 |
|---|---|
| 跨包相对路径 JSR 无法解析 | 替换为 jsr:@lessjs/... |
jsr: 缺少版本约束 |
添加 @^0.6.0 约束 |
jsr: 子路径格式错误 |
jsr:@lessjs/ui/tokens@ver → jsr:@lessjs/ui@ver/tokens |
@lessjs/adapter-lit 缺少 jsr: imports |
添加 render-dsd / less-runtime 映射 |
| Mixin extends 不被 JSR 快速类型检查器支持 | 导出预组合 DsdLitElement 基类 |
WithDsdHydration 缺少显式返回类型 |
添加 : T & Constructor<DsdHydrationMixin> + DsdHydrationMixin 接口 |
scanPackageIslands 动态导入不可分析 |
标注为有意设计,添加文档注释 |
文档
- 新增 ADR 0005:WithDsdHydration Mixin 决策记录
- README badge 样式更新
包版本
| 包 | 版本 |
|---|---|
| @lessjs/core | 0.6.1 → 0.6.2 |
| @lessjs/adapter-lit | 0.6.1 → 0.6.2 |
| @lessjs/ui | 0.6.1 → 0.6.2 |
| @lessjs/rpc | 0.6.1(未变) |
| @lessjs/signal | 0.6.1(未变) |
| @lessjs/create | 0.6.1(未变) |
变更统计
24 个文件变更,+822 / -209 行
[0.6.1] - 2026-05-05
变更
- 移除 KISS 遗留代码,所有包升级至 0.6.1
- 修复跨包 JSR 发布路径
- 重命名
@lessjs/signals→@lessjs/signal以匹配 JSR 包名
Changelog
All notable changes to LessJS will be documented in this file.
[0.6.2] - 2026-05-07
New Features
WithDsdHydration Mixin + Three-Layer DSD Model
Introduced a declarative DSD Hydration Mixin replacing the old LitDsdElement base class, and established a three-layer component model.
| Layer | Type | Description |
|---|---|---|
| Layer 1 | dsd-static |
SSG pre-rendered, no client interactivity |
| Layer 2 | dsd-interactive |
DSD pre-rendered + declarative event binding (WithDsdHydration) |
| Layer 3 | pure-island |
Pure client-side Island, no DSD |
-
@lessjs/adapter-lit: Added
WithDsdHydrationMixin andDsdLitElementpre-composed base class- Detects DSD-pre-populated shadow root, skips duplicate rendering
- Declarative event binding via
static hydrateEvents - Automatic cleanup via AbortController
- Added
DsdHydrationandDsdHydrationMixintype interfaces
-
@lessjs/core: Added types and interfaces
ComponentLayertype (dsd-static/dsd-interactive/pure-island)HydrateEventDescriptorinterface (declarative event binding)- Added
dsdoption toisland()(defaulttrue,false= pure-island) - Added
__layermetadata marker on island classes
Removed
Hydratableinterface andRenderAdapter.hydratedead code- Lit-specific
requestUpdatedetection fromlessBind() _extractCeTag()dead function in@lessjs/adapter-lit- Moved
injectLayoutStylesfrom@lessjs/coreto@lessjs/ui/ssg-inject(eliminates core→ui circular dependency)
UI Component Migration
less-theme-toggle,less-code-block,less-layout→ Migrated to WithDsdHydration Mixinless-dialog→ Fixed missing DSD hydrationless-hero-ping→ Marked as Layer 3 pure-island
JSR Publishing Fixes
| Issue | Fix |
|---|---|
| Cross-package relative paths unresolvable by JSR | Replaced with jsr:@lessjs/... |
Missing version constraints on jsr: imports |
Added @^0.6.0 constraint |
| Incorrect JSR subpath format | jsr:@lessjs/ui/tokens@ver → jsr:@lessjs/ui@ver/tokens |
@lessjs/adapter-lit missing jsr: imports |
Added render-dsd / less-runtime mapping |
Mixin extends unsupported by JSR fast type checker |
Exported pre-composed DsdLitElement base class |
WithDsdHydration missing explicit return type |
Added : T & Constructor<DsdHydrationMixin> + DsdHydrationMixin interface |
Unanalyzable dynamic import in scanPackageIslands |
Annotated as intentional with documentation comments |
Documentation
- Added ADR 0005: WithDsdHydration Mixin decision record
- Updated README badge styles
Package Versions
| Package | Version |
|---|---|
| @lessjs/core | 0.6.1 → 0.6.2 |
| @lessjs/adapter-lit | 0.6.1 → 0.6.2 |
| @lessjs/ui | 0.6.1 → 0.6.2 |
| @lessjs/rpc | 0.6.1 (unchanged) |
| @lessjs/signal | 0.6.1 (unchanged) |
| @lessjs/create | 0.6.1 (unchanged) |
Change Stats
24 files changed, +822 / -209 lines
[0.6.1] - 2026-05-05
Changes
- Removed KISS legacy code, bumped all packages to 0.6.1
- Fixed cross-package JSR publish paths
- Renamed
@lessjs/signals→@lessjs/signalto match JSR package name
v0.6.1
v0.6.1 — Declarative Shadow DOM + 岛屿架构 + Web Standards
概述
v0.6.1 是 LessJS 历史上最大的架构升级。引入 Declarative Shadow DOM (DSD) 作为核心渲染模型、4 种策略的岛屿懒加载、Safe/Unsafe HTML 品牌类型、基于 TC39 Signals 的响应式系统、Form-Associated Custom Elements、Navigation API,以及全面对齐 WHATWG Web Standards。
版本号从 v0.5.5 直接跳到 v0.6.1(跳过 0.6.0),原因是 JSR 发布流水线修复过程中进行了多次迭代。代码本身代表完整的 v0.6 里程碑。
自 v0.5.5 以来 38 个提交,102 个文件变更,+4,509 / −3,259 行。
新功能
Declarative Shadow DOM (DSD) 渲染模型
v0.6 最大的变化。LessJS 现在将页面渲染为纯 DSD HTML —— 首次绘制不需要任何 JavaScript。
| 功能 | 描述 | 规范 |
|---|---|---|
| DSD 字符串渲染器 | renderDSD() 同步生成 <template shadowrootmode="open"> HTML |
WHATWG HTML §13.4 |
| L2 嵌套 DSD | 嵌套 Custom Elements 递归渲染 —— 子 CE 在父级 shadow DOM 内拥有自己的 <template shadowrootmode> |
DOM Standard |
| DSD 规范对齐 | shadowrootdelegatesfocus、shadowrootserializable、shadowrootslotassignment、shadowrootcustomelementregistry —— 通过 DsdOptions 支持所有 WHATWG 属性 |
WHATWG HTML Standard |
| DSD Polyfill | 自动注入 <script> polyfill —— Firefox 尚不支持原生 DSD,使用 attachShadow() 降级 |
— |
| DSD 水合 | 组件检测预存在的 shadow root 内容,跳过 Lit 重新渲染,避免 DOM 节点重复 | — |
岛屿架构 —— 4 种策略
| 策略 | 触发方式 | 使用场景 |
|---|---|---|
eager |
立即导入 | 关键交互组件 |
lazy |
requestIdleCallback |
默认 —— 非关键交互 |
idle |
同 lazy |
可读性别名 |
visible |
IntersectionObserver |
首屏以下组件 |
新的 island() 包装器替代旧的猴子补丁模式:
import { island } from '@lessjs/core';
class MyCounter extends LitElement { /* ... */ }
export default island('my-counter', MyCounter, { strategy: 'visible' });Safe/Unsafe HTML 品牌类型
遵循 Lit 的转义语义,提供编译时安全:
import type { SafeHtml, UnsafeHtml } from '@lessjs/core';
type SafeHtml = string & { readonly __safeHtml: unique symbol }; // HTML 转义后的安全文本
type UnsafeHtml = string & { readonly __unsafeHtml: unique symbol }; // 原始/受信任的 HTMLescapeHtml()返回SafeHtml—— 安全用于文本内容UnsafeHtml跳过转义 —— 用于模板字面量和unsafeHTML()指令- 已转义的输入直接透传,不会二次转义
@lessjs/signal —— 基于 TC39 Signals 的响应式系统
新包。 749 行,除内联 TC39 signal-polyfill 外零依赖。
| API | 描述 |
|---|---|
signal(value) |
创建响应式状态 |
computed(fn) |
自动更新的派生值 |
effect(fn) |
依赖变化时执行的副作用 |
islandEffect(fn) |
绑定岛屿生命周期的自动清理 effect |
channel(name) |
命名信号,用于岛屿间通信 |
themeSignal |
内置主题状态信号 |
架构:引擎层(TC39 原语)→ 框架层(.value 语法、自动清理)→ 语法糖层(islandEffect、channel、themeSignal)。
当浏览器原生支持 Signal 时,polyfill 会被 tree-shaking 移除。
Form-Associated Custom Elements
less-button 和 less-input 现在可以参与原生 <form> 提交和验证:
<form onsubmit="console.log(new FormData(this))">
<less-input name="username" label="用户名" required></less-input>
<less-button type="submit">提交</less-button>
</form>static formAssociated = true—— 启用ElementInternals- CSS
:state()伪类用于验证状态 delegatesFocus用于键盘无障碍
Navigation API
新模块 @lessjs/core/navigation —— 现代 SPA 路由:
import { navigate, onNavigate, matchRoute } from '@lessjs/core/navigation';
navigate('/about'); // 编程式导航
onNavigate((url) => console.log(url)); // 导航监听
matchRoute('/posts/:id', '/posts/42'); // URLPattern 路由匹配- 优先使用原生 Navigation API(Chrome 102+),不支持时降级到 History API
URLPattern路由匹配(WHATWG §7.2)
Dialog 组件
less-dialog —— 原生 <dialog> + popover API:
<less-dialog>
<button slot="trigger">打开对话框</button>
<div>对话框内容</div>
</less-dialog>- 原生模态行为(焦点陷阱、ESC 关闭、
::backdrop) inert属性使背景内容不可交互,保障无障碍- CSS
:state()伪类用于开启/关闭状态
主题系统重构
| 变更 | 描述 |
|---|---|
| CSS Custom Properties | 主题颜色通过 CSS 变量注入到 :root —— 无需 DOM 遍历 |
_propagateTheme() 移除 |
不再需要递归 DOM 遍历设置 data-theme |
| O(1) 主题切换 | 性能不再受组件树深度影响 |
| Closed Shadow DOM 支持 | CSS 变量自动穿透 closed shadow root |
prefers-color-scheme 检测 |
尊重操作系统级偏好(CSS Media Queries Level 5 §4.2) |
color-scheme CSS 属性 |
告知浏览器使用原生明/暗色表单控件 |
颜色 Token 单一数据源
新的 @lessjs/ui/tokens/color-values —— 零依赖纯数据模块:
- 亮色和暗色主题各 18 个颜色 token
generateRootColorCSS()—— 为 SSG 注入生成:rootCSS- 消除了
ssg-postprocess.ts和tokens/colors.ts之间 4 个 token 的数据漂移
基础设施变更
Adapter 协议:消除 globalThis 污染
| 之前 (v0.5) | 之后 (v0.6) |
|---|---|
globalThis.__lessLitTemplateCheck |
registerAdapter({ isTemplate, ... }) |
globalThis.__lessLitSsrRenderer |
模块作用域 _globalAdapter |
globalThis.__lessLitStylesExtractor |
RenderAdapter 接口 |
globalThis.__lessLitAdapterInstalled |
server.ssrLoadModule() 自动安装 |
所有 globalThis hook 已消除。适配器通过 registerAdapter() 显式注册,与 renderDSD() 共享同一模块作用域。
npm 发布移除
- 所有
_build_npm.ts、tsconfig.build.json、vite.config.build.ts、package.json文件从每个包中删除 - CI 工作流现在仅发布到 JSR —— 不再使用 dnt/npm 转换
- 发布顺序:
rpc → ui → adapter-lit → signal → core → create(依赖顺序) - CI 在
packages/*/src/**变更时也会触发(不仅仅是deno.json)
跨包 JSR 导入
Deno workspace 自动解析 @lessjs/* 导入,但 JSR 发布要求显式 jsr: 标识符和版本约束。每个跨包导入现在都有显式映射:
// packages/core/deno.json
"imports": {
"@lessjs/ui/tokens/color-values": "jsr:@lessjs/ui@^0.6.1/tokens/color-values"
}格式:jsr:@scope/package@^version/subpath —— 版本约束在子路径之前。
Bug 修复
| Bug | 根因 | 修复 |
|---|---|---|
| 站点颜色全灭 | :host { --less-bg-base: initial; } 将 CSS 自定义属性设为 guaranteed-invalid value,切断了 :root → Shadow DOM 的继承链 |
删除 :host 中所有 initial 声明 —— CSS Custom Properties 自然穿透 Shadow DOM |
| SSG inline CSS 死代码 | ssg-postprocess.ts 注入的 less-layout .app-layout{...} 选择器在 Shadow DOM 内永远匹配不到 |
移除所有 shadow-internal 选择器,仅保留 less-layout{display:block} + :root 颜色变量 |
| 首页高度不足 | docs-home 组件未撑满视口 |
添加 min-height: 100vh |
| SSG Lit 渲染失败 | installLitAdapter() 通过 Deno import() 注册到不同模块实例,与 Vite SSR 中的 renderDSD() 不共享 |
改用 server.ssrLoadModule() —— 与 renderDSD 同一模块作用域 |
| CSS token 数据漂移 | ssg-postprocess.ts 硬编码 14 个颜色 token,tokens/colors.ts 有 18 个 —— 缺少 4 个 |
新建 color-values.ts 作为单一数据源,ssg-postprocess.ts 动态导入 |
| 嵌套 CE 渲染为文本 | <counter-island> 的 DSD 作为文本内容出现在 <less-layout> 的 shadow DOM 内,而非真实 DOM 元素 |
adapter-lit 中 unwrapDsdForNestedCe() —— 从 DSD 包装中提取嵌套 CE,使其成为真实 DOM 节点 |
| DSD 重复渲染 | Lit 在 DSD 已填充的 shadow root 中再次渲染 → 内容重复 | _dsdHydrated 标志 —— createRenderRoot() 检测已有 shadow 内容,跳过 Lit 渲染 |
| DSD 正则遗漏属性 | shadowrootdelegatesfocus 等 <template> 属性未被匹配 |
修正正则以匹配所有 shadowroot* 属性 |
| Footer CSS 选择器不匹配 | .app-footer footer 与实际 DOM 不匹配 |
改为 .app-footer |
| 移动端侧栏打不开 | display:none 导致 transform 无法工作 |
改用 width:0 + overflow:hidden —— 保持盒模型活跃以支持过渡动画 |
| 首页汉堡菜单幽灵 | 首页没有侧栏但汉堡按钮仍显示 + 背景遮罩出现 | :host([home]) .mobile-menu { display:none } + 统一单一侧栏方案 |
核心教训
CSS Custom Property 的
initial是陷阱。 与普通 CSS 属性中initial表示"继承上级"不同,自定义属性的initial是 guaranteed-invalid value —— 它会主动切断继承链。在 Shadow DOM 的:host中用initial重新声明自定义属性会静默地破坏整个级联。
Vite SSR 有独立的模块图。
import()和server.ssrLoadModule()不共享模块实例。需要同一模块作用域的代码(适配器注册 + renderDSD)必须都使用server.ssrLoadModule()。不要用globalThis做桥接 —— 那只是掩盖问题的创可贴。
代码质量
| 改进 | 描述 |
|---|---|
globalThis 去污染 |
__lessRenderAdapter、__lessLitAdapterInstalled、__lessLit* 全局变量全部移除 |
| Adapter 模块作用域 | installLitAdapter() 通过 server.ssrLoadModule() 注册 |
island.ts Mixin |
替代猴子补丁模式;__lessIslandWrapped 防止双重包装 |
| TypeScript 类型 | 修复 6 个类型断言问题 |
| JS 现代化 | var → const/let,移除禁用注释 |
| 废弃代码删除 | html-template.ts、less-bind.ts、vite-ext.d.ts 已删除 |
| 类型合并 | PackageIslandMeta 统一从 @lessjs/core 导入 |
| CSS 别名清理 | 移除 lessScaffoldColorCSS 废弃别名 |
customElements.define 守卫 |
8 个 UI 组件:try/catch → customElements.get() 幂等守卫 |
escapeHtml 统一 |
adapter-lit/ssr.ts 从 @lessjs/core/render-dsd 导入,消除 3 处重复 |
| 废弃类型移除 | PlannedIslandStrategy、RouteMeta、FrameworkPlugin 已删除 |
新增文件
| 文件 | 描述 |
|---|---|
packages/core/src/island.ts |
岛屿包装器 —— island()、lessBind()、getSSRProps() |
packages/core/src/navigation.ts |
Navigation API —— navigate()、onNavigate()、matchRoute() |
packages/signals/src/index.ts |
TC39 Signals 响应式系统 —— 749 行 |
packages/ui/src/less-dialog.ts |
原生 <dialog> + popover 组件 |
packages/ui/src/tokens/color-values.ts |
颜色 token 单一数据源 |
删除文件
| 文件 | 原因 |
|---|---|
packages/*/package.json |
npm 发布移除 —— 仅 JSR |
packages/*/_build_npm.ts |
dnt 构建脚本移除 |
packages/*/tsconfig.build.json |
npm 构建配置移除 |
packages/*/vite.config.build.ts |
npm 构建配置移除 |
packages/core/src/html-template.ts |
被 DSD 渲染替代 |
packages/core/src/vite-ext.d.ts |
不再需要 |
版本号
| 包 | v0.5.5 | v0.6.1 | 说明 |
|---|---|---|---|
@lessjs/core |
0.5.5 | 0.6.1 | DSD、岛屿、Navigation、Safe/Unsafe HTML |
@lessjs/ui |
0.5.5 | 0.6.1 | DSD 水合、dialog、Form-Associated CE、color-values |
@lessjs/rpc |
0.3.1 | 0.6.1 | 版本同步 |
@lessjs/adapter-lit |
0.2.1 | 0.6.1 | Safe/Unsafe HTML、嵌套 CE 解包、unsafeHTML 指令 |
@lessjs/create |
0.4.6 | 0.6.1 | 模板更新适配 v0.6 |
@lessjs/signal |
— | 0.6.1 | 新增 —— TC39 Signals 响应式系统 |
破坏性变更
| 变更 | 迁移方式 |
|---|---|
_propagateTheme() 移除 |
使用 CSS 变量(var(--less-*))—— 主题切换自动生效 |
mobile-sidebar-overlay 移除 |
统一使用 .docs-sidebar 适配桌面和移动端 |
registerAdapter() 协议替代 globalThis hook |
所有 __lessLit* 全局变量已移除 —— 显式调用 registerAdapter() |
installLitAdapter() 通过 Vite SSR 自动安装 |
用户代码不再需要手动调用 |
signals 中 derived() + fire-once effect() 移除 |
使用 TC39 标准的 computed() / effect() |
island() Mixin 替代猴子补丁 |
__lessIslandWrapped 防止双重包装;旧补丁模式不再生效 |
html-template.ts 删除 |
使用 @lessjs/core 的 renderDSD() |
| npm 发布移除 | 仅从 JSR 安装:deno add jsr:@lessjs/core |
RouteMeta / FrameworkPlugin 类型移除 |
直接使用 RouteEntry 和 Plugin[] |
0.6-alpha1
v0.6.0-alpha.1 — Declarative Shadow DOM + Islands Architecture + Web Standards
概述
v0.6.0-alpha.1 是 LessJS 的重大架构升级版本,引入了 Declarative Shadow DOM (DSD)、岛屿懒加载架构、Safe/Unsafe HTML 品牌类型、TC39 Signals 二开、Form-Associated Custom Elements、Navigation API,以及全面采用 Web Standards 规范。
基于 WHATWG HTML Living Standard 全面架构审查(ARCHITECTURE-REVIEW.md),完成 8 个 Phase 共 38 项任务。
主要变更
🚀 核心架构升级
| 功能 | 描述 | 规范参考 |
|---|---|---|
| Declarative Shadow DOM (DSD) | 服务端直接输出 Shadow DOM 结构 | WHATWG HTML Standard |
| L2 Nested DSD | 支持嵌套 Custom Elements 递归渲染 | DOM Standard |
| DSD 规范对齐 | shadowrootdelegatesfocus/shadowrootserializable/shadowrootslotassignment/shadowrootcustomelementregistry |
WHATWG HTML Standard |
| 岛屿懒加载 | visible/idle 策略的 client islands | Custom Elements |
| Safe/Unsafe HTML | 品牌类型区分安全/非安全 HTML | 遵循 Lit 语义 |
| CSS 变量主题 | 替代 _propagateTheme() DOM 遍历 |
CSS Custom Properties |
| TC39 Signals 二开 | 基于 signal-polyfill 的 @lessjs/signals,支持 signal()/computed()/effect()/islandEffect() |
TC39 Signals Proposal |
| Form-Associated CE | less-button/less-input 使用 ElementInternals 参与表单提交/验证,支持 :state() 伪类 |
HTML Living Standard |
| Navigation API | navigate()/onNavigate()/matchRoute(),URLPattern 路由匹配 |
WHATWG Navigation API |
| Speculative Loading | <link rel="modulepreload"> for eager islands,<link rel="prefetch"> for lazy islands |
Resource Hints |
| dialog/popover + inert | 原生 <dialog> + ::backdrop + popover + inert 无障碍 |
HTML Living Standard |
🎨 主题系统重构
| 变更 | 描述 |
|---|---|
| CSS Custom Properties | 主题颜色通过 CSS 变量注入到 :root |
_propagateTheme() 移除 |
不再需要递归 DOM 遍历设置 data-theme |
| 主题切换性能 | O(1) 复杂度,不受组件树深度影响 |
| Closed Shadow DOM 支持 | CSS 变量自动穿透 |
📦 新增包
| 包 | 描述 |
|---|---|
@lessjs/signals |
响应式信号系统(TC39 Signals 二开) |
navigation.ts |
Navigation API + URLPattern 路由 |
less-dialog.ts |
原生 dialog/popover 组件 |
🔧 改进
| 范围 | 变更 |
|---|---|
render-dsd.ts |
data-ssr-props 属性保留 SSR 属性 |
ssg-postprocess.ts |
CSS 变量注入到 :root,parse5 re-export |
less-layout.ts |
统一 sidebar(移除 mobile-sidebar-overlay),语义化 HTML(div→nav/footer),inert 无障碍 |
adapter-lit/ssr.ts |
unsafeHTML directive 支持,unwrap DSD for nested CE |
entry-generators.ts |
visible/idle 懒加载策略,speculative links |
adapter protocol |
消除所有 globalThis 污染,改用同模块作用域注册 |
runtime-shim.ts |
DsdOptions 对象模式(delegatesFocus/serializable/slotAssignment/customElementRegistry) |
navigation.ts |
Navigation API 集成,URLPattern 路由匹配,navigationType 修复 |
island.ts |
Mixin 替代猴子补丁,__lessIslandWrapped 防重入 |
design-tokens.ts |
prefers-color-scheme 检测 + color-scheme 属性设置 |
tokens/colors.ts |
color-values.ts 零依赖单一数据源,消除 token 漂移 |
🧹 代码质量
| 改进 | 描述 |
|---|---|
| globalThis 去污染 | __lessRenderAdapter / __lessLitAdapterInstalled 全移除 |
| adapter 模块作用域 | installLitAdapter() 改走 server.ssrLoadModule() 注册 |
| island.ts 语法修复 | 修复缺少右括号 |
| TypeScript 类型 | 修复 6 个类型断言问题 |
| JS 现代化 | var → const/let,移除禁用注释 |
| 废弃代码删除 | html-template.ts / less-bind.ts / vite-ext.d.ts |
| 重复类型合并 | PackageIslandMeta 统一从 @lessjs/core 导入 |
| CSS 别名清理 | 移除 lessScaffoldColorCSS 废弃别名 |
| customElements.define 守卫 | 8 个 UI 组件 try/catch → customElements.get() 幂等守卫 |
| escapeHtml 统一 | adapter-lit/ssr.ts 从 @lessjs/core/render-dsd 导入,消除 3 处重复 |
Web Standards 采用
v0.6 全面采用以下 Web 规范:
| 规范 | 应用 |
|---|---|
| HTML Living Standard | DSD 模板、<template>, <slot>, <dialog>, inert, popover |
| DOM Standard | Shadow DOM、Custom Elements |
| Custom Elements | customElements.define() 统一注册,Form-Associated CE,ElementInternals,:state() 伪类 |
| CSS Custom Properties | 主题系统、组件样式变量化 |
| CSS Shadow Parts | 预备 ::part() 样式穿透 |
| Fetch Standard | 预备标准 Fetch API |
| Navigation API | SPA 导航,URLPattern 路由匹配 |
| TC39 Signals | signal-polyfill 二开,响应式状态管理 |
浏览器兼容性
| 特性 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| Custom Elements v1 | ✅ | ✅ | ✅ | ✅ |
| Shadow DOM v1 | ✅ | ✅ | ✅ | ✅ |
| HTML Templates | ✅ | ✅ | ✅ | ✅ |
| CSS Shadow Parts | ✅ | ✅ | ✅ | ✅ |
| Declarative Shadow DOM | ✅ | ✅ | ✅ | ✅ |
测试
- 322 个测试全部通过(含 95 steps)
deno lint137 files 无警告deno task test完整通过- create-less 集成测试全绿(SSG 一键构建管道验证)
版本号
| 包 | 旧版本 | 新版本 |
|---|---|---|
@lessjs/core |
0.5.5 | 0.6.0-alpha.1 |
@lessjs/ui |
0.5.5 | 0.6.0 |
@lessjs/rpc |
0.3.1 | 0.3.1 (无变更) |
@lessjs/adapter-lit |
0.2.1 | 0.3.0 |
@lessjs/create |
0.4.6 | 0.4.7 |
@lessjs/signals |
- | 0.6.0-alpha.1 (NEW) |
破坏性变更
| 变更 | 描述 |
|---|---|
_propagateTheme() 移除 |
主题系统改用 CSS 变量,不再需要此方法 |
移除 mobile-sidebar-overlay |
统一使用 .docs-sidebar |
| CSS 变量命名 | --less-* 继续使用,无变更 |
registerAdapter() 协议 |
全局 __lessLitSsrRenderer 等 globalThis hook 移除,适配器必须通过 registerAdapter() 显式注册 |
installLitAdapter() 调用方式 |
SSG 构建通过 Vite SSR 模块加载自动安装,用户侧无需手动调用 |
derived() + fire-once effect() 移除 |
@lessjs/signals 旧 API 完全删除,替换为 TC39 标准的 computed()/effect() |
island() Mixin 替代猴子补丁 |
__lessIslandWrapped 防重入,旧猴子补丁模式不再生效 |
迁移指南
从 v0.5.x 迁移
- 主题切换:无需改动,
less-theme-toggle自动适配 - 自定义样式:使用
var(--less-*)变量替代硬编码颜色 - 岛屿组件:无需改动,
island.ts向后兼容
新增用法
// 响应式信号
import { signal, computed, effect, islandEffect } from '@lessjs/signals';
// SSR Props 绑定
import { less } from '@lessjs/core';
// 岛屿懒加载
import { Island } from '@lessjs/core/island';
// Navigation API
import { navigate, onNavigate, matchRoute } from '@lessjs/core/navigation';🐛 Bug 修复
| Bug | 根因 | 修复 |
|---|---|---|
| 站点颜色全灭 | :host { --less-bg-base: initial; } 把 CSS 自定义属性设为 guaranteed-invalid value,切断了 :root → Shadow DOM 的继承链 |
删除 :host 中所有 initial 声明,让 CSS Custom Properties 自然穿透 Shadow DOM |
| SSG inline CSS 死代码 | ssg-postprocess.ts 注入 less-layout .app-layout{...} 等 document-scope 选择器,Shadow DOM 封装导致永远匹配不到 |
移除所有 shadow-internal 选择器,仅保留 less-layout{display:block} + :root 颜色变量 |
| 首页高度不足 | docs-home 组件未撑满视口 |
添加 min-height: 100vh |
| SSG Lit 渲染失败 | installLitAdapter() 通过 Deno import() 注册到 Deno 模块实例,但 renderDSD() 运行在 Vite SSR 的另一份模块实例,_globalAdapter 不共享 |
改用 server.ssrLoadModule() 安装 adapter,与 renderDSD 同一模块作用域 |
| CSS token 数据漂移 | ssg-postprocess.ts 硬编码 14 个颜色 token,与 tokens/colors.ts 的 18 个 token 重复,缺少 4 个 token |
新建 color-values.ts 作为单一数据源,ssg-postprocess.ts 从中动态生成 |
核心教训 1:CSS 自定义属性的
initial≠ "恢复默认/继承上级",而是 "guaranteed-invalid value"。
这与普通 CSS 属性(如color: initial→ 继承)的行为完全相反。
在 Shadow DOM 组件的:host中重声明自定义属性会主动切断继承链。核心教训 2:Vite SSR 有独立模块图,
import()和server.ssrLoadModule()不共享模块实例。
需要同一模块作用域的代码(如 adapter 注册 + r...
v0.5.5
概述
v0.5.5 完成了品牌从 KISS 到 LessJS 的全面更名,覆盖 105 个文件。修复了移动端 sidebar 在所有页面(包括首页)的打开问题,修复了 PWA manifest favicon 404 和 dnt 构建错误。所有 package 版本已更新。
主要变更
🏷️ 品牌更名完成(KISS → LessJS)
| 范围 | 变更内容 |
|---|---|
| 包名 | @kissjs/* → @lessjs/* |
| 主函数 | kiss() → less() |
| 类名 | KissButton → LessButton, KissLayout → LessLayout 等 |
| 临时目录 | .kiss/ → .less/ |
| 域名 | kiss.js.org → lessjs.com |
| 文档站 | README.en.md 全文重写,CSS 变量 --kiss-* → --less-*(69 处),路由 /kiss-compiler → /less-compiler |
| 全局变量 | __kissLit* → __lessLit*, __kissSsrDefinePatched → __lessSsrDefinePatched |
| CSS 类 | .kiss-row → .less-row(示例页面 K.I.S.S. 首字母缩写) |
🐛 Bug 修复
| # | 问题 | 修复 |
|---|---|---|
| A6 | 移动端 sidebar 打不开(首页 + 其他页面) | CSS display:none 阻止 transform 生效;改用 width:0 + overflow:hidden 保留盒模型 |
| 首页 hamburger 只显示遮罩无 sidebar | 始终渲染 sidebar DOM,桌面端折叠,移动端通过 :host([menu-open]) 控制显隐 |
|
| PWA manifest.json favicon 404 | src: /favicon.svg → /assets/less-logo.svg;添加 docs/public/favicon.svg |
|
| dnt npm 构建失败 | packages/rpc/_build_npm.ts LICENSE 路径 ../LICENSE → ../../LICENSE |
|
| CI 格式/lint 失败 | deno fmt 格式化 5 文件,lint 清理未使用 imports,publish exclusion 添加 !dist 取消 gitignore 排除 |
✅ 测试
- 325 个测试全部通过
- adapter-lit 新增 escape 一致性测试
deno publish --dry-run全部 5 个包干净通过
版本号
| 包 | 旧版本 | 新版本 |
|---|---|---|
@lessjs/core |
0.5.4 | 0.5.5 |
@lessjs/ui |
0.5.4 | 0.5.5 |
@lessjs/rpc |
0.3.0 | 0.3.1 |
@lessjs/adapter-lit |
0.2.0 | 0.2.1 |
@lessjs/create |
0.4.5 | 0.4.6 |
文件变更统计
- ~110 个文件变更(累计 Pre-0.6 全部任务)
- ~1400 行新增,~1000 行删除
- 4 个文件重命名,2 个新文件
向前兼容
@kissjs/*可通过 npm 重定向继续使用kiss()作为less()别名保留(标记为废弃).kiss/临时目录仍在 .gitignore 中保留
后续计划
- v0.6:DSD + Island Communication(L2 Nested DSD、safe/unsafe HTML、slot/projection、Error visibility)
- v0.7:Serverless Fullstack
Overview
v0.5.5 completes the full brand rename from KISS to LessJS across 105 files. Fixes the mobile sidebar opening issue on all pages (including homepage), the PWA manifest favicon 404, and the dnt build error. All package versions updated.
Key Changes
🏷️ Brand Rename Completed (KISS → LessJS)
| Scope | Change |
|---|---|
| Package names | @kissjs/* → @lessjs/* |
| Main function | kiss() → less() |
| Class names | KissButton → LessButton, KissLayout → LessLayout, etc. |
| Temp directory | .kiss/ → .less/ |
| Domain | kiss.js.org → lessjs.com |
| Documentation | Full README.en.md rewrite, CSS vars --kiss-* → --less-* (69), routes /kiss-compiler → /less-compiler |
| Global vars | __kissLit* → __lessLit*, __kissSsrDefinePatched → __lessSsrDefinePatched |
| CSS classes | .kiss-row → .less-row (K.I.S.S. acronym in examples page) |
🐛 Bug Fixes
| # | Issue | Fix |
|---|---|---|
| A6 | Mobile sidebar won't open (homepage + other pages) | display:none prevents transform from working; replaced with width:0 + overflow:hidden to keep box model alive |
| Homepage hamburger shows backdrop only, no sidebar | Always render sidebar DOM, collapse on desktop, restore on mobile via :host([menu-open]) CSS |
|
| PWA manifest favicon 404 | src: /favicon.svg → /assets/less-logo.svg; added docs/public/favicon.svg |
|
| dnt npm build failure | packages/rpc/_build_npm.ts LICENSE path ../LICENSE → ../../LICENSE |
|
| CI format/lint failures | deno fmt fixed 5 files, lint cleaned unused imports, publish exclusion added !dist to un-exclude from gitignore |
✅ Tests
- 325 tests — all passing
- adapter-lit added escape consistency tests
deno publish --dry-runclean for all 5 packages
Version Bumps
| Package | Old | New |
|---|---|---|
@lessjs/core |
0.5.4 | 0.5.5 |
@lessjs/ui |
0.5.4 | 0.5.5 |
@lessjs/rpc |
0.3.0 | 0.3.1 |
@lessjs/adapter-lit |
0.2.0 | 0.2.1 |
@lessjs/create |
0.4.5 | 0.4.6 |
File Change Stats
- ~110 files changed (cumulative for all Pre-0.6 tasks)
- ~1,400 insertions, ~1,000 deletions
- 4 file renames, 2 new files
Backward Compatibility
@kissjs/*continues to work via npm redirectskiss()preserved as alias forless()(deprecated).kiss/temp directory remains in .gitignore alongside.less/
Upcoming
- v0.6: DSD + Island Communication (L2 Nested DSD, safe/unsafe HTML, slot/projection, Error visibility)
- v0.7: Serverless Fullstack
Full Changelog: v0.5.4...v0.5.5