This is a post from my old blog before I switched to a garden model.

Updates

As I explained elsewhere, I gave up writing my own blogging software. But this was still an interesting exploration, so I leave it up in case it helps someone in the future.


I’ve been playing around with WASM and Rust a bit lately. Mostly as a way to avoid writing JavaScript, but also because Rust has some useful existing libraries that I want to use that aren’t available in the JavaScript world.

I wrote the blog software I’m using in JavaScript as a proof of concept of using client-side JavaScript code to do all the heavy lifting of a static site generator with the hope of making something that a non-technical person could use. Most static site generators target people who are comfortable with command line tools, and while there is Publii, which I think is a great piece of software, I think there is still some space for a static site generator with features that make it work for more than just blogging.

Lately I’ve been trying to write one using the ServiceWorker API, and using WASM for the code that runs the ServiceWorker. This has turned out to be tricker than I had hoped, because of some limitations of how the ServiceWorker has to be set up.

The basic idea of what I want to do is use a ServiceWorker as a client-side “server” that responds to requests like an actual HTTP server would, just using the fetchfetch handler to do it. I’ve gotten a working concept and I’m just trying to polish it up so it could be used for a variety of projects.

One of the nice things that falls out of this is that it is offline-first. The whole idea of ServiceWorkers is a way to support offline web applications that work in both online and offline modes.

Why would you want to write this in Rust though?

That’s a good question, and one worth asking. This isn’t just a “Rewrite it in Rust” exercise. The thing is, there are several crates I want to use, syntect for syntax highlighting among others, and I really appreciate the help of having a language that will catch dumb errors that I’ll make instead of silently ignoring them (on this front TypeScript might be a good alternative, but doesn’t solve the first problem).

Want to see what I’m talking about?

Part of the problem I ran into is that for ServiceWorkers you need to register some event handlers, and they have to happen immediately, not in any async code. Unfortunately, as far as I can tell loading WASM takes async code, so you can’t just register these callbacks in the WASM code without some support from the JavaScript code.

I found a project called go-wasm-http-server that does basically what I want, and worked around some of the problems I was running into, but is in Go and not Rust. Go has issues that I won’t go into here, but it’s a perfectly cromulent language, just not the one I want to use.

In that go-wasm-http-server project, they managed it by configuring a Promise that the WASM code resolves so it can pass back the callback that the event handler needs to service the request. In their ServiceWorker JavaScript code they do something like this:

const handlerPromise = new Promise(resolve => {
	self.setFetchHandler = resolve;
});
 
self.addEventListener("fetch", event => {
   // do some prelimenary stuff, url checking or something
   event.respondWith(handlerPromise.then(handler => handler(event.request)))
});
const handlerPromise = new Promise(resolve => {
	self.setFetchHandler = resolve;
});
 
self.addEventListener("fetch", event => {
   // do some prelimenary stuff, url checking or something
   event.respondWith(handlerPromise.then(handler => handler(event.request)))
});

Now, while working on my version and trying to see what was happening with logging, I did something like this:

// same handlerPromise code
self.addEventListener("fetch", event => {
	let promise = handlerPromise.then(handler => {
		let response = handler(event.request);
		console.log(response);
		response
	});
	event.respondWith(promise);
});
// same handlerPromise code
self.addEventListener("fetch", event => {
	let promise = handlerPromise.then(handler => {
		let response = handler(event.request);
		console.log(response);
		response
	});
	event.respondWith(promise);
});

Can you see the error? I’m sure the people who write JavaScript day in and day out can see the issue. I forgot to actually returnreturn the responseresponse on Line 6 . It’s probably because I was writing a lot of Rust where if the last value of a function doesn’t end with a ;; it’s the return value.

But what happens when this code is loaded in the browser? Do we get a console warning about how the Promise’s thenthen closure isn’t returning anything? Nope.

I can’t really blame them though, because we didn’t say we wanted a return value from that closure. We might have that option in something like TypeScript, but this isn’t that. After an hour or so trying to figure out what was wrong, I could move on to trying to polish my code up to turn it into something that will allow for fetching templates and doing rendering of content as needed.

The idea is that this ServiceWorker will provide the UI for editing the content, and it will use something like the AWS JavaScript SDK to store the output in S3 for publishing. That’s basically what my current JavaScript proof-of-concept does, but I want to clean it up.

I also want to try to use Htmx and maybe some Hyperscript as well for the UI itself. If you want to know why I’d want to use something like this, I think there’s some merit in how the web was originally concived and I feel like these two things push us back in that direction. It might be a dead end, but when working on personal projects I think that rule number one should always be:

Explore something new, nothing else matters.

Until next time.