Experimenting with no-build Web Applications

Some years ago I build an eBook generator called Little Webby Press. It would pick a folder with Markdown files and turn them into an EPUB3 non-DRM eBook file and also a zip containing a static website for your book.

The cool thing about Little Webby Press is that all the workflow happens on the client-side. There's no back end, no database, no user accounts. You drag and drop a folder, press a button, and it works.

I used it to make some niche technical books in the past and since I don't keep analytics, I have no idea how many people have used it.

Old LWP

Old version of LWP.

I spent the recent years without writing any book, but recently, after developing BlogCat and writing The Web Should Be A Conversation, I have an itch to write a book about WebExtensions. Of course, I'd like to use my own eBook generator to write this book, and that is how I nerdsnipped myself into making a new eBook generator.

There was nothing wrong with the old version of Little Webby Press. It was built by leveraging the following stack:

  • Svelte 3 for the UI.
  • BrowserFS to have a browser-side filesystem so I could leverage NodeJS file handling routines.
  • Handlebars for the templates used by both eBook and website generation.
  • JsZip to create the EPUB and the zip containing the website.
  • Rollup and a gazillion plugins
  • Taildwind and Daisy for CSS

All in all a very common setup for like five years ago. The problem is that I don't like the current version of Svelte and I really dislike the current web development ecosystem with its reliance on transpilation and too many frameworks and dependencies and build steps. Feels like we're developing in a fantasy version of JS that no engine is really able to run. I hate that.

So instead of writing the book I want to write, I decided to rewrite Little Webby Press in a stack that is more suitable to my own enjoyment.

New LWP.

The new Little Webby Press

My main objective with the new version was to eliminate the build step. I wanted a no-build system in which the JS I write is exactly what runs on the browser.

Instead of implementing everything from scratch, I kept many of the NodeJS-based dependencies but used an importMap to get them from JsDelivr. This was done so I could reuse the majority of the book and website generation code.

Don't want to read this blog post, you can go check the source code.

I switched from Svelte to Mithril — which is my favourite JS lib to make UIs — and completely removed BrowserFS. Instead I created my own file handling routines which have the same arguments as the NodeJS ones but different names. Instead of a full-blown filesystem implementation, it is just an object where the keys are the paths and the property content is the file content. Can't be any more naive than that.

I also removed all the CSS stuff and just replaced it with Pico CSS.

LWP showing the information about Moby Dick.

The code became a lot smaller than the previous version and what is written is exactly what the browser runs. There's no JSX, no Svelte templates, no JS features that the browser doesn't understand.

The repository is the actual app that is run, Github Pages is just set to serve from the root of the repo. Any change there is a new deployment, no github actions, no scripts to run.

It is so refreshing.

Press play to it in action.

How does it actually work

It is a simple Mithril application with a router in app.js and three routes. One for the book generation app, one for the documentation, and one for a simple about.

The book generation needs a source folder. You can either drag and drop a folder, or, use the Load manuscript folder button. That will trigger a routine that will read all files in that folder and create a gigantic object with all the paths and contents.

Both the ebook and the website generation functions are massive waterfalls (don't we all think that waterfalls are beautiful?) that make multiple loops over all the files assembling a resulting file tree by copying things around and using handlebars to generate HTML and other files.

Once the resulting file tree is ready, it is added to zip file using JsZip and that file is downloaded from the browser into the downloads folder.

EPUBs are zip files. So both the ebook and the website generation are almost the same waterfall but using different templates.

The output can be configured with a TOML control file that has a ton of features and switches.

Both documentation books for Little Webby Press were generated with Little Webby Press, check out Getting Started and Book Configuration Specification.

The templates used are in a zip file that is loaded when the application loads. They contents of that zip file are placed in the same enourmous object with a /templates/ prefix for the prop name (remember we're mimicking a filesystem).

Because the template is loaded via XHR that means that someone wanting to generate a book and a website can actually use their own template as well by creating a zip with the correct contents and placing it inside the source folder.

Due to not having a build system, you can run this on your machine by simply downloading the files from the repository and using your favourite web server. It is also easy to tweak for your own use in case you need it to work a bit differently in a way that the book configuration and template system can't handle.

Unexpected gains

What was unexpected was the massive gain in performance. The previous Little Webby Press would take about two and half seconds to create the EPUB for Moby Dick, the new version took 125 milliseconds. To assemble both the eBook and website, the old version took 4.7 seconds and the new version takes less than half a second.

This is from the console output of the old version:

Generating site: 4722ms - timer ended

And the new version:

Generating site: 394ms - timer ended

It is so fast right now that I even changed the workflow to autodownload the files while in the previous one there were two buttons, one to generate and one to download cause the process took so long.

I'm not sure why the old version took so long — specially considering the code is basically unchanged— I can only assume that the BrowseFS code is slower cause it is doing a lot more than simply setting a prop on an object.

Moby Dick website generated by Little Webby Press

Going forward, I think I'm gonna focus on no-build webapps only. The most important aspect of a software I'm making and distributing for free is that I need to enjoy working with it and this ticks all my boxes.

Did you enjoyed reading this content? Want to support me?

You can buy me a coffee at ko-fi.

Comments? Questions? Feedback?

You can reach out to me on Bluesky, or Mastodon, Secure Scuttlebutt, or through WebMentions.

Mentions