Skip to content

Releases: lessjs-run/lessjs

v0.9.2

09 May 17:20

Choose a tag to compare

  • 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

09 May 11:18

Choose a tag to compare

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 版本允许破坏性变更。以下是迁移注意点:

  1. @lessjs/blog@lessjs/content: 插件入口从 lessBlog()lessContent({ blog: {...}, nav: {...}, sitemap: {...} }),子模块按需启用
  2. i18n 从 content 拆出: 原先 lessContent({ i18n: {...} }) → 独立的 lessI18n({ locales, defaultLocale }) 插件
  3. 删除的路由: /demo/examples/guide/api-design/guide/api-routes/guide/blog-system/guide/design-philosophy/guide/less-compiler/styling/less-ui
  4. 删除的包: @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/ and dist/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() and switchLocale() 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.xml with 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.js shadow 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...
Read more

v0.9

09 May 01:01

Choose a tag to compare

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

08 May 19:33

Choose a tag to compare

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.xmlrobots.txt。支持排除路径、自定义 changefreq/priority。

SSR 属性绑定保留

Breaking Change: @lessjs/adapter-litinterpolate() 函数现在将 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),将 navItemsnav-items 等属性名转换。

islandEffect() 泄漏修复

@lessjs/signalislandEffect() 修复了三个资源泄漏问题:

  • 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.jsondocs/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.tsawait import(pkg) 变量动态导入触发 JSR unanalyzable-dynamic-import 警告(不影响功能,仅影响发布提示)

升级指南

  1. 替换 @lessjs/blog → @lessjs/content

    - import { lessBlog } from '@lessjs/blog';
    + import { lessContent } from '@lessjs/content';
    - lessBlog({ contentDir: 'posts' })
    + lessContent({ blog: { contentDir: 'posts' } })
  2. 侧边栏导航迁移:删除手动维护的 nav-data.ts,在路由文件中添加 meta 导出,配置 lessContent({ nav: { routesDir: 'app/routes', headerNav: [...] } })

  3. Sitemap 启用:添加 lessContent({ sitemap: { hostname: 'https://yoursite.com' } })

  4. 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 navItemsnav-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-nav plugin, ensuring import '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/sitemap to generate sitemap.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 /ui page
  • @lessjs/blog@lessjs/content global 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.ts lacks camelToKebab conversion — currentPath is output as currentpath instead of current-path (#1)
  • await import(pkg) variable dynamic import in route-scanner.ts triggers JSR unanalyzable-dynamic-import warning (does not affect functionality, publish advisory only)

Upgrade Guide

  1. Replace @lessjs/blog → @lessjs/content:

    - import { lessBlog } from '@lessjs/blog';
    + import { lessContent } from '@lessjs/content';
    - lessBlog({ contentDir: 'posts' })
    + lessContent({ blog: { contentDir: 'posts' } })
  2. Sidebar navigation migration: Delete manually maintained nav-data.ts, add meta exports to route files, configure lessContent({ nav: { routesDir: 'app/routes', headerNav: [...] } })

  3. Enable sitemap: Add lessContent({ sitemap: { hostname: 'https://yoursite.com' } })

  4. 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

08 May 15:18

Choose a tag to compare

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 discovery
  • e2e/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 log undefined: Generated shim code referenced log but had no definition. Added var 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/debug calls replaced with createLogger() 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 log undefined: 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

07 May 14:55

Choose a tag to compare

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.tsserializeAttributes() 改用 escapeAttrValue(),与 render-dsd.ts 保持一致。此前 null/undefined 值处理不一致。
  • headExtras/headFragments XSS 警告:添加 @security/@dangerous JSDoc 标注。当注入内容包含 <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-lit job。
  • 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.warnconsole.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:install

LessJS 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() in runtime-shim.ts now uses escapeAttrValue() instead of escapeAttr, matching render-dsd.ts. Previously, null/undefined values were not handled consistently.
  • headExtras/headFragments XSS warnings: Added @security/@dangerous JSDoc annotations. Runtime console.warn is 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-commit runs deno fmt --check + deno lint + deno check. Enable with deno task hooks:install.
  • CI adapter-lit tests: New test-adapter-lit job 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 via escapeAttrValue instead of passing through escapeAttr. 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.warn or console.debug with [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:install

Full Changelog: v0.6.1...0.7.0

Full Changelog: v0.6.1...v0.7.0

v0.6.2

07 May 12:08

Choose a tag to compare

更新日志

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:新增 WithDsdHydration Mixin 和 DsdLitElement 预组合基类

    • 检测 DSD 预填充的 shadow root,自动跳过重复渲染
    • static hydrateEvents 声明式事件绑定
    • AbortController 自动清理
    • 新增 DsdHydrationDsdHydrationMixin 类型接口
  • @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-toggleless-code-blockless-layout → 迁移至 WithDsdHydration Mixin
  • less-dialog → 修复缺失的 DSD hydration
  • less-hero-ping → 标记为 Layer 3 pure-island

JSR 发布修复

问题 修复
跨包相对路径 JSR 无法解析 替换为 jsr:@lessjs/...
jsr: 缺少版本约束 添加 @^0.6.0 约束
jsr: 子路径格式错误 jsr:@lessjs/ui/tokens@verjsr:@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 WithDsdHydration Mixin and DsdLitElement pre-composed base class

    • Detects DSD-pre-populated shadow root, skips duplicate rendering
    • Declarative event binding via static hydrateEvents
    • Automatic cleanup via AbortController
    • Added DsdHydration and DsdHydrationMixin type interfaces
  • @lessjs/core: Added types and interfaces

    • ComponentLayer type (dsd-static / dsd-interactive / pure-island)
    • HydrateEventDescriptor interface (declarative event binding)
    • Added dsd option to island() (default true, false = pure-island)
    • Added __layer metadata marker on island classes

Removed

  • Hydratable interface and RenderAdapter.hydrate dead code
  • Lit-specific requestUpdate detection from lessBind()
  • _extractCeTag() dead function in @lessjs/adapter-lit
  • Moved injectLayoutStyles from @lessjs/core to @lessjs/ui/ssg-inject (eliminates core→ui circular dependency)

UI Component Migration

  • less-theme-toggle, less-code-block, less-layout → Migrated to WithDsdHydration Mixin
  • less-dialog → Fixed missing DSD hydration
  • less-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@verjsr:@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/signal to match JSR package name

v0.6.1

07 May 05:56

Choose a tag to compare

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 规范对齐 shadowrootdelegatesfocusshadowrootserializableshadowrootslotassignmentshadowrootcustomelementregistry —— 通过 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 }; // 原始/受信任的 HTML
  • escapeHtml() 返回 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 语法、自动清理)→ 语法糖层(islandEffectchannelthemeSignal)。

当浏览器原生支持 Signal 时,polyfill 会被 tree-shaking 移除。

Form-Associated Custom Elements

less-buttonless-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 注入生成 :root CSS
  • 消除了 ssg-postprocess.tstokens/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.tstsconfig.build.jsonvite.config.build.tspackage.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-litunwrapDsdForNestedCe() —— 从 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 现代化 varconst/let,移除禁用注释
废弃代码删除 html-template.tsless-bind.tsvite-ext.d.ts 已删除
类型合并 PackageIslandMeta 统一从 @lessjs/core 导入
CSS 别名清理 移除 lessScaffoldColorCSS 废弃别名
customElements.define 守卫 8 个 UI 组件:try/catchcustomElements.get() 幂等守卫
escapeHtml 统一 adapter-lit/ssr.ts@lessjs/core/render-dsd 导入,消除 3 处重复
废弃类型移除 PlannedIslandStrategyRouteMetaFrameworkPlugin 已删除

新增文件

文件 描述
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/corerenderDSD()
npm 发布移除 仅从 JSR 安装:deno add jsr:@lessjs/core
RouteMeta / FrameworkPlugin 类型移除 直接使用 RouteEntryPlugin[]
Read more

0.6-alpha1

06 May 16:03

Choose a tag to compare

0.6-alpha1 Pre-release
Pre-release

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 现代化 varconst/let,移除禁用注释
废弃代码删除 html-template.ts / less-bind.ts / vite-ext.d.ts
重复类型合并 PackageIslandMeta 统一从 @lessjs/core 导入
CSS 别名清理 移除 lessScaffoldColorCSS 废弃别名
customElements.define 守卫 8 个 UI 组件 try/catchcustomElements.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 lint 137 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 迁移

  1. 主题切换:无需改动,less-theme-toggle 自动适配
  2. 自定义样式:使用 var(--less-*) 变量替代硬编码颜色
  3. 岛屿组件:无需改动,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...

Read more

v0.5.5

05 May 18:28

Choose a tag to compare

概述

v0.5.5 完成了品牌从 KISS 到 LessJS 的全面更名,覆盖 105 个文件。修复了移动端 sidebar 在所有页面(包括首页)的打开问题,修复了 PWA manifest favicon 404 和 dnt 构建错误。所有 package 版本已更新。


主要变更

🏷️ 品牌更名完成(KISS → LessJS)

范围 变更内容
包名 @kissjs/*@lessjs/*
主函数 kiss()less()
类名 KissButtonLessButton, KissLayoutLessLayout
临时目录 .kiss/.less/
域名 kiss.js.orglessjs.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 KissButtonLessButton, KissLayoutLessLayout, etc.
Temp directory .kiss/.less/
Domain kiss.js.orglessjs.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-run clean 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 redirects
  • kiss() preserved as alias for less() (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