
引言
作为一名前端开发者,我一直有个未完成的计划,那就是独立构建一个符合个人技术理念的 JavaScript 框架。最初的想法是围绕虚拟 DOM 进行设计,但进入2025年,前端技术日新月异,各类框架层出不穷且竞争激烈,虚拟 DOM 已不再是当下的主流方案。因此我调整了计划,并决定立即开启项目。
2025年上半年,这个项目进入实际开发阶段,并于今年10月在 npm 上发布。
本文不会深入技术细节,而是侧重于经验分享,以易于理解的方式介绍本框架的优势特点与技术选型考虑。希望这些内容能为同样有兴趣探索框架开发的同行,提供一些可借鉴的思路。
背景
自从 React
凭借虚拟 DOM 与函数式组件获得广泛采纳之后,前端开发便进入了以虚拟 DOM 为主导的时期,各类基于相似理念的框架不断涌现,连 Vue
等主流框架也全面接纳了该技术。业内长期存在一个共识:DOM 操作成本高昂,需要谨慎对待。由于单个 DOM 节点包含数百个属性,虚拟 DOM 通过将其抽象为轻量级 JavaScript 对象,仅保留节点类型、属性和子节点等关键信息,从而有效规避直接操作 DOM 的负担。再结合高效的 diff 算法,比较新旧虚拟 DOM 的差异,实现最小化 DOM 更新,最终达到优异的运行时性能。
然而到了2019年,情况开始发生变化。新兴框架 Svelte
另辟蹊径,通过在编译阶段将组件模板转换为简练的 JavaScript 代码,大幅减少运行时开销。在 js-framework-benchmark 等框架性能测试中,Svelte
表现突出,甚至超越了多数主流框架。它揭示了一个新思路:将复杂的优化工作提前到编译时处理,而非留到运行时解决。这证明了即使不依赖虚拟 DOM,通过编译时优化同样能实现卓越性能。
顺便一提,
Svelte
的作者是知名前端开发者 Rich Harris,其代表作还包括Reactive
、Rollup
和Degit
等受欢迎的工具库。
如果说 Svelte
是编译型 JavaScript 框架的开创者,那么随后出现的 Solid
,则在编译器设计与响应式机制上实现了更深层次的结合。它不仅继承了 Svelte
的编译时优化理念,更进一步引入了基于 Signal
的响应式系统。与 Svelte
所使用的脏检查机制相比,Signal
能够在数据变化时实现更精准高效的追踪更新。
在这两种技术的结合下,Solid
将 DOM 更新的粒度优化到了接近"原子级"的水平,仅更新真正发生变化的部分。凭借这种极致的优化,Solid
在 js-framework-benchmark 性能测试中取得了1.11分的惊人成绩,无限接近原生 JavaScript 实现的1.00分,至今仍在该榜单中保持领先地位。

当然,Solid
的优势不仅在于其出色的性能,还在于它保留了与 React
相似的 JSX 语法和 API 设计。相比基于模板的 Svelte
,这种高度相似的语法与显著降低的上手难度,使其能够更快被社区接受。
正是这些特点,使 Solid
在发布后便迅速吸引我的关注。作为一名曾经对虚拟 DOM 深信不疑的开发者,我逐渐意识到,编译时优化才是实现高性能前端框架的根本出路。而我的框架------Zess,也正是在此背景下诞生的。
介绍

Zess(发音为 /zɛs/)是一个基于编译器的 JavaScript 框架,专注于构建基于标准 HTML、CSS 和 JavaScript 之上的用户界面。与传统的专注于运行时的框架不同,Zess
将其主要工作转移到编译阶段。通过静态分析和编译时优化,它将声明式组件转换为精简高效的命令式代码。这带来了更少的运行时开销、更快的首屏加载速度,以及接近原生水平的用户体验性能。
作为一个编译器驱动的 JavaScript 框架,Zess
在设计之初就以 Solid
为参考对象,目标是在保持与其接近的性能水平、基本相同的 JSX 语法与 API 设计的同时,实现更小的体积和更低的开发门槛,类似于 Preact
与 React
之间的定位关系。从架构上看,这类编译型框架主要由编译器 与响应式系统两大核心构成,下文将分两个章节对它们进行具体介绍。
1. 编译器
构建 JSX 编译器的流程可分为三个核心阶段:解析 、转换 与代码生成。即先将 JSX 解析为 AST(抽象语法树),再按需修改 AST 节点,最终转换为相应的 JavaScript 代码。
解析与生成目前社区已有成熟方案,无需重复实现。常见解析器如 babel
、esprima
、espree
和 acorn
等均支持 JSX。受早期技术习惯影响,我在项目初期也选择了 babel
------它工具链完整,生态丰富,且被 Solid
等框架的 JSX 插件所采用。
但在实际开发中,babel
显得"过重",其 AST 与 ESTree 标准存在细微差异,所有操作都须依赖其特定 API,这在需要高度定制化的场景中限制了灵活性。编译型框架的编译器需应对复杂逻辑,远非简单表达式转换所能涵盖。
随着前端工具链的"锈化(Rustify)",像 swc
这样的高性能解析器逐渐兴起,官方称其性能远超 babel
。然而在我启动项目时,Rust 方案尚不成熟:swc
的 AST 不符合 ESTree 标准,集成成本较高;oxc-parser
(这是 Vite
的下一代构建工具 Rolldown
的底层依赖)仍处于早期阶段,尚未达到生产状态。
最终我选择了 meriyah
+ astring
这一组合。meriyah
是当前最快的 JavaScript 解析器,支持 JSX(TypeScript 可借助 Vite 的 esbuild 处理);astring
则是轻量且高效的代码生成器。根据 astring
官方的性能测试显示,meriyah
与 astring
组合在解析和生成代码的速度上远超 acorn
、sucrase
等竞争对手。更重要的是,两者均严格遵循 ESTree 标准,在兼容性、灵活性与性能之间取得了良好平衡,非常契合本项目的需求。

为落实上述设计目标,Zess
的 JSX 编译器(@zessjs/compiler)致力于实现与 Solid
一致的代码生成效果,目前已支持包括但不限于以下几种优化:
- 动态表达式追踪 :识别 JSX 中的动态表达式(如无参数调用的
Signal
函数),并将其转换为effect
副作用函数,实现响应式更新。 - 属性批量更新 :将同一个 DOM 节点的多个动态属性合并至单个
effect
函数中监听,减少副作用函数数量,提升更新效率。 - Fragment 表达式处理 :将 Fragment 节点作为数组处理,其中带有动态值的子元素会转换为
memo
计算值以进行响应式追踪,并对嵌套于 DOM 结构中的 Fragment 执行展开操作。 - JSX 特殊属性转换 :针对
ref
等特殊属性,根据其值的类型生成对应逻辑;对on*
类事件属性,则根据事件名称的大小写形式进行差异化编译处理。
碍于篇幅所限,上文仅介绍了部分核心优化。在整体设计思路上,Zess
的编译器与 Solid
高度一致,但在静态节点的编译优化上存在显著区别。请看以下示例:
jsx
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count => count + 1);
return (
<button type="button" onClick={increment}>
<p>{count()}</p>
</button>
);
}
render(() => <Counter />, document.getElementById("app"));
以上代码经过 Solid
编译后如下所示:
js
import { template as _$template } from "solid-js/web";
import { delegateEvents as _$delegateEvents } from "solid-js/web";
import { createComponent as _$createComponent } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
var _tmpl$ = /*#__PURE__*/_$template(`<button type=button><p>`);
import { render } from "solid-js/web";
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(1);
const increment = () => setCount(count => count + 1);
return (() => {
var _el$ = _tmpl$(),
_el$2 = _el$.firstChild;
_el$.$$click = increment;
_$insert(_el$2, count);
return _el$;
})();
}
render(() => _$createComponent(Counter, {}), document.getElementById("app"));
_$delegateEvents(["click"]);
而 Zess
的编译结果是这样的:
js
import {createComponent as _$createComponent, createElement as _$createElement, delegateEvents as _$delegateEvents, insert as _$insert, setAttribute as _$setAttribute} from "@zessjs/core";
import {render, useSignal} from "@zessjs/core";
function Counter() {
const [count, setCount] = useSignal(1);
const increment = () => setCount(count => count + 1);
return (() => {
const _el$ = _$createElement("button");
_el$.$$click = increment;
_$setAttribute(_el$, "type", "button");
const _el$2 = _$createElement("p");
_$insert(_el$2, count);
_el$.append(_el$2);
return _el$;
})();
}
render(() => _$createComponent(Counter, {}), document.getElementById("app"));
_$delegateEvents(["click"]);
通过对比 Solid
与 Zess
的编译产物可以看出,两者在静态内容处理上采取了不同的技术路线:
Solid
将 JSX 中的静态内容提取为模板工厂函数,在需要获取动态节点时克隆模板结果,并通过节点树关系定位动态内容。该方案也被最新的 Svelte 5
和 Vue Vapor Mode
所采纳。
而 Zess
则采用 createElement
函数逐个创建 DOM 结构,仅对动态节点添加监听,静态节点则直接批量插入父节点,这种思路与旧版 Svelte
类似。相较于模板方案,Zess
的优势在于:
- 所有节点变量可直接访问,无需通过节点树查找动态内容
- 省去模板克隆带来的性能开销
- 在多数场景下性能表现持平甚至更优,实现负担更轻
该方案的主要缺点是节点数量过多时可能生成较多重复代码。不过在实际开发中,多节点通常通过 <For>
组件或 map
函数批量生成,因此该影响较为有限。总体而言,这是一种值得采用的取舍策略。
2. 响应式系统
与编译器部分相比,响应式系统的实现原理并没有太多需要展开详述的内容。这并非意味着实现一个响应式系统很简单,而是因为 Signal
这一概念在当今前端领域已不新鲜。
早在至少十年前,开发者 Adam Haile 就创建了一个名为 S.js
的响应式库,其中已经运用了依赖图管理等算法来实现响应式机制,其 API 设计与今天多数基于 Signal
的框架非常接近,可谓极具前瞻性。
S.js
不仅 API 设计优秀,其最初目标就是作为操作 DOM 的响应式工具库,能够与各类框架无缝配合。基于这一理念,Adam 还开发了 Surplus
框架,它构建于 S.js
之上,其设计思路与今天的 Solid
等现代框架已十分接近,让人不得不钦佩作者的开创性思维。
尽管 S.js
本身并未广泛流行,但它对 Solid
框架的作者 Ryan Carniato 产生了深远影响。从 Solid
的早期代码中,可以清晰地看到对 S.js
的借鉴与致敬,最初版本甚至直接引入 S.js
作为响应式核心。随着 Solid
的逐渐成熟与社区影响力的扩大,经过 Ryan 与社区的持续迭代和优化,Solid
如今已发展出一套独具特色、自成体系的 Signal
响应式机制。Solid
的响应式系统独特之处主要在于以下两点:
- 推送与拉取混合的依赖响应模型(Push-pull hybrid) :不同于传统的以
Preact Signals
为代表的拉取为主的惰性求值模型以及以MobX
为代表的主动推送更新并即时计算值的方式,Solid
将上述两种模式相结合,当有信号被修改时,会向下游推送更新通知,标记可能需要更新的计算值为脏状态,而计算值被读取时会向上游拉取其依赖的信号是否发生变更等信息,检查是否需要重新计算。通过这种方式既实现了更精准的响应式更新,又避免了过度计算带来的性能浪费。 - 基于数组与索引的依赖管理系统 :不同于主流使用
Set
或双向链表等结构的依赖管理方案,Solid
采用了数组配合索引数组的联动机制。该方案通过维护双向索引映射关系,在依赖的添加与移除等操作上实现了与Set
相同的 O(1) 时间复杂度,同时凭借数组顺序存储的特性带来了更快的遍历速度,且避免了Set
的额外创建开销。这使得它在依赖数量较少的日常使用场景中,能够实现更出色的性能,让响应式更新的整体效率更高。

和 JavaScript 框架类似,Signal
响应式框架也有专门的性能基准测试 js-reactivity-benchmark。从官方测试结果来看,alien-signals
表现最为突出,其优异的性能已被 Vue
采纳成为新响应式系统的底层实现;而 S.js
尽管已停止维护多年,性能依然位居前列,可见其算法设计之精妙;Preact Signals
排名略高于 Solid
,部分得益于其惰性求值特性在测试中的优势,加之其实现较为简单,缺少 effect
嵌套等功能(由 uSignal
作者 Andrea Giammarchi 所指出),使其在测试中具有一定优势,因此不能将该榜单看作性能的唯一指标。Solid
虽不顶尖但也仍算优秀,更重要的是其响应式系统与框架设计理念高度契合,在功能与性能之间得到理想平衡。
Zess
作为对标 Solid
的轻量编译型框架,在响应式系统的实现上同样遵循了简洁高效的设计理念。在保留 Solid
核心机制与常用 API 的基础上,对原有实现进行大幅精简,移除了当前场景下非必要的功能,做到了真正的短小强悍。根据 Bundlephobia 的数据,其核心包 @zessjs/core 经 Gzip 压缩后仅 5.7kB,可以说是非常轻量了。

性能表现
经过编译器与响应式系统的深度优化,Zess
在最新的 js-framework-benchmark 性能测试中取得了令人瞩目的 1.20 分(分数越低性能越优),这一成绩不仅超越了 Vue
、React
、Angular
等主流框架,更是大幅领先于绝大多数虚拟 DOM 实现。所有测试项目均呈现绿色评级,证明其性能表现全面均衡无短板。
在 select row 和 swap rows 这两项关键测试中,Zess
的表现尤为亮眼。这两项测试表面上是简单的行操作,实则需要处理成千上万行数据的精准定位与交换,虚拟 DOM 的 diff 比较机制在此时便成为负担。而编译型框架能完美规避这种场景,展现出了压倒性的性能优势。

当然,Zess
的表现也并非完美无缺。从测试结果可以看出,其成绩仍略低于 Solid
和 Svelte
等以性能著称的框架,这说明在 DOM 操作处理和响应式系统算法方面仍有提升空间,后续将重点针对这些环节进行深度优化。
总结
Zess
是我完全参照现代前端框架标准开发的开源项目,采用 pnpm
+ monorepo
架构进行项目管理,通过 GitHub Actions 实现了从测试、发布到部署的全流程自动化。文档使用 Rspress
构建,并集成了 Algolia DocSearch 以提供全文搜索能力。
作为我的首个开源项目,Zess
将持续迭代完善,重点优化 Signal 算法、实现 SSR 渲染与水合等功能。同时我也计划推出更多有价值的开源工具。如果您对这个项目感兴趣,欢迎关注我的动态,也请为 Zess
点个 Star,您的支持将是我持续创作的最大动力!🙏
Github :Zess ⚡ The compiler-driven JavaScript framework for building user interfaces.