NHacker Next
  • new
  • past
  • show
  • ask
  • show
  • jobs
  • submit
Show HN: Coi – A language that compiles to WASM, beats React/Vue
gdotdesign 14 hours ago [-]
It's nice to see that we are converging on the same syntax I came up for Mint (https://mint-lang.com) 8 years ago (feels strange to write it down). I saw Ripple some time ago its syntax has the same structure more or less (component, style, render, state, etc...)
Malp 12 hours ago [-]
Very interesting! Have you considered posting this to HN (again - looks like it was when it first came out & 2 years ago)?
gdotdesign 12 hours ago [-]
Yes! I'm very close to releasing 1.0 and I'm planning on doing it then.
written-beyond 13 hours ago [-]
:O this is so cool how did I never run into it! The most different SPA framework I had seen so far was ELM.
trzeci 12 hours ago [-]
I love the idea and execution. I did some reading of the code webcc and it's just brilliantly simple, and delivers to the promise.

From the product perspective, it occupies a different market than Emscripten, and I don't see it's good comparison. Your product is borderline optimized to run C++ code on Web (and Coi is a cherry on top of that). Where Emscripten is made to make native C++ application to run on Web - without significant changes to the original source itself.

Now, the `webcc::fush()` - what are your thoughts about scalability of the op-codes parsing? Right now it's switch/case based.

The flushing part can be tricky, as I see cases when main logic doesn't care about immediate response/sharing data - and it would be good to have a single flush on the end of the frame, and sometimes you'd like to pass data from C++ while it's in its life scope. On top of that, I'd be no surprised that control of what flushes is lost.

(I'm speaking from a game developer perspective, some issues I'm thinking aloud might be exaggerated)

Last, some suggestion what would make developers more happy is to provide a way to change wasm compilation flags - as a C++ developer I'd love to compile debug wasm code with DWARF, so I can debug with C++ sources.

To wrap up - I'm very impressed about the idea and execution. Phenomenal work!

io_eric 12 hours ago [-]
You're right about the market positioning - WebCC isn't trying to be Emscripten. It's for when you want to build for the web, not just on the web. I'm actually using it myself to port my game engine, currently in the process of ripping out Emscripten entirely.

On the opcode parsing - the switch/case approach is intentionally simple and surprisingly fast. Modern compilers turn dense switch statements into jump tables, so it's essentially O(1) dispatch.

Your flush timing concern is understandable, but the architecture actually handles this cleanly. Buffered commands accumulate, and anything that returns a value auto-flushes first to guarantee correct ordering. For game loops, the natural pattern is batch everything during your frame logic, single flush at the end. You don't lose control, the auto-flush on sync calls ensures execution order is always maintained.

DWARF debug support is a great call

publicdebates 13 hours ago [-]
> 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.

Just curious, what would the FPS be using native plain pure JavaScript for the same exact test?

io_eric 13 hours ago [-]
Good question! Pure JS would likely be comparable or slightly faster for this specific test since there's zero interop overhead. The 10k rectangles benchmark is specifically testing the interop architecture (Emscripten's glue vs. my command buffer), not WASM vs JS performance.

The real advantage comes when you have compute-intensive operations, data processing, image manipulation, numerical algorithms, etc. The batched command buffer lets you do those operations in WASM, then batch all the rendering commands and flush once, minimizing the interop tax.

For pure "draw 10k rectangles with no logic," JS is probably fastest since there's no bridge to cross. But add real computation and the architecture pays off :)

Lalabadie 13 hours ago [-]
I don't have exact numbers, but the difference between drawing to a 2d canvas and a Webgl canvas is also significant.

Different use cases, obviously, but if a project needs very fast 2D drawing, it can be worth the additional work to make it happen in a Webgl context.

dceddia 9 hours ago [-]
I concur, also with no benchmarks to share, but I had the experience of rewriting a video editor timeline to use WebGL instead of the 2D canvas I started with and it got much faster. Like being able to draw 10k+ rectangles at 60fps became easy, where with 2D canvas it was stumbling.
publicdebates 7 hours ago [-]
I don't know any project which uses the 2D canvas. It's horribly inefficient except for the most trivial use-cases (basically demos). Any serious web graphics uses WebGL and shaders.
io_eric 7 hours ago [-]
Hard disagree. Canvas 2D is fully GPU-accelerated in modern browsers and can easily handle thousands of draw calls at 60fps,more than enough for most practical applications. For data visualization, interactive tools, drawing apps, and UI rendering, it's a robust and performant choice. WebGL is often overkill unless you're dealing with extreme datasets or 3D scenes. With its simpler API and faster startup, Canvas 2D is perfectly suited for the vast majority of 2D use cases. Labeling it as 'horribly inefficient' is simply wrong ._.
rthrfrd 14 hours ago [-]
Sounds interesting!

But do you think it would be possible to achieve similar results without a new language, but with a declarative API in one of your existing languages (say, C++) instead?

If possible, that would remove a big adoption barrier, and avoid inevitably reinventing many language features.

io_eric 14 hours ago [-]
A C++ library could wrap DOM APIs (WebCC already does this), but achieving a fine-grained reactive model purely in library code involves major trade-offs

A dedicated compiler allows us to trace dependencies between state variables and DOM nodes at compile-time, generating direct imperative update code. To achieve similar ergonomics in a C++ library, you'd effectively have to rely on runtime tracking (like a distinct Signal graph or VDOM), which adds overhead.

rthrfrd 12 hours ago [-]
Thanks! WebCC looks interesting. I like the attempt to be lean, but in the context of running an entire browser, my personal choice would be for a little runtime overhead vs a new language and toolset.

All the best with it!

P.S. It would also be interesting to see how far it's possible to go with constexpr etc.

bobajeff 12 hours ago [-]
I've been looking into UI libraries lately like Qt and Slint and wondered why they chose to create a DSL over just using css, html and a little bit of js. But I imagine C++ generation from a small DSL is easier than js.
flohofwoe 6 hours ago [-]
First: very nice project, kudos! But:

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

This sounds a bit suspicious tbh.

For instance in this Emscripten WebGL2 sample I can move the instance slider to about 25000 before the frame rate drops below 120 fps on my 2021 M1 MBP in Chrome:

https://floooh.github.io/sokol-html5/drawcallperf-sapp.html

Each 'instance draw' is doing one glUniform4iv and one glDrawElements call via Emscripten's regular WebGL2 shim, e.g. 50k calls across the WASM/JS boundary per 120Hz frame, and I'm pretty sure the vast bulk of the execution time is inside the WebGL2 implementation and the actual call overhead from WASM to JS is negligible (also see: https://hacks.mozilla.org/2018/10/calls-between-javascript-a... - e.g. wasm-to-js call overhead is in the nanoseconds area since around 2018).

Still, very cool idea to have this command batching, but I doubt that the performance improvement can be explained with the WASM-JS call overhead alone, there must be something else going on (maybe it's as simple as the command buffer approach being more cache-friendly, or the tight decoding loop on the JS side allowing more JIT optimizations by the JS engine - but the differences you saw are still baffling, because IME 10k fairly simple operations in 25 or 10 milliseconds (e.g. 40 or 100fps) are not enough too see much of a difference by CPU caches or inefficient JIT code generation, and by far most of the time should be spent inside the WebGL2 implementation, and that should be the same no matter if a traditional shim or the command buffer approach is used).

io_eric 5 hours ago [-]
Thanks for the feedback! You're absolutely right to question this.

Just to clarify, my benchmark was using Canvas2D, not WebGL, that's why the numbers are much lower than your WebGL2 example. Based on your comment I actually removed the command batching to test the difference, and yeah, the batching optimization is smaller than I initially thought. WebCC with batched commands hits ~100 FPS, without batching it's ~86 FPS, and Emscripten is ~40 FPS. So the batching itself only contributes about ~14 FPS.

The bigger performance difference compared to Emscripten seems to come from how Canvas2D operations are handled. Emscripten uses their val class for JS interop which wraps each canvas call in their abstraction layer. WebCC writes raw commands (opcode + arguments) directly into a buffer that the JS side decodes with a tight switch statement. The JS decoder already has direct references to canvas objects and can call methods immediately without property lookups or wrapper overhead. With 10k draw calls per frame, these small per-call differences (property access, type boxing/unboxing, generic dispatch) compound significantly.

hnhn34 2 hours ago [-]
Looks like there's a bug in WebKit-based browsers where clicking on something doesn't always work.

Nothing to add other than I really, really like this. Keep us posted on future updates!

kccqzy 14 hours ago [-]
> 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

This itself is quite cool. I know of a project in ClojureScript that also avoids virtual DOM and analyzes changes at compile-time by using sophisticated macros in that language. No doubt with your own language it can be made even more powerful. How do you feel about creating yet another language? I suppose you think the performance benefits are worthwhile to have a new language?

io_eric 13 hours ago [-]
I started with WebCC to get the best possible performance and small binaries, which works well for things like games. However, writing UI code that way is very tedious. I built Coi to make the development process more enjoyable (better DX) while keeping the efficiency. To me, the gain in performance and the cleaner syntax felt like a good reason to try a new language approach :)
Leftium 9 hours ago [-]
Svelte used to have compile-time reactivity, but switched to runtime reactivity via signals: https://svelte.dev/blog/runes

I'm curious if Coi will also suffer from the drawbacks of compile-time reactivity. Or as its own language that doesn't have to "fit" into JS, can Coi side-step the disadvantages via syntax?

Runes allowed simpler Svelte syntax while making it more clear, consistent, flexible, and composable.

Support for signals may help in terms of interop with native browser JS and other frameworks: https://github.com/tc39/proposal-signals

io_eric 8 hours ago [-]
I suspect Svelte’s heavy lift with compile-time reactivity came largely from trying to infer "reactive intent" from standard JavaScript code. Since JS is so dynamic, the compiler had to rely on heuristics or strict assignment rules

Coi avoids this ambiguity because the compiler can definitively trace usage patterns. Since mut variables are explicitly declared, the compiler essentially just looks at where they are used in the view {} block to establish dependencies at compile time. This static analysis is precise and doesn't require the compiler to "guess" intent, effectively preserving the benefits of compile-time reactivity without the fragility found in dynamic languages

vanderZwan 11 hours ago [-]
I too feel like Emscripten is doing way more than it should for the vast majority of actual use-cases for WASM on the web out there. It's too heavy to install, too much of a hassle to get running, produces way bigger output than it should if the main target is a website, and adds needless friction by being largely oblivious to how the web works from the C++ side of things. The shared memory architecture + batched calls also aligns with hunches I had about unlocking fast WASM for web dev. So this sounds extremely interesting!

Coi looks pretty nice! But honestly I think WebCC might actually be the thing I have been waiting for to unlock WASM on the web. Because if I understand correctly, it would let me write C++ code that compiles to tiny WASM modules that actually integrates with generic JS code very efficiently. Which would make it much easier to add to existing projects where there are some big bottlenecks in the JS code.

Looking forward to giving it a spin!

progx 14 hours ago [-]
I don't like this html-syntax, cause you use a separate syntax-methods for existing js-functions wrapped as html-tags:

  <if text.isEmpty()>
    <div class="preview-text empty">Start typing...</div>
  <else>
    <div class="preview-text">{text}</div>
  </else>
  </if>
As a JS/TS dev it feel unnatural to write language operations as html-tags. That is what i did not like in svelte too.

Here something that looks little more like PHP-Style, better separation, but too much to type:

  <?coi
  if (empty($text)) {
  ?>
    <div class="preview-text empty">Start typing...</div>
  <?coi
  } else {
  ?>
    <div class="preview-text">${text}</div>
  <?coi
  }
  ?>

Shorter with a $-func for wrapping html-content

  if (empty($text)) {
    $(<div class="preview-text empty">Start typing...</div>)
  } else {
    $(<div class="preview-text">${text}</div>)
  }
I don't know, has somebody a better idea?
gdotdesign 13 hours ago [-]
It's perfectly fine to allow if in tags (the compiler can figure it out). In Mint you can do that no problem: https://mint-lang.com/sandbox/6lnZHiG8LVRJqA

    component Main {
      fun render : Html {
        <div>
          if true {
            <div>"Hello World!"</div>
          } else {
            <div>"False"</div>
          }
        </div>
      }
    }
zareith 13 hours ago [-]
Vue style attribute directives are imho a far better dx compared to all of the above.
frankhsu 14 hours ago [-]
Gotta say the Shared Memory approach is genius. Finally someone's cutting down the clunky back-and-forth.
elzbardico 11 hours ago [-]
This is the kind of innovation that coding agents make difficult to get traction.
arendtio 14 hours ago [-]
From a syntax perspective, I prefer the component syntax in Vue / Riot, which is HTML-like. That way, the general structure is clear, and you have to learn only the additional directives. As a bonus, syntax highlighting in most editors just works without an additional plugin.
written-beyond 14 hours ago [-]
I think this genuinely might be the first time I'm seeing a language rework for UI's that actually makes sense and incorporates all that we've learned in the modern age about UI code.

What I am wondering is how language interop will work? The only way I see this growing is either you can easily import js libraries or you get a $100,000 dono and let Claude or any other LLM run for a few days converting the top 200 most used react packages to Coi and letting it maintain them for a few months until Coi's own community starts settling in.

I would love to use this for non web use cases though, to this date UI outside of the browser(native, not counting electron) is still doggy doo doo when compared to the JS ecosystem.

anonymous908213 13 hours ago [-]
> converting the top 200 most used react packages to Coi

Web developers write your own code challenge (impossible)

zigzag312 14 hours ago [-]
Well, you claim to combine several interesting features. Type safety, small binary size, high performance, predictable performance (no GC). So, I'm interested how this will turn out.

For web small binary size is really important. Frameworks like Flutter, Blazor WASM produce big binaries which limits their usability on the web.

JS/TS complicates runtime type safety, and it's performance makes it not suitable for everything (multithreading, control over memory management, GC etc.)

I wonder how much/if no GC hurts productivity.

It looks like Coi has potential to be used for web, server and cross-platform desktop.

Since the intermediate step is C++ I have a question what this means for hot-reload (does that make it impossible to implement)?

orphea 15 hours ago [-]
Did you compare with Svelte?
k__ 15 hours ago [-]
Came to ask this.

React and Vue aren't exactly known for their performance and Svelte does compile time optimizations.

embedding-shape 15 hours ago [-]
Heh, not an argument against you or any point you made, today you are right. But when React first made an appearance, basically the two big selling points was 1) use same state in multiple places and 2) better performance.

Fun how with time, the core purpose of a library ever so slightly change :)

timeon 12 hours ago [-]
Was React really faster than Prototype? Anyway today it is one of the slowest: https://krausest.github.io/js-framework-benchmark/2026/chrom...
embedding-shape 12 hours ago [-]
As far as I remember, in some cases yes. I remember when it initially launched, the typical demo was a list of 10K items or something, and the speaker (maybe Pete Hunt?) demonstrated the amount of time it took to add/remove items from that list, and how without the Virtual DOM, there was a lot of trashing (or something), and with the vdom, things got a lot faster.

I think this was back in 2013-2014 sometime though, so I might be misremembering, it's over a decade ago after all.

k__ 14 hours ago [-]
I mean, that was a decade ago and back in the day the only reasonable contenders were Angular and maaaaybe ExtJS.
embedding-shape 13 hours ago [-]
Backbone.js, Knockout.js, Ember, Dojo, Prototype, YUI all were reasonable alternatives at the time. Although all with their own warts and highlights, as is tradition.
mirekrusin 14 hours ago [-]
It appears in comparison chart on the linked page.
orphea 11 hours ago [-]
arikrahman 34 minutes ago [-]
Reminds me of the old xkcd strip about all the languages
NetOpWibby 8 hours ago [-]
This looks great.

Is it possible to import modules from npm/jsr?

LudwigNagasena 12 hours ago [-]
What are the exact features that require it to be a new language with new syntax?
io_eric 11 hours ago [-]
Reactive DOM updates – When you change state, the compiler tracks dependencies and generates efficient update code. In WebCC C++, you manually manage every DOM operation and call flush().

JSX-like view syntax – Embedding HTML with expressions, conditionals (<if>), and loops (<for>) requires parser support. Doing this with C++ macros would be unmaintainable.

Scoped CSS – The compiler rewrites selectors and injects scope attributes automatically. In WebCC, you write all styling imperatively in C++.

Component lifecycle – init{}, mount{}, tick{}, view{} blocks integrate with the reactive system. WebCC requires manual event loop setup and state management.

Efficient array rendering – Array loops track elements by key, so adding/removing/reordering items only updates the affected DOM nodes. The compiler generates the diffing and patching logic automatically.

Fine-grained reactivity – The compiler analyzes which DOM nodes depend on which state variables, generating minimal update code that only touches affected elements.

From a DX perspective: Coi lets you write <button onclick={increment}>{count}</button> with automatic reactivity. WebCC is a low-level toolkit – Coi is a high-level language that compiles to it, handling the reactive updates and DOM boilerplate automatically.

These features require a new language because they need compiler-level integration – reactive tracking, CSS scoping, JSX-like templates, and efficient array updates can't be retrofitted into C++ without creating an unmaintainable mess of macros and preprocessors. A component-based declarative language is fundamentally better suited for building UIs than imperative C++.

iamsaitam 14 hours ago [-]
This looks very interesting! Do you have any tips/pointers on how one could use Coi to generate a component and then integrate it into an existing project which uses a traditional Javascript framework?
radicalethics 11 hours ago [-]
Still holding out for a full WASM DOM renderer.
merqurio 15 hours ago [-]
Nice work ! Thanks for shating

It reminds me of https://leptos.dev/ in Rust, although the implementation might be very different

nkmnz 12 hours ago [-]
Could this be a transpilation target for existing Vue code to achieve smaller bundle size and higher runtime speed?
io_eric 10 hours ago [-]
Possible in theory, but a Vue→Coi transpiler would be complex given the different reactivity models and syntax.

Most practical approach: AI-assisted conversion. Feed an LLM the Coi docs + your Vue code and let it transform components. For migrating existing codebases, that's likely the most efficient path.

For new code, writing Coi directly is simpler :)

amelius 13 hours ago [-]
So the style and view parts work like f-strings in Python?

That's something I could live with.

monster2control 11 hours ago [-]
I like it. Here’s hopping it continues to grow
skybrian 14 hours ago [-]
This looks quite promising. How long does it take to compile?
io_eric 13 hours ago [-]
Pretty fast. It doesn't drag in the C++ standard library, so builds stay lean. My demo page takes about ~1s to compile for me (after the first time)
hans2002 4 days ago [-]
Binary size alone got me interested. What's missing before 1.0?
4 days ago [-]
doublerabbit 11 hours ago [-]
COI compiles on FreeBSD but the example app didn't.

   Fatal error: 'stdint.h' file not found
Yet exists within /usr/include

Not a rant, but developers, please include testing on FreeBSD. Git issue raised.

io_eric 11 hours ago [-]
Fixed! The issue was specific to FreeBSD's clang setup. When compiling with --target=wasm32 and -nostdlib, clang on Linux/macOS still finds minimal freestanding headers for the wasm32 target, but FreeBSD's clang doesn't have these configured by default - even though stdint.h exists, it's not in the search path for cross-compilation targets.

The fix was adding freestanding stdint.h and stddef.h to webcc's compat layer using compiler built-ins (__SIZE_TYPE__, etc.). This makes webcc work consistently across all platforms without relying on platform-specific clang configurations.

I hope it works now for you - hit me up if there are still problems!

doublerabbit 10 hours ago [-]
Can confirmed fixed. tyvm!
zedai00 15 hours ago [-]
would love to try it soon!
cap11235 14 hours ago [-]
https://www.gnu.org/software/stow/manual/stow.html If what you want is an orchestrated symlink farm, here's your dude.
Hasnep 13 hours ago [-]
Wrong thread?
gethly 14 hours ago [-]
[flagged]
nebezb 8 hours ago [-]
A low effort reply for a low quality take.

Read the post. You might learn something.

bookofsleepyjoe 14 hours ago [-]
[flagged]
Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact
Rendered at 01:49:35 GMT+0000 (Coordinated Universal Time) with Vercel.