Building a small game using txiki.js and SDL3

It’s Friday, so time to fool around some! Tonight I wanted to connect some dots and see if building a small game is feasible using txiki.js, my tiny JavaScript engine, and SDL3.

First we need some prep, how are we going to use SDL3 from txiki.js? This past week I’ve been taking a look at the ffi bindings in txiki.js and made some performance improvements, with the hope that something like this would be possible.

I then found SDL2 ffi bindings for Deno, courtesy of littledivy. With some help from Claude Code, I ported those to txiki.js and adapted them to SDL3, you can find the repo here.

With all the ducks in a row, some cool demos could be built!

Here is a small shooting game with audio and everything.

I am blown away. Building a small game is something I have wanted to do forever, and now that I see it’s possible to do with the tools that I build, I plan on trying to build one from scratch. My first thought is to recreate an old frogger style, and the first game I ever remember playing as a kid: Horace Goes Skying.

txiki.js 26.3.0 released, a new dawn!

txiki.js is a small and powerful JavaScript runtime. It targets the latest ECMAScript spec and implements many web platform features.

After a long while (I’m counting since release 24) txiki.js 26.3.0 has been released, and it’s the biggest release since its inception, so let’s unpack the release.

Replace wasm3 with WAMR

wasm3 was a great match for txiki.js: it was “just a bunch of C files”, easy to embed, and it just worked. Alas its creator suffered acute personal losses due to the war in Ukraine and the project development stopped.

Development never picked up, and since issues were not being solved, a contributor suggested migrating to WAMR and created a draft PR. It took a while, but it ultimately took it over and pushed it over the finish line! txiki.js now has a well supported and more featureful WASM support.

I also took the chance to modernize the WASI interface and add a couple of extra features there.

Native Windows build with MSVC

Something I wanted to do for a while was to make a better Windows build. Using MSVC, and fully static. Thanks to the move to WAMR and away from curl, making all of that happen became attainable. vcpkg helped, and the release binary is now a statically linked 5-6MB exe!

HTTP client improvements

txiki.js started out with a simple fetch polyfill, layered on top of XHR. This polyfill had some shortcomings, and I ended up vendoring it so I could make some changes to better adapt it to our internals, and modernize it, since it didn’t support streaming interfaces.

Roughly at that point I realized that rather than using XHR under the hood, it would probably be better to have a somewhat low level HTTP client which both XHR and fetch use internally. This also unlocked a big milestone for fetch in txiki.js: streaming support.

In addition, both fetch and XHR now support automatic decompression!

Web streams all the things!

Alright, let’s get with the big ones. Sockets, stdio and child process streams APIs have historically been somewhat bespoke across different runtimes (Node, Deno and Bun) but ever since the introduction of Web Streams, there seems to be convergence towards using them as the main API surface for reading and writing.

I went ahead and changed all custom APIs in txiki.js to be “Web Streams First”. There is no custom read / write API anymore, it’s Web Streams all the way down!

This suddenly makes everything more composable, and likely future proof.

Direct Sockets

Another big one. When I was browsing through WinterTC specs I noticed an interesting one, the sockets API. As I was browsing through the open issues I noticed there was a mention of Direct Sockets, a proposal Google is championing, that started out before the WinterTC one, but remained dormant for a while, but it picked up recently.

After looking at both I decided to go with Direct Sockets for now, since it’s the more complete one. I extrapolated pipe support and we are off to the races.

I’ll keep watching the space and adapt as it evolves, but the core is very similar.

Replaced curl with libwebsockets

I’ll write an in-depth article only for this one, but essentially I made the call to replace curl with libwebsockets because it gives us access to HTTP and WebSocket clients and servers with a single library.

Since we need some TLS support, I ended up using MbedTLS, since OpenSSL is way too big. This also allows me to start implementing the Subtle Crypto API on top of it going forward.

HTTP and WebSocket server

Oh boy was this a long time in the making. I had wanted this for such a long time, but needed to have all the ducks in a row. With the introduction of libwebsockets we were almost there already, so I went for it.

txiki.js now as a simple API for HTTP / WS servers, inspired by Deno, Bun and Cloudflare Workers. It’s compatible with Hono with a simple adapter too! Here is a sample:


// Run with: tjs serve examples/http-server.js
//

export default {
    fetch(request) {
        const url = new URL(request.url);

        return new Response(`Hello World!\nYou requested: ${url.pathname}\n`);
    },
};

Here is a en echo WebSocket server:

// Run with: tjs serve examples/ws-echo-server.js
//
// Connect with: websocat ws://localhost:8000
//

export default {
    fetch(request, { server }) {
        if (request.headers.get('upgrade') === 'websocket') {
            server.upgrade(request);

            return;
        }

        return new Response('This is a WebSocket server. Connect using a WebSocket client.\n');
    },
    websocket: {
        open(ws) {
            console.log('Client connected');
        },
        message(ws, data) {
            console.log(`Received: ${data}`);
            ws.sendText(`echo: ${data}`);
        },
        close(ws, code, reason) {
            console.log(`Client disconnected: ${code} ${reason}`);
        },
    },
};

Code bundling

Even though creating a standalone has been a possibility for a while with txiki.js, it was annoying that bundling had to be manually done.

With all the streams and fetch work done, it was easy to chain streams to download and decompress esbuild, so that’s what tjs bundle does first, before running it with the right command line arguments to create a bundle txiki.js will handle well. Problem solved!

New website!

To put the cherry on top (and because I was really pumped!) I decided to tackle something I had been putting back for a long time: a website for the project with the documentation and guides.

I’ve done similar things for other projects before, and the biggest problem is usually putting together the first version. Incremental improvements are easier, but getting started can feel impossible.

Well, that’s done now, so head over to txikijs.org to see it in action.

Shout out to my oldest daughter for doing the logo ❤️

Other cool stuff

Happy tinkering! As for me, I hope to make progress on TLS support and some of the subtle crypto API for the next release.

QuickJS-NG 0.10.0 released!

QuickJS is a small and embeddable JavaScript engine. It aims to support the latest ECMAScript specification. QuickJS NG is a fork of the original QuickJS project by Fabrice Bellard and Charlie Gordon, after it went dormant, with the intent of reigniting its development.

Another few months of development, version 0.10.0 is out. Here are some notable changes

  • Implemented Array.fromAsync
  • Support os.Worker on Windows
  • Added parserless build mode
  • Added os.exePath()
  • Added promise hooks
  • Fixed reporting handled promises as unhandled in tracker

See the full changelog here.

txiki.js 24.12.0 released

txiki.js is a small and powerful JavaScript runtime. It targets the latest ECMAScript spec and implements many web platform features.

It’s built on the shoulders of giants: it uses QuickJS-ng as its JavaScript engine, libuv as the platform layer, wasm3 as the WebAssembly engine and curl as the HTTP / WebSocket client.

Earlier this week I released version 24.12.0 featuring standalone executables, namespace reorganization, more web platform features, initial binary releases, more filesystem APIs, REPL updates and more! Check here for the full changelog, and read-on for some highlights.

Read more

txiki.js 24.6.0 released!

Long time, no post. Time to break the dry spell!

txiki.js is a small and powerful JavaScript runtime. It targets ECMAScript 2023 and implements many web platform features.

It’s built on the shoulders of giants: it uses QuickJS-ng as its JavaScript engine, libuv as the platform layer, wasm3 as the WebAssembly engine and curl as the HTTP / WebSocket client.

This is the first release after 6 months, and a lot has happened since the last one. 103 commits to be exact. Let’s break it down.

Read more

txiki.js 23.10.0 released!

txiki.js is a small and powerful JavaScript runtime. It targets ECMAScript 2020 and implements many web platform features.

It’s built on the shoulders of giants: it uses QuickJS as its JavaScript engine, libuv as the platform layer, wasm3 as the WebAssembly engine and curl as the HTTP / WebSocket client.

After a few months of hiatus I’m happy to announce a new txiki.js release: 23.10.0, and it’s a good one!

This new release is packed with new features:

  • Faster startup time
  • New fs features
  • SQLite builtin module
  • Storage web APIs support
  • curl integration fixes
  • Added import.meta.path
  • Better CLI errors
  • Improved types documentation

Check the full changelog here!

This release is particularly exciting because I’ve been looking forward to integrating SQLite support for a while. I started implementing the Storage API support thinking I’d use a file for persistence, but at one point I thought I’d use a SQLite databse instead, so I went for it!

There is likely stuff to be improved specially in this new builtin, the SQLite module, but I’m happy it’s in now!

The last release was over 6 months ago, so please do check the full changelog, I almost didn’t remember all the things that were added in the months past…

Thinking ahead, it might be time to start bringing in Mbed TLS support. Maybe just hasing at first, then TLS sockets, then WebCrypto. Step by step…

txiki.js 23.1.0 released, and it’s a big one!

txiki.js is a small and powerful JavaScript runtime. It’s built on the shoulders of giants: it uses QuickJSas its JavaScript engine, libuv as the platform layer, wasm3 as the WebAssembly engine and curl as the HTTP client.

After a few months of more work I’m happy to announce a new txiki.js release, version 23.1.0, quite possibly the biggest release I’ve made, and one that paves the way for the future. Let’s dive right in!

Standard library refactor

I’ve actually gone back and forth on this one. The standard library modules started being importable as @tjs/ modules, but I later switched to having it all bundled together in @tjs/stdlib. While easier to work with, that was a mistake. It’s slower to load and the churn due to the bundles being in the repo is just annoying.

It’s also important to look around and see what others are doing. Both Node and Bun seem to have settled in using namespace:module naming for builtins, so that’s what I went with. All standard library modules are now imported like so: tjs:module.

These modules are now documented too, so you can see the full API at a glance.

CLI refactor

The CLI also got some much needed love on this one. Generally speaking the CLI allowed one to either launch a file or eval an expression, plus some modifiers for each. After giving it some though, the idea of using subcommands sounded the most appealing, so now you can do tjs run foo.js and tjs eval ”console.log(42)”

But there is more. There is a also a builtin test runner now so tjs test is a thing too!

Last, running WASI binaries directly was too easy not to do, so you can also tjs run foo.wasm now and it works as expected.

Top level await

Just when I was about ready to cut the release I noticed zamfofex had sent a patch adding top level await support to QuickJS to the mailing list. That was too good to pass so I quickly incorporated it, and it worked beautifully!

This makes writing simple scripts (and tests!) so much more pleasant! So off I went and migrated the test suite to use TLA, the examples too, and added some initial support to the REPL while I was at it!

There is even more!

While these are the highlights, there are a lot more fixes that went into the release. Please checkout the full release notes here.

txiki.js 22.11 released with FFI, WebSocket and more!

txiki.js is a small and powerful JavaScript runtime. It’s built on the shoulders of giants: it uses QuickJS as its JavaScript engine, libuv as the platform layer, wasm3 as the WebAssembly engine and curl as the HTTP client.

Today I’m happy to release txiki.js 22.11, which contains a bunch of improvements that have happened during the last few months. Let’s look at some of those!

Read more