展示HN:Coi – 一种编译为WASM的语言,超越React/Vue

25作者: io_eric16 天前原帖
我通常用 C++ 开发网页游戏,但使用 Emscripten 总让我觉得有些过于复杂,因为我并不需要完整的 POSIX 模拟或庞大的标准库,只是为了在画布上渲染一些东西并处理基本的用户界面。 我想解决的主要问题是 JS/WASM 的互操作瓶颈。与其为每个调用使用标准的粘合代码,我将所有内容迁移到一个共享内存架构,使用命令和事件缓冲区。 它的工作原理是,我将所有的指令批量处理在 WASM 中,然后只向 JS 发送一个“刷新”信号。JS 端随后一次性直接从共享内存中读取所有内容。这种方式效率更高,我进行了一次基准测试,在画布上渲染 10,000 个矩形,结果差异显著:Emscripten 的帧率大约为 40 FPS,而我的设置达到了 100 FPS。 但是在 C++ 中编写 DOM 逻辑是非常痛苦的,所以我构建了 Coi。它是一种基于组件的语言,在编译时静态分析变化,以实现 O(1) 的反应性。与传统框架不同,它没有虚拟 DOM 的开销;编译器将状态变化直接映射到命令缓冲区中的特定句柄。 我最近对比了 Coi 和 React、Vue 在一个 1,000 行表格上的性能:在行创建、行更新和元素交换方面,Coi 表现最佳,因为它完全避免了“差异”步骤,并最小化了桥接跨越。它的包大小也是三者中最小的。 这个架构最酷的地方之一是标准库的工作方式。如果我想支持一个新的浏览器 API(比如 Web Audio 或新的 Canvas 功能),我只需将定义添加到我的 WebCC 模式文件中。当我重新编译 Coi 编译器时,该语言会自动获得一个新的标准库函数来访问该 API,完全不需要手动封装。 我对这个项目的进展感到非常自豪。它结合了自定义 WASM 堆栈的性能和一种让我觉得写起来很舒服的语法(至少对我来说是这样 :P)。此外,由于中间步骤是 C++,我还在考虑让它在服务器端工作,这样就可以在整个堆栈中共享组件。 示例(Coi 代码): ```coi component Counter(string label, mut int& value) { def add(int i) : void { value += i; } style { .counter { display: flex; gap: 12px; align-items: center; } button { padding: 8px 16px; cursor: pointer; } } view { <div class="counter"> <span>{label}: {value}</span> <button onclick={add(1)}>+</button> <button onclick={add(-1)}>-</button> </div> } } component App { mut int score = 0; style { .app { padding: 24px; font-family: system-ui; } h1 { color: #1a73e8; } .win { color: #34a853; font-weight: bold; } } view { <div class="app"> <h1>Score: {score}</h1> <Counter label="Player" &value={score} /> <if score >= 10> <p class="win">You win!</p> </if> </div> } } app { root = App; title = "My Counter App"; description = "A simple counter built with Coi"; lang = "en"; } ``` 在线演示: [https://io-eric.github.io/coi](https://io-eric.github.io/coi) Coi(语言): [https://github.com/io-eric/coi](https://github.com/io-eric/coi) WebCC: [https://github.com/io-eric/webcc](https://github.com/io-eric/webcc) 我很想听听你的想法。虽然它仍然远未完成,但作为一个副项目,我对此感到非常兴奋 :)
查看原文
I usually build web games in C++, but using Emscripten always felt like overkill for what I was doing. I don&#x27;t need full POSIX emulation or a massive standard library just to render some stuff to a canvas and handle basic UI.<p>The main thing I wanted to solve was the JS&#x2F;WASM interop bottleneck. Instead of using the standard glue code for every call, I moved everything to a Shared Memory architecture using Command and Event buffers.<p>The way it works is that I batch all the instructions in WASM and then just send a single &quot;flush&quot; signal to JS. The JS side then reads everything directly out of Shared Memory in one go. It’s way more efficient, I ran a benchmark rendering 10k rectangles on a canvas and the difference was huge: Emscripten hit around 40 FPS, while my setup hit 100 FPS.<p>But writing DOM logic in C++ is painful, so I built Coi. It’s a component-based language that statically analyzes changes at compile-time to enable O(1) reactivity. Unlike traditional frameworks, there is no Virtual DOM overhead; the compiler maps state changes directly to specific handles in the command buffer.<p>I recently benchmarked this against React and Vue on a 1,000-row table: Coi came out on top for row creation, row updating and element swapping because it avoids the &quot;diffing&quot; step entirely and minimizes bridge crossings. Its bundle size was also the smallest of the three.<p>One of the coolest things about the architecture is how the standard library works. If I want to support a new browser API (like Web Audio or a new Canvas feature), I just add the definition to my WebCC schema file. When I recompile the Coi compiler, the language automatically gains a new standard library function to access that API. There is zero manual wrapping involved.<p>I&#x27;m really proud of how it&#x27;s coming along. It combines the performance of a custom WASM stack with a syntax that actually feels good to write (for me atleast :P). Plus, since the intermediate step is C++, I’m looking into making it work on the server side too, which would allow for sharing components across the whole stack.<p>Example (Coi Code):<p>component Counter(string label, mut int&amp; value) {<p><pre><code> def add(int i) : void { value += i; } style { .counter { display: flex; gap: 12px; align-items: center; } button { padding: 8px 16px; cursor: pointer; } } view { &lt;div class=&quot;counter&quot;&gt; &lt;span&gt;{label}: {value}&lt;&#x2F;span&gt; &lt;button onclick={add(1)}&gt;+&lt;&#x2F;button&gt; &lt;button onclick={add(-1)}&gt;-&lt;&#x2F;button&gt; &lt;&#x2F;div&gt; }</code></pre> }<p>component App { mut int score = 0;<p><pre><code> style { .app { padding: 24px; font-family: system-ui; } h1 { color: #1a73e8; } .win { color: #34a853; font-weight: bold; } } view { &lt;div class=&quot;app&quot;&gt; &lt;h1&gt;Score: {score}&lt;&#x2F;h1&gt; &lt;Counter label=&quot;Player&quot; &amp;value={score} &#x2F;&gt; &lt;if score &gt;= 10&gt; &lt;p class=&quot;win&quot;&gt;You win!&lt;&#x2F;p&gt; &lt;&#x2F;if&gt; &lt;&#x2F;div&gt; }</code></pre> }<p>app { root = App; title = &quot;My Counter App&quot;; description = &quot;A simple counter built with Coi&quot;; lang = &quot;en&quot;; }<p>Live Demo: <a href="https:&#x2F;&#x2F;io-eric.github.io&#x2F;coi" rel="nofollow">https:&#x2F;&#x2F;io-eric.github.io&#x2F;coi</a><p>Coi (The Language): <a href="https:&#x2F;&#x2F;github.com&#x2F;io-eric&#x2F;coi" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;io-eric&#x2F;coi</a><p>WebCC: <a href="https:&#x2F;&#x2F;github.com&#x2F;io-eric&#x2F;webcc" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;io-eric&#x2F;webcc</a><p>I&#x27;d love to hear what you think. It&#x27;s still far from finished, but as a side project I&#x27;m really excited about :)