3 months ago

JavaScript DX without JavaScript

I’ve been thinking a lot lately about web UIs after seeing that the best UI component kits or design systems are so strongly coupled to the JavaScript ecosystem. From Tailwind to React through CSS-in-JS, with a necessary stop at Storybook, it’s inconceivable to have an excellent experience building web UIs without using JavaScript. HTMX, Rails Hotwire, Phoenix LiveView, which claim that they stay true to the platform, feel like a downgrade when coming from something like React. They are religious about their approach, but they fail to recognize the joy of using React, Svelte, or Vue to enhance the developer experience. I sympathize with their idea of not wanting to bring the complexity of a JavaScript toolchain though. If you want to stay true to the Rails framework while not giving up on the experience that JavaScript tools can bring, you’ll most likely find yourself moving everything to the client as an SPA with a build tool like Vite. Fun fact, Shopify suffered a bit from this—teams built internal services following the SPA model because developers wanted to have access to the React-based design system Polaris. Voilà, you’ve got a new problem: maintaining the JavaScript stack, the REST API (or GraphQL if you want to live at the limit), the client-side state, and the hundreds of issues that might arise from packages in the node_modules directory.

Erlang taught me that with the right modeling of the problem space, you have the potential to eliminate a whole set of problems that emerge at higher layers. Erlang does so with its concept of processes, and Elixir added the layer of programming language that we are used to. What if we need to peel layers, escape JavaScript, and move down levels to model the problem at a different layer?

If we look at the common denominator across all of them, what we find is a function that goes from a domain where we find components like .svelte, .vue, .jsx to .html, .css, and .js that our browsers can read. They all support being hydrated on the client, and some do it through a virtual DOM. We have a compiler. And this is the foundation upon which many of the tools emerged. But so many emerged that using the compiler directly became unthinkable to the point that React doesn’t recommend it anymore. This made the layer upon the compilers another foundation for frameworks to emerge. Their aim? Abstract away the absurd amount of complexity underneath. Some even went further, like Vercel, which built a business upon it. And having so many layers has a cost that depending on who you ask, they say they are willing to pay it. JavaScript developers do; they’ve internalized the brittleness and the frenzies of the ecosystem. They are fine spending days understanding how a deep dependency update resulted in crashes in their apps. Organizations, on the other hand, don’t want this. Still, they don’t want to miss out on the productivity that JavaScript concepts and tools can bring.

Among the ideas that emerged at the JavaScript layer, there was one that I believe played an important role in the state of things. I’m talking about sharing components in NPM packages. Components could be encapsulated in an NPM package, distributed through the NPM registry, and integrated by build tools like Vite, which can be instructed via plugins for how to do so with every available UI technology. This addresses the natural need that developers have for sharing code of their project. Code that represents UI in this case. Do you know why Tailwind is so successful? I believe one of the important factors is that they allowed a way of sharing components, copying and pasting, in this case, without requiring developing your UI at a JavaScript layer. It’s not a surprise that DHH supported it. It was solving something that neither Rails nor other technologies that are not based on JavaScript had solved.

And all of this leads me to the most important question: Does all of this have to be solved at the JavaScript layer? Or have we cargo-culted from UI frameworks like React, normalizing the layers of complexity along the process? I believe more the second.

I think the industry is missing a platform-agnostic and technology-agnostic compiler that can be plugged in as an in-code backend for rendering purposes in any other stack (Rails, Phoenix, Express). The compiler would support features that developers have grown accustomed to, like writing styles in the component and getting them estranged into CSS or having state that changes over time and that the system takes care of mapping to HTML when it changes. We don’t need to reinvent the wheel at a low level when web components exist. They are not there in terms of capabilities, but we can augment the missing bits and bet on the future of them. Moreover, the compiler can support sharing components via packages, making them easier to integrate. For example:

compiler install shadcn

And I can just write the following in my project:

<shadcn.button>
  Click me
</shadcn.button>

No more do you have to have Tailwind CLI, set it up this way, and have React too, and have this config file at the root. What an absurd amount of unnecessary tools. I just want to install a design system and use it.

If you notice, being able to write and share UI opens a lot of exciting opportunities. For example, giving design agencies a standard format to share their work and even charge for it. Instead of having to export it in many formats or just one that the customer uses, you can export it in a universal format that’s easily integratable. Or imagine training AI models with examples of design and having a tool like Vercel’s V0 that’s not coupled to React or Next and doesn’t try to lead you into becoming a customer of anything. Imagine too a Git platform that’s able to visually render the diffs.

I believe this would positively impact the tools built upon it because instead of having to waste time supporting every new UI framework that comes out, they can focus on one and make that experience the same.

Sounds like a giant effort, like Tuist seemed when I started working on it, but incrementally and with a lot of time, we arrived at where we are today. I feel I need to give this a shot in my spare time. I dream of enabling an ecosystem of sharing UI like the one that we developers have the luxury of having access to. The impact that a tool of such nature would have on the tech ecosystem is immeasurable. It would also align the experience of all the web frameworks, regardless of the programming language, and the decision of using one or another would be more nuanced.

I threw a repo and started dumping ideas on it at https://github.com/glossia/noora. I named it after Noora, which is an Arabic name that means light—the light in this ocean of complexity in the JavaScript world. Send me an email at oss@pepicrft.me if the above idea sounds cool and you’d like to contribute.

About Pedro Piñera

I created XcodeProj and Tuist, and co-founded Tuist Cloud. My work is trusted by companies like Adidas, American Express, and Etsy. I enjoy building delightful tools for developers and open-source communities.