Those that know me know that I’m a sucker for standards. If there’s a way to do something that works with the platform or web standards, I’m picking that approach. Former coworkers have described me as “the standards guy”. This is why I have always been a big believer in the practice of progressive enhancement. My process for writing web content is to first start with HTML, it is the structure of the page and (mostly) what the user will see! For me, this explains the appeal of the Enhance framework. What does a web framework look like if you start from HTML and progressively add layers on top? Lately, I finally had an opportunity to try Enhance out working on a trivial app and see how well it worked in practice.

Needing to write a simple application for an interview where the instructions were to “use any framework” I figured this would be an excellent opportunity to evaluate Enhance; the team I interviewing with would be in charge of making a review of frameworks and tradeoffs for a greenfield project so at the very least evaluating Enhance together would make for a more fun interview experience than working with NextJS again and talking about React. Generally speaking the app needed (at least) 3 pages: one page to display a random piece of data from an API endpoint, one page to search for data from the API, and a way to click on and view a specific search result. It would also need a header and a footer component for completeness. All of this seemed naturally right within the reach of a framework like Enhance.

Creating pages with Enhance, like many of today’s frameworks is delightfully simple thanks to its file based routing structure. Practically this always made a lot of sense to me, individual files after all are how the computer sees the world and serves whatever you request. At its simplest, this is serving the contents of your /var/www/html/ folder like has been done for years. For Enhance these pages all naturally live in the app/pages/ folder. Add file, get route, what’s not to like?

The next part of Enahnce is where things start to get a little different from other frameworks. Inside the app/elements/ folder lives all of your custom elements. This is a bit of a loaded term so it is important to understand just what these elements are and what they both do and do not do because it’s one of the essential distinctions of Enhance.

When Enhance is talking about elements what it means are custom HTML tags. What’s happening here is that Enhance is taking advantage of the fault-tolerant nature of the HTML language. Whenever you create a new element in the browser the engine will create it as an instance of the HTMLElement by default. You can see this in the developer console with a simple example:

Two lines from the developer console in Firefox. The first shows an element created with document.createElement and the next looks at the of the created element.

Each element gets its own name, just like any other component system, and inside just write whatever you want with plain HTML. Since you are just writing HTML there are no funny gotchas like with JSX (looking at your classNames). Because your components are individual elements you can target your style scoping to each one without the need of reaching for the ShadowDOM or CSS modules. Elements come with a store for reflecting state based on their attributes and, as we’ll come back to later, for consuming data provided to them.

Working with elements in Enhance then is similar to working with a templating system from previous frameworks. Instead of using a templating system like Liquid or Handlebars though everything is done with HTML and and tagged template literals. By using template literals and stringifying your HTML it allows for expansion of variables in the same way that other templating DSL’s have for years. The good news is that today’s editors make working with HTML in this way delightfully simple (even if that might mean a plugin or two) thanks to the number of frameworks that have taken this approach. Just like other templating systems, this makes it very easy to compose your site by creating smaller elements elements that build into bigger elements eventually forming the structure of your entire site. The big tradeoff in this approach? It only works great for static files.

Once you get the structure of your site up, you might find that you need to add some sort of action once a component gets mounted onto the page. In my scenario I had a footer component that I wanted to have hit an API each time it was mounted to display some unique data (think of the banner on NPM.js that displays a different acronym on each page load). Somewhat surprisingly, there’s not an easy way to do this right now with Enhance. There is no built in way for your function components to hook onto the custom elements connectedCallback. This is because Enhance does not automatically register elements in the app/elements/ folder as custom elements. This is a design choice to keep the amount of JavaScript loaded to a bare minimum. Instead, you have to use a slightly different pattern if you want your elements to be upgraded1 and take client side actions.

To interact with files on the client side Enhance also has an app/browser/ folder where you can stick all of your code. To make your elements upgraded components then, you need to redefine your element in a file in this folder and then write a regular custom element. Then at the root of your element inside app/elements/ be sure to add a reference to the bundled script where it lives in the _public/browser folder. The browser folder is not restricted to custom elements, you can add any code you like here and it will be bundled into the site. This along with the @bundles feature of the .arc file allows you to customize what gets bundled and exposed to the site (although you still need to explicitly add bundles to any individual page). If you would like to import other dependencies and use them in your app this is the pattern you are expected to follow.

Last but still very important are Enhance’s API routes or as some have put it, progressive enhancement for the server. API routes are how the data gets into an element’s store, allowing them to be more than static files. When a request is made to a resource that has a matching API route, it first goes through the API route before passing the data into the elements. The Enhance docs actually include a great diagram of the lifecycle that does a great job of explaining exactly what happens on every request.

Enhance's lifecycle diagram of requests. Detailed but not complicated.

This is the sort of diagram you wish came with all frameworks!

Helpfully the routes allow you to return JSON directly which will be passed directly to any elements that exist on the page that match the route2. Enhance includes most of the things that you see in most API routes like dynamic routing and catchall routing. There is a very rudimentary concept of middleware but it has a few open issues about how to improve the experience.

Most web frameworks however start at the opposite end of the spectrum, with JavaScript. There are a lot of reasons that this makes sense since JavasScript is the “hard” part of web development. Unlike HTML and CSS JavaScript as a language is not very fault tolerant! If you get something wrong here it’s easy to bring your website crashing down. Focusing on the hard parts of web development first makes a lot of sense. Unfortunately, it seems that by focusing on the hard part we’ve allowed the focus on JavaScript to consume a lot of focus that could and should be spent working with HTML and CSS3. That attempt to refocus web development on HTML and CSS first and progressively enhance everything is what sets Enhance apart.

It seems clear here that Enhance is trying to strike a balance not loading JavaScript unnecessarily while also not making it too hard to add what you need. You won’t find the words “sprinkle” or “island” in their documentation, but the effect is the same but with an added step of friction. For a framework whose stated goal is making it simple to work with HTML, they appear to want you to think critically about the amount of JavaScript that you are loading. Given the way they make working HTML their gold path, it’s as if the entire framework is designed to make you ask if there’s a way to achieve the effect you want without resorting to JavaScript.

Working with a trivial app it became clear that there were a few things that I wanted Enhance to do that it couldn’t. Redefining my “upgraded” component in the browser folder feels like an anti-patter; it would be great to define elements only once. Apparently, this has been brought up by several folks and changes here are coming to future releases (this may be out of date by the time you read this!). It would be great if the bundler was smart enough to include script tags if I had defined an “upgraded” component, right now adding scripts is a very manual process. Even as a person who wants to be shown the magic manually adding tags to my elements feels a bit tedious. The good thing about the approach Enhance takes is that it strives to reduce necessary client side JavaScript. The bad part is that when I do want to reach for JavaScript on the client, Enhance provides little effort; client side JavaScript appears to be an afterthought. I would love some sort of semi-opinionated way for stores on the client side. The development experience is not suited for an app that must hold a lot of state values or interactivity between components. Ultimately for complex apps, I am probably going to need something there4. Like many open source projects, I think the documentation could probably be improved a bit with some more use cases.

So what to make of Enhance then? There’s a part of me that feels as though I’m actively fighting with the framework but I don’t think that’s accurate. It is more accurate to say that I am battling my own engrained expectations of what a framework should provide and do after spending so much time working in “Big JavaScript”. Largely Enhance stays very true to the goal of making you write progressively “enhance-able” code. I think the basic fact of progressive enhancement is that it requires more deliberate thought about the code you approach and the code you produce.

Enhance is definitely well thought out software that tries to guide you down the correct path for web development that many of us don’t follow like we know we should. For this alone, I think it deserves a lot of praise. I’m not sure I would advocate using it yet as a framework at work given the few missing niceties and also because it’s frankly just a big break from other frameworks. In my interview I described Enhance as feeling like “a really great beta, it just feels like there’s a few things missing from making it ‘perfect’”. Nothing about Enhance has changed my mind about the future of web components (I’m all in). What I expect is that Enhance becomes one of those overnight success stories a decade in the making. It’s built by a small but passionate team and only seems to be getting better and gaining traction. Personally, I intend to keep using it instead of starting from a blank index.html page for my projects, I can’t think of a stronger piece of praise than thinking enough of a project to use it in my own free time.

See also:

  1. I’m going with the word “upgraded” here because that matches the API of CustomElementsRegistry#upgrade but there’s pretty robust debate on what you call the pattern of registering and upgrading custom elements from ‘regular’ HTML to JavaScript web components. 

  2. The way the returns are structured it suggests that there might be other values you could pass back from the API route but nothing is mentioned in the documentation. Future work perhaps? 

  3. There are a lot of reasons for this and I’m not here saying they’re wrong but this could probably be its own very long blog post by someone else! 

  4. One use case I imagined with storing data would be a way to pass my data from one page to the next page client side so that I wasn’t refetching data I just got. For example, if I’m on page items.html displaying a collection of items and I want to click on an item to go to see a single item at $item.html I could just display the item with the data I’ve just received instead of needing to make another request from the item API route handler.