Building Mystery Mansion Madness without a UI Framework
Published: 2022-10-21 12:21:52

---
title: Building Mystery Mansion Madness without a UI Framework
published: true
description: Did the world ever really need React, Angular, etc?
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxbdwtvxptwc0tyelk0u.jpeg
# Use a ratio of 100:42 for best results.
---

Before 2012, all of my websites were made using HTML, CSS and a sprinkling of JS. Then, I went all-in on [AngularJS](https://angularjs.org/), followed by [React](https://reactjs.org/). I started using [Typescript](https://www.typescriptlang.org/) and then [PureScript](https://purescript.org) and learned more frameworks like [Halogen](https://github.com/purescript-halogen/purescript-halogen) and [Concur](https://github.com/purescript-concur/purescript-concur-react). I even wrote my own UI framework called [`purescript-deku`](https://github.com/mikesol/purescript-deku). 

Recently, I had the chance to put together a [PoC for a gaming company I founded](https://mmm.joyride.fm). The PoC's goal is to learn more about social gaming patterns. Time and speed were of the essence: I had to build the game in two weeks and it had to run at 60 fps on most devices.

My first reaction was to reach for PureScript, `deku`, and three.js. This was going fine for a couple days, but I quickly realized that I was hitting a gnarly wall:

1. PureScript's generated code, even though it was heavily optimized, still struggled to run at 60 fps on older devices.
2. A lot of my PureScript code looked & felt imperative, which is clunkier than writing straight-up imperative JS.
3. Chrome's flame charts were basically unreadable, as the functional patterns I was using led to 100-function-deep charts that provided no actionable suggestions or information.

In parallel, [PureFunctor](https://github.com/PureFunctor) had developed a small Vanilla JS engine on top of [three.js](https://threejs.org) with no additional UI frameworks. He showcased it in a single-page app, and the whole thing held together in less than 400 lines of code. I took his initial work and modified it slightly, adding [`tailwind-css`](https://tailwindcss.com) for styling and developed it into [Mystery Mansion Madness](https://mmm.joyride.fm), which in just a few days has been played by 100s of people.

In this article, I'd like to talk about how I un-frameworked and un-compiles-to-JS'd myself for this project, why it felt oh-so-good, and what my learnings are for future projects.

## Is JS that bad after all?

A lot of discussion around JavaScript in the development community (at least those I'm part of) involves images of fire, brimstone, and pain. Phrases like "callback hell" and "painstakingly hand-crafted code" are common currency.

![Ryu fighting code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/czxp53dia1z8zfcyaxnm.jpeg)

A friend of mine that used to work for a large soul-eating consultancy before landing a job at Spotify bemoaned having to work with legacy code-bases held together by jQuery. She's now very happy to be working on modern apps in React and TypeScript.

Given this environment, there aren't many external incentives to try writing anything in JS. But I'm not gonna lie, putting together an application in mostly-vanilla JS was pretty frickin' awesome:

- `eslint` and `htmlhint` caught 95+% of errors that would have crashed my app.
- I could leave my dev server running without drowning in fan noise.
- Compile times are insanely fast.
- People could dip into the project in very little time and be productive almost immediately.
- The time from `git push` to live-in-production was less than 1m.
- Making preview builds & links from my local machine took 75% less time.
- I could optimize things when needed without fear of hidden costs from framework-related cruft.

Perhaps the most refreshing change was that the browser took the place of the compiler in the dialectical back-and-forth I like to have with a machine counterpart as I write code.

I'm used to the PureScript compiler guiding me through the development process, and in general, getting the types to check will lead to a crash-free application. Here, Chrome & the Chrome console wound up filling that role. This meant that my eyes were constantly on the application and I was continuously testing it, so I was more likely to catch UI/UX warts than by interacting with a compiler. For really sensitive stuff or fragile flows, the occasional `puppeteer` test wound up being all I needed.

Furthermore (and maybe this is cuz I'm no spring chicken anymore) I only have so much cognitive capacity to spar with a machine. Vanquishing the PureScript compiler is certainly a dopamine boost, but it usually leaves me in a state of mental fatigue when I then approach the actual application I'm building. As a result, I get into the bad habit of writing copious amounts of code without looking at the UI, which invariably distances me from those who consume what I'm building. Working in Vanilla JS helped me keep my eye on the ([spooky 👻👻👻](https://mmm.joyride.fm)) prize.

## Learnings from PureScript

It might sound here like I'm hating on frameworks & type-safe languages like TypeScript and PureScript, but I'm not. PureScript is by far my favorite language, and if it were as fast as JS, as easy to debug with flame charts, and built into Google Chrome, I would be using it instead for this project. But what I _did_ take from PureScript into JS-land was a collection of patterns. I can't stress enough how important this is.

PureScript & Haskell (but mostly PureScript) taught me how to think, and those patterns are super useful in _any_ application. In no particular order, thanks to PureScript, I found myself:

- Knowing when to curry functions for partial application.
- Knowing when & where to use Applicatives for concurrent programming via Promises.
- Being comfortable passing around functions as values when it made sense.
- Writing code that can be aggressively refactored. Originally, I thought this would not go smoothly because I'm used to letting the PureScript compiler walk me through refactoring. But functional patterns lead to composable chunks, which makes refactoring easier, and it turned out that I had enough of the types in my head (& in the comments!) to know what to change & where, and eslint does a good enough job to fill in any gaps. 
- Thinking in terms of FRP and SDOM architectures, both of which I learned from the ground up in PureScript. In the case of SDOM, PureScript was and still is the bleeding edge of thought - just this week someone released a new SDOM-flavored library called [`purescript-jelly`](https://jelly.yukikurage.net/), and exciting new ideas are emerging from the PS community all the time.

I'm not saying that these things can _only_ be learned from a compiles-to-JS language like PureScript. But different languages express different ideas with different degrees of fluidity, and by working in a language like PureScript, I found myself soaking up these ideas faster, which made them easier to apply outside of PureScript.

So I would recommend to anyone doing webdev to learn PureScript _not only_ because making apps with it is awesome, but also because it will make you a better developer _outside of_ PureScript.

## Wish-list

In spite of these laudatory remarks regarding (mostly) vanilla JS, there were still a few little things I would have liked. Perhaps they exist already, but I couldn't find them. They're all on the order of utilities:

### Elements to vars

(Jewish) Santa, please give me a watcher that looks for JS ids that follow a given pattern (ie that start with `x-`) and generates a file where they're all gotten by ID, ie:

```javascript
export const foo = document.getElementById('x-foo');
export const foo = document.getElementById('x-bar');
```

### Fragment prefixing

If I have a bit of HTML:

```html
<div id="x-my-div">
  <span id="x-my-span">hi</span>
</div>
```

I'd like to be able to get the fragment by id, clone it, _and_ replace ids with a given key, for example:

```javascript
const mySpan = expand(myDiv).using("foo-");
```

Would result in:

```html
<div id="x-foo-my-div">
  <span id="x-foo-my-span">hi</span>
</div>
```

For dynamic content, this is really all you need, and even that is overkill for a lot of cases. For example, in Mystery Mansion Madness, new players pop up with the following snippet of code:

```javascript
const data = doc.data();
let html = "";
for (var i = 1; i < 9; i++) {
  if (data["player" + i + "Name"] && i !== player) {
    html += `<div><span class="bg-zinc-600/80 p-2 text-white">${
            data["player" + i + "Name"]
          } has joined!</span></div>`;
  }
}
document.getElementById(id).innerHTML = html;
```

### A rock-solid, highly configurable router

My SPA doesn't use routing, but if it did, I would have coded my own router. There are some nice options out there for bits and pieces of a router like [`path-to-regexp`](https://www.npmjs.com/package/path-to-regexp), but I haven't found _the_ killer router util yet. But I admittedly haven't looked hard either...

## Conclusion

If you, like me, use React, Angular, Halogen, Typescript, ReScript, PureScript, F#, Clojure, Idris or something else that is either a framework or compiles to JS, I highly recommend that for your next project, you don't use any of these and just write the JavaScript by hand. You may fall (back) in love with it and never use a framework/compiles-to-JS language again.

OTOH, if you _haven't_ ever used a framework or compiles-to-JS language, I recommend trying them out! Especially PureScript.

What will I use for my next project? Idk, maybe I'll write a giant byte array, use it like a Turing machine, and be done with it. Or maybe [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck). But odds are that it'll be vanilla JS if I do something like this again, as I'll just copy and paste this as a template. If anything, I'd like to dig deeper into Tailwind's utility-first philosophy and make a library of small composable webgl utilities so that, for example, I can get a Phong effect without using a full-blown framework like three or babylon.

Until then, enjoy [Mystery Mansion Madness!](https://joyride.fm) and lemme know if you find any bugs. PRs are welcome, provided they're in vanilla JS 😁