Zero framework frameworks and making the web fun again.

ʞooH ɯlǝsu∀
9 min readJan 24, 2020

Making stuff should be fun!

It is my belief that when we work we can sometimes get caught up in producing a result and kind of forget about the process. We pride ourselves on being able to “tough it out” or “grind through it”. And there’s a lot to be said for being able to grind through things, but there’s also a lot to be said for arranging work so that it isn’t a grind.

If you are stressed fighting your code, or having to hold a lot of things in your working memory, it can undermine a longer term sustainable work pattern, making your body not want to build apps again. Being able to do a whole sketch in an afternoon is important; having a good sense of progress, getting rewarded for work, these can be positive longer-term self-reinforcement cycles.

After a decade dealing with heavy ideas like Facebook React, or collapsing empires like parse.com, or the rather strange introduction of having to actually compile a web page, or soul-crushing labyrinthine arbitrary perverse mini-grammars masquerading as standards, suddenly it seems that making web apps is becoming fun again.

Building apps on the web is becoming fast, immediate, with no pre-compiling, with fewer dependencies, fewer weird frameworks and fewer idiosyncratic patterns. The tools we do choose, like mapbox.com, seem robust and stable now. Back ends are becoming beefier, real time, and there are third party services that can carry the weight of any idea you want to try. I’m constantly surprised by how lately things “just work”.

Pleasing patterns

If you’re a modern developer these are largely things you’ve observed, in which case this is a campfire story, fun but already known. But if you’ve stepped away from the web perhaps this will encourage you to come back and play:

  1. Javascript ES6 becoming readily available has been huge. Being able to just import a file is such a nice feeling. Being able to declare tiny anonymous arrow functions is also a delight. Promises as well have become heavily used. These are all small improvements but make the whole experience of describing work succinct and pleasurable. Multiline quotes has been critical. Better namespace scoping has also been critical.
  2. WebComponents. React has both functional components and Class Components — which are reasonable ways to express a bit of layout. But I prefer having nothing at all — using only built in features. WebComponents do lack some event handling capabilities — I have found myself trying to listen to MutationObservers to circumvent this. As well, separately the shadow DOM can be a bit tricky. But overall at least you can package up a widget as a single file that includes layout, styling and code in one blob that can move between applications and frameworks.
  3. MobX is a third party framework for responding to state change, so perhaps it breaks the aspiration of a zero framework goal. React is often paired with Redux and it brings along ideas of reducers and defining what the events are. What I find with Redux is that one tends to want to “design ahead of time” and this requires more thinking for me than MobX. That said, the React/Redux pattern can be done in an incredibly elegant and terse way that separates concerns.
  4. Google Firebase provides fairly seamless back end persistence with real time updates. That means that for lightweight game interaction (messaging, the movement of characters on a map and so on) you don’t need to setup any kind of custom high performance websocket. In previous apps quite a bit of energy was spent first writing a client side state wrapper — and then having that state wrapper talk to server side persistence. I wanted client side state replication for speed; and synchronization of all this, especially in a multiple client situation was a big hassle. Applications are mostly state, so being able to use off the shelf pieces here is a big deal. I will say however I tend to not directly work with raw database objects on the client side but rather with composites that are the results of queries. And I’ve found in practice that state synchronization isn’t that much of a hassle to maintain by hand between distributed client instances — so the older Google product ‘datastore’ is often fine as well. This speaks to the ‘API wall’ point below as well.
  5. KISS philosophy on managing state. I tend to avoid anything more complex than vanilla javascript hashes. POJO or Plain Old Javascript Objects are an eminently reasonable way to manage client side state. It’s true that we can think of state as “classes of behavior”, and we can wrap up that class into some kind of class abstraction with methods… but that gets heavy and requires pre-planning on how to partition your universe. Also, inhaling and exhaling assets requires transmogrifying them into class objects — might as well just stick with hashes as they are. This also speaks to the ‘API wall’ point below.
  6. Simple API wall — a library of methods that pass vanilla hashes in and out. I’ll discuss this in more detail below.
  7. Rich clients. Nowadays all my apps are single page apps. The server simply delivers the app to the client once, and then all future interactions are pure queries of an API wall. The server simply services database transactions (mediated by the security of an API wall). Much simpler than building pages on the server with a layout grammar.
  8. Routing. I do tend to write my own in browser routing — I will discuss that at the bottom.

Pulling the pieces together

Here’s one trivial component I wrote recently that pulls several of the above patterns together:

import {Services} from ‘./services.js’

I like to separate concerns. Here I bring in an API wall whose job it is to “gives me what I want” so that elsewhere I just focus on what I want to do with those objects. It can go to the server, or have its own cache — but generally I like to just have state all behind one wall and I don’t care here how it solves things.

class ThriveHeader extends HTMLElement {

Here I start out a new WebComponent — this is obviously fairly routine.

constructor() {}

connectedCallback() {
mobx.autorun( this.repaint.bind(this) )
Services.UserCurrent(true)
}

Then, I tend to put my startup logic in the connectedCallback() rather than in the constructor() for a couple of small reasons. State is more fully settled by that time, and it’s a good time to tell MobX to watch for any state being tickled and to remake the display. Basically, once client logs in it updates the header to say “logged in” versus “please log in”. And then I kick the system forward by asking for the current user (which will eventually trigger MobX).

repaint() {
let loggedin = Services.user && Services.user.uuid ? true : false

The repaint itself simply wants to know what to paint — and by asking for Services.user then MobX knows my general intent (that I want this to be repainted if the user changes).

this.style.display = “contents”

Here I force a style on the HTMLElement itself. It is possible to have the DOM skip the parent but I just prefer to force the style. And I don’t bother using a shadow DOM here.

this.innerHTML =
`<header style=”display:flex;padding:2%”>
<span style=” margin-left: auto; line-height:40px;vertical-align:bottom;”>
${loggedin ?
`<a href=”#thrive-profile”>${about}</a> <a href=”#thrive-signout”>Logout</a>` :
`<a href=”#thrive-signup”>Signup</a> | <a href=”#thrive-signin”>Login</a>`
}
</span>
</header>`
}

Here I directly paint the component — and I didn’t have to be super elegant or thoughtful; I just have a blob of some HTML — that is “good enough” to sketch out the idea I had — without a lot of thinking.

}

customElements.define(‘thrive-header’, ThriveHeader )

API Walls

We’ve been taught that classes and class abstractions — where a class has methods that encapsulate and act on state — are a useful pattern. Many of us try to define rich classes, class hierarchies, and then manipulate objects by calling class methods. This training can be hard to undo.

Behaviors associated with a given concept are somewhat cross-cutting; the behaviors cut across classes and often developers end up trying patterns like mix-ins to capture and organize the complexity here. It turns out that class abstractions may simply not be a useful mental model for organizing behavior. It’s true that an Orc and a Treasure Chest might both ostensibly be versions of “game objects” but any similar behavior can just as easily be a component that is tacked onto the instance later on, rather than in some kind of abstract definition.

If you look at the best of breed ECSY project you’ll see a modern take on how to associate behavior with objects. This is likely the right direction for the web and will empower a generation of developers to share rich behaviors in modular ways in a way similar to the Unity3D community.

But even then, it turns out that in practice there’s a difference between a server side requirement and a client side requirement. A server side may be focused on tracking all the discrete objects that make up an experience, but a client side may simply want to paint some composite of those objects to the display. The client is in fact more presentation focused. The server can search multiple tables, join things as needed, and deliver a rich object to the client that exactly summarizes what is desired. Formal object semantics (on the client side) can be overkill.

Historically the client side pattern that works for me is to have a single “wall” of methods that produce whatever you want on demand (often but not always by talking to a server). I have a bucket of global static methods that resolve the highest level goals of my application — and then under the hood they can do whatever magic they need to do. I also like to combine this with MobX so I can observe state changes easily. Below is a simple example of managing user login:

let Services = mobx.observable({
user: 0,
async Query(args={}) {},
async UserCurrent(reload=false) {},
async UserSignin(args) {},
async UserSignup(args) {},
async UserSignout() {},
async UserUpdate(args) {},
async UserDelete() {},
async Stats() {}
})

export { Services }

Routing

Web designers and developers often break up the user experience into pages. If a user asks for a login, then bit of routing logic finds the login page and serves it to the user.

In traditional server side apps a page is usually described in a scripting language such as ejs, and then produced as a view on the client as html. Today developers often write single page apps where all the layout is already on the client but otherwise things have not changed much.

However routers themselves tend to be fairly static with brittle matching rules, and routing can become cumbersome. I prefer to roll my own routing.

Stepping back a bit, in my mind an app consists of 3 kinds of graphs:

  1. Objects → whatever represents the concepts that humans interact with. In THREEJS it could be your “scene” — which may have cameras, lights, meshes and so on. In a 2d app this could be web pages, calendar events, or cards of information. These are often related to each other in an arrangement of parents and children.
  2. Event relationships → the wiring between objects. For example an upvote button might be wired to some logic that does some database checks and repaints a display. Or a button could be wired to a fresh page transition.
  3. Object kinds → Often developers arrange objects into class hierarchies. For example a game may have an “animal” page that can show a view of a “cat” or a “dog”. I do prefer to use prototype based categorization but generally speaking there’s often a pattern of prototypical objects and then either instances of those objects or variations of the prototype.

I tend to see the router as having to interact with all three of these concepts. A router is driven by an event. There may be a given object, and that object may have an associated view, or base class view. A router can be very small, and by writing my own I have more flexibility on how I wish to handle page transitions.

For simple apps I also like to embed the router in the HTML itself to help make the routing more transparent and legible to other readers. The index page for an app can therefore become a kind of table of contents for the experience:

<html>
<body>
<script type=module>
import ‘/js/my-router.js’
import ‘/js/my-first-page.js’
import ‘/js/my-second-page.js’
</script>
<my-router>
<my-first-page/>
<my-second-page/>
</my-router>
</body>
</html>

Concluding thoughts

What makes work fun for you? For me programming is an enjoyable quiet contemplative act; perhaps similar to playing the piano or knitting. I want to be able to put something down for a month, and come back to it later and easily pick it up again. It’s especially nice when the sketch or initial framework I have built is one that others can contribute to easily (even if that ‘new person’ is myself later on in time). Often I’m working with novices or designers who have their own ideas, and empowering them to go in and make changes themselves can give them a sense of agency. In sum, to keep work fun I tend to avoid some of the power tools, complicated process and structure and lean towards being more playful in my work.

--

--

ʞooH ɯlǝsu∀

SFO Hacker Dad Artist Canuck @mozilla formerly at @parcinc @meedan @makerlab