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.

QuickJS-NG 0.9.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.

After a couple of months of development version 0.9.0 is out. A few notable changes:

  • qjs will now exit on unhandled rejections: this aligns the reference interpreter with Node, Deno and Bun
  • Tons of new C APIs: JS_IsDate, JS_IsRegExp, JS_IsMap, JS_IsPromise and more, check quickjs.h
  • QUICKJS_NG is now defined so applications wanting to support both the old version and NG have an easy way to tell them appart
  • Inline caches have been removed, see the interesting conversation here
  • Amalgamated builds, see quickjs-amalgam.zip in the release artifacts, just a C file and 2 header files!
  • Meson build system support

See the full changelog here.

TIL: the default stack size on Windows is 1MB

When compiling with MSVC. Here is how I learned that.

We are close to merging Meson support for QuickJS. During the PR review a test was failing when compiled with Clang and ubsan in debug mode. It turned out to be related to the test setting a very small stack size for the interpreter, so I suggested to remove it entirely. Tests passed! … except on Windows. They now failed on all configurations, not only in debug mode. Time to dig in.

The first step is of course try and reproduce the problem. Luckily I have an old Windows laptop where I can test stuff. I could easily reproduce the problem. The api-test target consistently failed. It simply crashed without providing any output. Checking the exit code ($LASTEXITCODE on PowerShell) yielded -1073741571.

So, since it’s 2025 I decided to ask ChatGPT and see if it could shed any light, and it was right on the money:

The exit code -1073741571 (or 0xC00000FD in hexadecimal) indicates a Stack Overflow Exception (STATUS_STACK_OVERFLOW).

Aha! I had recently updated the default interpreter stack size to 1MB, so I wondered if the default stack size on Windows would be around that value, and thus it would crash before it could be caught by the engine. Guess what, it’s exactly 1MB. Bingo! (While writing this post I ran into the conversation Ben and I had about the V8 default in that very PR, which did mention the 1MB!)

Turns out it’s also easy to set it at build time: passing the /STACKSIZE linker flag. Adding /STACK:8388608 to the linker would set it to 8MB, as the default is on Linux. I created a PR setting it to 8MB both for MSVC and MinGW (for consistency) and the tests were green, success!

Fixing 5GHz WiFi on an Archer C7 with OpenWRT

I’ve had a TP-Link Archer C7 v2 router for quite a while. I can’t remember exactly why or when I got it but I suspect it was to tinker with OpenWRT 🙂

At some point in time I was having some weird WiFi problems, specifically on the 5GHz bands. Bandwidth would tank to the point of becoming unusable even for video streaming, and I couldn’t figure out why. I ended up shelving it, but I chose to keep it since the OpenWRT support was great (modulo this) since it might come in handy later. I guess that later is now 🙂

I wanted to embark on a little routing adventure (post coming soon!) so I decided to dust off the good old Archer and updated it to the latest OpenWRT which was recently released. I remembered the 5GHz WiFi problem so I decided to search the web and see if there was any new solution to my old problem. Unconstrained by the need to get it working or by mere luck, I found an issue on GitHub which described my problem to the letter! This comment was right on the money.

Apparently OpenWRT changed the firmware driver used for the chip in this particular driver from the original ath10k to a fork called ath10k-ct which was going to be better maintained. Not sure what happened there, but it seems like ath10k is a better one for this router so the way forward here is to go ahead and use `ath10k`. The following commands are better run over a wired connection to make sure you don’t lose connectivity after we uninstall and reinstall WiFi firmware.

# First remove the old firmware
opkg remove kmod-ath10k-ct ath10k-firmware-qca988x-ct

# Update the package list
opkg update

# Install the non -ct version of the firmware
opkg install kmod-ath10k ath10k-firmware-qca988x

# Reboot
reboot

After the router comes back up the 5GHz WiFi should be stable again! Now back to tinkering…

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…