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:
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
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.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.
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.
Enhance.dev Developer Docs – Enhance’s developer documentation
- Enhance Movies – Example website put together by the Enhance team. I leaned heavily on their work here to see how they would do things.
- MDN Guide: Using Custom Elements – MDN’s guide to working with custom elements. Relevant for working with Enhance.
I’m going with the word “upgraded” here because that matches the API of
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? ↩
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! ↩
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. ↩