用 MoonBit 打造的 Luna UI:日本开发者 mizchi 的 Web Components 实践

日本拥有50K+粉丝开发者_ mizchi 长期活跃于前端与工具链领域,因对 TypeScript、WebAssembly 以及现代 Web 工程实践的深入研究而受到关注,同时也是 MoonBit 日本社区中较为活跃的开发者之一。

在本文中,作者结合自身在_ React、Preact、Qwik、Svelte 等框架中的实践经验,介绍了他为什么选择从零实现一个 UI库 ------Luna UI,并重点讨论了轻量运行时、Signal 响应式系统,以及以 Web Components、SSR 与 Hydration 为前提的整体设计思路。

以下为原文的中文翻译。

作为一名 UI 库爱好者,我从 React 开始尝试过各种 UI 库,但最终决定自己写一个。

多年过去,我对现有库的不满依然无法解决或难以解决,所以现在我要做我真正想要的东西。

  • 轻量级运行时带来的便携性

  • 基于 Signal 的细粒度响应式

  • 足够轻量,无需编译时优化

  • 支持 WebComponents SSR + Hydration (大概是世界首创)

这就是我做的 Luna。我还制作了文档网站。

它是用 Moonbit+Luna 本身编写的,甚至 SSG 也是自制的。

https://luna.mizchi.workers.dev/

对现有 UI 库的不满

  • React: 体积大。由于为了兼容现有资产,运行缓慢。不喜欢 RSC 实现的改进方向。

  • Qwik/Solid: 编译时展开很碍事。

  • svelte/vue: SFC 难以集成到生态系统中。

  • preact: 我认为它是思路最正确的,但 signal 是后来补上的。生态系统一般。

  • 共同问题: 互操作性低。除 Qwik 外,SSR 速度令人不满。没有以 WebComponents 为核心的项目。

基于这些考虑,我参考了 preact/signal,并以 WebComponents SSR + Hydration 为前提进行了设计。

根据我使用 Qwik + preact + svelte 的经验,Qwik 的优化过于复杂。如果核心足够轻量,preact 风格的轻量核心就足够了。

我也考虑过扩展 preact,但考虑到下文提到的 SSR Hydration 集成以及 Native SSR 的前景,SSR 优化需要垂直整合,因此我决定自研。

产出

🌕 Luna UI - 一个用 MoonBit 编写的基于 Signal 的声明式 UI 库。可用于 Moonbit/JS。

在下文提到的示例代码中,我对比了 luna 和 preact 的相同实现,preact 为 20kb,而 luna 仅为 6.7kb。实际上这取决于 treeshake 使用了哪些功能,但就结果而言可以说是大获成功。

示例代码: tsx

由于我用 jsx-runtime 包装了 Moonbit JS 后端的产物,因此在 js 构建中可以直接使用 JSX。

Bash 复制代码
$ npm add @luna_ui/luna

安装后即可使用:

javascript 复制代码
import { createSignal, createMemo, render, For, Show } from '@luna_ui/luna';

function Counter() {
  const [count, setCount] = createSignal(0);
  const doubled = createMemo(() => count() * 2);
  const isEven = createMemo(() => count() % 2 === 0);

  return (
    <div>
      <h1>Luna Counter Example</h1>
      <p>Count: {count}</p>
      <p>Doubled: {doubled}</p>
      <p>{() => isEven() ? 'Even' : 'Odd'}</p>
      <div class="buttons">
        <button onClick={() => setCount(c => c - 1)}>-</button>
        <button onClick={() => setCount(c => c + 1)}>+</button>
        <button onClick={() => setCount(0)}>Reset</button>
      </div>
    </div>
  );
}
//...
const app = document.getElementById('app');
if (app) {
  render(app, <App />);
}

以 tsx 为前提的示例项目

https://github.com/mizchi/luna-tsx-example

示例代码: Moonbit

这是 Moonbit 版本。

rust 复制代码
fn main {
  let doc = @js_dom.document()
  guard doc.getElementById("app") is Some(el)

  let count = @signal.signal(0)
  let app = @dom.div([
    p([@dom.text_dyn(fn() { "Count: " + count.get().to_string() })]),
    button(
      on=@events().click(fn(_) { count.update(fn(n) { n + 1 }) }),
      [text("Click me")],
    ),
  ])
  @dom.render(el |> @dom.DomElement::from_jsdom, app)
}

我感觉连接 vite 和 moonbit 这两个构建过程很复杂,所以自制了 vite-plugin-moonbit 来进行整合。

https://github.com/mizchi/vite-plugin-moonbit

引入后可以实现这样的效果:

JavaScript 复制代码
import { greet } from 'mbt:username/app';

moon build --target js 会生成 .d.ts 文件。在 vite 端,可以从 Moonbit 的命名空间解析符号,并带有完整的类型支持。很便利吧?

我还集成了错误报告器,将 moon build --watch 的结果作为 vite 错误显示。如果没有这个,类型错误导致不输出时会很麻烦。(一共有选项可以手动设置)

Moonbit 目前还没有 JSX,所以采用函数式 DSL 风格编写。

之所以说"目前还没有",是因为相关的提案正在实现中:

https://github.com/moonbitlang/moonbit-evolution/issues/19

有了这个后会变得非常自然。虽然目前是基于函数的 DSL,但我认为写起来感觉也不错。

实际运行演示

我想通过实际运行的 Demo 来展示它的强大。

Demo: 射击游戏

这是为了基准测试而制作的游戏。

这没有使用 HTML Canvas,而是生成了 100x100 的 DOM 节点,并在每一帧进行实时重绘。

在 DevTools 中测试发现,JS 负载极低,且维持在 60FPS。在手机上尝试也非常丝滑。

用 React 尝试制作同等效果时,只能跑出 12FPS 左右。

Bundle 体积也保持在 6.4kb 的轻量水平。

源代码在这里 https://github.com/mizchi/luna.mbt/tree/main/src/examples/game

Demo: TodoMVC

虽然最近见得少了,但我还是制作了作为框架基准测试的 TodoMVC。

https://luna.mizchi.workers.dev/demo/todomvc/

我认为这是展示现实应用场景的一个很好的例子。

源代码

https://github.com/mizchi/luna.mbt/tree/main/src/examples/todomvc

Demo: Sol Framework

我正在开发一个相当于 Next.js 的框架,名为 Sol。Luna 和 Sol,即月亮和太阳。

这目前还是一个概念验证(PoC),但部署在 Cloudflare Worker 上的 Demo 已经可以运行了。

据我所知,目前还没有其他框架能实现这一点。

考虑到最近 React 的安全性问题,我正考虑并实现更安全的设计。等可以使用后,我会另写文章介绍。

Astra SSG: 专门为文档制作的 SSG

我一直觉得,如果框架或 UI 库的文档是用其他工具写的,那就太逊了。

所以,我用 Luna 自制了一个静态站点生成器

名字叫 astra。和 astro 很像?结合下文提到的 sol 框架,luna, sol, astra 都是拉丁语中与天体相关的术语...

文档: https://luna.mizchi.workers.dev/

这个文档网站就是用基于 luna 的 SSG 构建的。框架参考了 docusaurus,并添加了我想要的功能,比如通过 00_ 这样的前缀自动对文档进行排序。

bash 复制代码
$ npx @luna_ui/astra new mydocs
$ cd mydocs
$ npx @luna_ui/astra dev
$ npx @luna_ui/astra build # 生成 dist-docs/*

不过目前还是有很多硬编码的实现,可配置项较少。这是未来的课题。

实际上前几天制作的 markdown 编译器就是为了这个而做的。

https://github.com/mizchi/markdown.mbt

为什么选择 MoonBit 实现

这说来话长,自古以来就存在"双重模板问题",即客户端和服务器必须编写相同的模板,并在客户端继承逻辑。

Next.js 是第一个在现实中成功解决这一问题的框架。SSR 不仅仅是在服务器生成 HTML,还必须通过在客户端注入 JS 活跃部分来实现。

根据我使用 Next.js 和 Qwik 的经验,如果要进行 Hydration 和 SSR 时的优化,就必须实现客户端运行时和服务器 SSR 的垂直整合。这是一项非常艰巨的任务,在 Node.js 和浏览器中是通过同一种语言保证幂等性来实现的。除 Node.js 之外几乎没有实现 SSR + Hydration 的原因就在于此。

但是,对于可以跨编译到多种目标的 Moonbit 来说,即使是不同的后端,是否也能实现这一点呢?

MoonBit 可以编译到多个目标:

Plain 复制代码
MoonBit → JavaScript (浏览器)
        → Native (SSR 服务器)
        → Wasm-GC (WasmEdge)

Moonbit 的优势在于其表达能力足以支撑像 Hydration 这样复杂的设计,且 JS 后端生成的代码非常轻量,几乎等同于原生 JS。而且 wasm-gc 后端也很轻量,还有高速的 native 后端。在我的测试中,Native 构建的速度比 JS 快了约 5 倍。

不过,目前执行 SSR 的 Sol Framework 中,服务器大部分是作为 Hono 的绑定实现的,因此只能在 JS 上运行。目前的目标是先在 Cloudflare Workers 的 JS 后端上运行。

欢迎试用

我想我已经展示了 Moonbit 的可能性。

虽然 Astra 和 Sol 的完成度还不算高,但 Luna 核心部分经过了大量测试,应该是相当好用的。

接下来我想挑战以下主题:

  • 完善 WebComponents 和 loader 的示例

  • 制作实时预览编辑器

  • 制作相当于 radix-ui 的 headless UI 框架

  • 在 Cloudflare Worker 上制作 WebComponents Registry

  • 实现 Native Server 的 SSR

  • 支持 Wasm,使其在 wasmedge 上运行

欢迎试用并提供建议。

https://github.com/mizchi/luna.mbt

相关推荐
程序员修心2 小时前
CSS浮动与表格布局全解析
前端·html
天骄t2 小时前
HTML入门:从基础结构到表单实战
linux·数据库
qq_398898932 小时前
【备忘】ASP.Net MVC无缝对接SQL Server数据库设置步骤
数据库·asp.net·mvc
努力学编程呀(๑•ี_เ•ี๑)2 小时前
宝塔上的数据库用Navicat如何连接
数据库
POLITE32 小时前
Leetcode 238.除了自身以外数组的乘积 JavaScript (Day 7)
前端·javascript·leetcode
l1t2 小时前
AI关于MySQL 能否存储数组的回答
数据库·mysql
光影少年2 小时前
AI前端开发需要会哪些及未来发展?
前端·人工智能·前端框架
Vincent_Vang2 小时前
多态 、抽象类、抽象类和具体类的区别、抽象方法和具体方法的区别 以及 重载和重写的相同和不同之处
java·开发语言·前端·ide
菩提小狗2 小时前
小迪安全_第4天:基础入门-30余种加密编码进制&Web&数据库&系统&代码&参数值|小迪安全笔记|网络安全|
前端·网络·数据库·笔记·安全·web安全