Compiled Atomic JavaScript?

This ad is not shown to multipass and full ticket holders
JSNation US
JSNation US 2025
November 17 - 20, 2025
New York, US & Online
See JS stars in the US biggest planetarium
Learn More
In partnership with Focus Reactive
Upcoming event
JSNation US 2025
JSNation US 2025
November 17 - 20, 2025. New York, US & Online
Learn more
Bookmark
GithubProject website
Rate this content

Can we apply the lessons of CSS to JavaScript?

While working on StyleX we use compilation and the technique Atomic CSS to create a more efficient system. Having small atomic CSS rules maximizes re-usability and dramatically reduces the size of the CSS at scale. A powerful compiler, makes the process automatic and frees from having to think custom DSLs. 

Can we do the same for JavaScript? What would the trade-offs be? Can it result in unique benefits not possible with most other approaches?

This talk has been presented at React Summit 2025, check out the latest edition of this React Conference.

FAQ

StyleX is a CSS and JS styling solution that operates at compile time, producing a highly optimized atomic stylesheet without runtime style injection. It is similar to Tailwind but considered by its creator, Naman, as one of the best styling solutions available.

Solenoid is a server-first atomic JavaScript framework created by Naman. It uses JSX on the server, with components and signals defined on the server but implemented in HTML, running in the browser without the need for hydration.

In Solenoid, you write code similar to React or SolidJS. The framework generates HTML that includes custom elements and functions, allowing for interactivity even as HTML streams in. This approach avoids the need for hydration and reduces double-data problems.

Solenoid is still in development and not feature complete. It generates larger HTML files, breaks some CSS selectors, and uses custom elements, which may not be the best implementation detail. Despite these trade-offs, it offers benefits like no hydration errors and interactive HTML streaming.

In Solenoid, components must be defined using function declarations, and all internal functions within a component must be arrow functions. This allows the compiler to properly handle and optimize the code.

Solenoid shares some similarities with Quick.js, particularly in its server-side rendering approach and use of HTML and JavaScript streams, but its implementation details differ significantly.

The main advantage of Solenoid is that it eliminates the need for hydration by making HTML the source of truth, thus avoiding hydration errors and making interactivity possible as soon as content streams in.

Solenoid was inspired by the idea of combining server-side rendering with atomic JavaScript principles, aiming to explore whether a new framework could offer unique benefits in terms of interactivity and performance.

Atomic CSS is beneficial because it scales better by reducing the size of CSS as the number of components increases. It allows for a small bundle of CSS to be loaded upfront, eliminating the need for lazy loading, which can otherwise become a performance bottleneck.

Naman Goel
Naman Goel
22 min
13 Jun, 2025

Comments

Sign in or register to post your comment.
Video Summary and Transcription
In 2008, the movie Vantage Point inspired the exploration of diverse perspectives in software development. The evolution from traditional CSS to atomic styles in StyleX and the scalability advantage of atomic JavaScript are significant areas of interest. Rethinking server-side rendering with React, Web Components, and the Hano framework introduces new possibilities for interactive components. Custom elements, Shadow DOM, and the Solenoid framework address challenges in CSS scoping and SSR for lighter-weight HTML. Signal functions in Solenoid offer a unique approach to data management and component development, enhancing app efficiency. Real-time interactive server setup, innovative server-side development, and the use of HTML as a source of truth contribute to project speed and efficiency. Debugging, component definition, HTML streaming, and component usage highlight the declarative nature and streaming capabilities of server-generated HTML.
Available in Español: ¿JavaScript Atómico Compilado?

1. Exploring StyleX and Atomic JavaScript

Short description:

In 2008, the movie Vantage Point showcased diverse perspectives. StyleX, a compile-time CSS and JS solution, offers optimized atomic styles. Atomic CSS's scalability advantage over traditional CSS is significant. Can atomic JavaScript replicate this success in the JavaScript realm?

Back in 2008, there was a movie called Vantage Point. It was a story told from many different perspectives, and you see how different perspectives can be very different from each other and tell you different stories. But then when you put them all together, something new might emerge. Anyway, I'm Naman, I worked at Meta for eight years. And for the last four to five years, I was working on StyleX. So what is StyleX? It's a CSS and JS styling solution, but it's compile time, so there's no style injection at runtime. It produces a highly optimized atomic stylesheet, similar to Tailwind. I would argue it's one of the best styling solutions out there, a lot better than Tailwind. There are some other ones that people don't know about, but that's neither here nor there. Regardless, the important point is, atomic CSS as a concept is winning. But it wasn't always like this. There was a long time in web development where lazy-loaded bundles were ubiquitous. We would have route-specific bundles of CSS, we would lazy load them as we navigated around pages, we would use syntax extensions like Sassless, and we would use architectures like BEM and OOCSS, which was actually just something we had to learn, and there was no tooling for it. But then atomic CSS came along and completely flipped the equation. And the reason that it was so successful is that it just scales better. Instead of your CSS growing with the number of components, where you just get bigger and bigger and bigger CSS, with atomic CSS, after a while, your CSS just stops growing. And as a result, you can have one small bundle of CSS loaded all up front. You don't need to lazy load any more CSS. Your HTML gets a little bit bigger, sure. You have more class names. But overall, a larger HTML is still way cheaper than a larger CSS file. And at scale, you realize how a large CSS file can become a performance bottleneck, which you otherwise don't notice. So I started thinking, can we do atomic JavaScript too and flip the equation in the JavaScript world?

2. Rethinking Server-Side React and Web Components

Short description:

I've used React for 12 years, exploring server-side rendering to recent server-first components. Can server components be interactive? HTMX simplicity appeals, but lacks declarative structure and component consistency. Delving into JSX, Hano framework, and Web Components' Shadow DOM for style isolation.

I've been using React for the last 12 years, and I started using it for server-side rendering. I went through React Create class, ES6 class components, function components with higher order components, Redux, and then Hooks. But then, this big change happened recently, called RSCs. And RSCs kind of flipped the model of React, where now everything is server-first. The data is server-first. We started thinking, why ship components to the client if the output is always going to be static? We can have a smaller client bundle if a lot of our UI can be represented with data or markup in this situation. We can still have client components for interactivity.

So I started thinking, can server components themselves be interactive? Hi, I'm Naman, and I'm not a fan of HTMX. But there are some things that are still kind of interesting to me about it. So this is a very simple HTMX demo, and what I like about it is that the logic of your application is right there in the HTML. There's no extra JavaScript to load, no extra bundles. It's all boiled down to something very simple, very efficient, in terms of the data that has to be loaded in the browser. But also, HTMX has imperative logic everywhere. You're updating things by IDs and inner HTML and outer HTML. There is no declarative system to keep track of everything and no components, no consistent data flow.

But then also, on the flip side, JSX can be used without React. And there's a server framework called Hano. It's a competitor to Express, which has first-class support for JSX, but just for rendering regular HTML. And so I started thinking, could I make a declarative component model that generates something like HTMX? Hi, I'm Naman, and I'm not a big fan of Web Components. But I've been digging into the APIs, looking for anything of value in that whole thing that we've built and put into the browsers. So I started looking at what are Web Components, what makes up Web Components. So the first thing that everybody talks about, because it's the most controversial part, is Shadow DOM. Shadow DOM gives you style isolation. That is its big value. Any CSS outside of the Shadow DOM doesn't apply to the markup inside the Shadow DOM, and any CSS inside the Shadow DOM doesn't affect anything outside of it. But we have Atomic CSS now. We don't have conflicts. We don't need this scoping of CSS in most cases. It really makes SSR hard, or sometimes impossible, if you have to support older browsers.

3. Exploring Custom Elements and Solenoid Framework

Short description:

CSS scoping and SSR challenges. Shadow DOM for specific cases. Template tags and custom elements with lifecycle events. Using custom elements for lighter-weight HTML and introducing Solenoid, a server-first atomic JavaScript framework.

We don't need this scoping of CSS in most cases. And it really makes SSR hard, or sometimes impossible, if you have to support older browsers. So my conclusion was, it's only really useful for some special cases. Like if you're trying to build your own CodePen-like demo, you might need something like Shadow DOM to encapsulate arbitrary styles. But for my use cases, let's burn it on fire. I don't want it.

Next, there's template tags. They're just strings. They're just strings that happen to live in HTML. I don't hate them, but they're also not all that exciting. Finally, there's custom elements. Custom elements, they're just any elements where you put a hyphen in the name. But they do have something of value, which is that they give you lifecycle events for mounting, unmounting, and updates. Without custom elements, there's actually no events in HTML to do that.

You have to use mutation observers and track when HTML is being loaded, which also doesn't tell you when something is unloaded. So it makes that part a little bit easier. So there might be something of value here. Using that, I think it should be possible to put some logic, some programming in HTML itself, turn this mock-up language into a programming language. So I started thinking, could custom elements be used to create a lighter-weight HTML or sorts? If you're looking at all the perspectives, you might be putting it all together, figuring it out, or you might be really, really confused. What the hell am I talking about? Just wait. I'll explain it. It'll make sense.

Yes, I built a new framework. Introducing Solenoid. It's like SolidJS, but with extra steps. It's a server-first atomic JavaScript framework. It is JSX on the server. The components are on the server. The signals are defined on the server, but then they are implemented in HTML. And then everything runs in the browser.

4. Understanding Signal Functions in Solenoid

Short description:

High-level overview of writing React-y and SolidJS-y code. Custom element for global signal definition. Signal functions instead of data, using custom elements for data updates, and Solenoid components resembling SolidJS with alien signals.

So how does it work? To give you a high-level overview, you write code like this, which is very React-y, SolidJS-y, and the framework generates something that looks like this. This is a custom element that defines the signal globally in the HTML for you so that other HTML can use it. But you don't have to write this gross, custom-element web component code. You can write code like this, which is, again, very SolidJS-like or React-like, except instead of state, you have signal, so they're functions. Everything is functions instead of data.

And then the framework generates something that looks like this, which again uses a custom element that tells it which signal to look up and bind to and subscribe to so the data can be updated. So do you want to see a demo? All right. I have to switch. So before getting any further, this is an HTML file. It's an output. The framework is still very much under construction. So it kind of works. It's a counter component. We know what that looks like. But let me show you what it took to build it.

So the counter component looks very similar to any Solid... Bigger. Bigger. Is that better? All right. Get lost. Okay. So a component in Solenoid looks like SolidJS. The signal API is slightly different, because it's using alien signals, where the signal is a function that is both a reader and a writer. So if you read it without... If you call it without arguments, it's a read. If you call it with arguments, it's a write. Other than that, it's just like SolidJS. There's no special syntax. All arrow functions are compiled in a certain way. All non-arrow functions are treated as components.

5. Efficient Compilation Process in Solenoid

Short description:

Generating code with a small runtime, atomic JavaScript functions, signal definition, and component compilation for efficient app development without server processing.

So you write something like this. And then it generates code that looks like this. So in the head section, there's a small runtime that's loaded in all apps. The runtime is currently nine kilobytes. It is currently lacking a couple of features, so there's no context or suspense yet. So at most, it should be 20 kilobytes. And other than that, there should be no additional framework JavaScript. Other than that, the only JavaScript that ever ends up in the browser is specific arrow functions, which are compiled to hoist out the closure. I'll show you how that works in a second.

And again, I've made it a little simple, just to see. In a real production app, this would be an import statement, and functions would be deduplicated. So the important part here is I said atomic JavaScript a while ago. These functions will be hashed. So if you use the same logic in multiple places, even with different data, it'll hash to the same ID and it'll be reused across multiple parts of your app, just like atomic CSS. And then we define a signal, which is a count. We define some buttons that update that count. We define a signal text that uses that count. And then there's even ifs already implemented. Loops are in progress at the moment. And that's what gives you this working code. And it's literally that HTML file. There's no extra magic here. It's not even running through a server at the moment.

To show you the compilation step of this, I'm going back to the counter. So that counter gets compiled into something that looks like this. So this part is mostly JSX, so that's not super interesting. What is more interesting is all the functions that you write, every single arrow function that you write in your component, might be using some data that is a parameter or an argument, but it might be using other data that just was around in the closure. So the compiler detects everything that was used from the closure, turns it into another argument called closure, and makes it a standalone function that can be serialized and sent to the server, which is how this works. Ready to show you, okay, that's running there. So when you run that component through the render function on the server, it generates a stream that looks kind of like this.

6. Real-Time Interactive Server Setup

Short description:

Generating functions, signals, and interactive streaming without hydration. Special server setup for real-time interactivity before Contentful Paint. Acknowledgment to a co-worker for framework assistance and discussion of trade-offs in the project.

So it's generating script tags that define functions, it's generating let signal that defines the signal, and it defines all of the HTML that I showed you in the HTML file anyway.

One final demo, which is over here, and I will come back to why this works, but one of the big value propositions of why I started building this is that there is no hydration. It is interactive while streaming in. So this is a special server that I've created that streams down the same HTML file one line at a time, and there's a one-second delay between each line. So I just want to show you how that works. So you see nothing for a while, then you see the count. It's already interactive while the HTML is still streaming in, and when the double count comes, it's already updating as well, and then when the final thing comes in, it's already interactive as well. So there's no delay for interactivity. There's no time to interactive. Interactivity happens before Contentful Paint, essentially.

Going back to extended display, going back to keynote. So I want to give a special thanks to Vikram Rangarajan. He was my co-worker at Meta a few years ago. He helped with a lot of the framework. I would not have been able to build all of this in time for the conference, so I just wanted to do that. Right after that, there's a question of, is this whole thing a really bad idea? So there are some pretty major trade-offs and unknowns. So firstly, the whole thing just barely works at the moment.

7. Innovative Server-Side Development

Short description:

New concept development, server-side features, challenges with CSS selectors, and the uniqueness of using Atomic CSS. Potential speed benefits and considerations of custom elements in the project.

It's very, very new. This was just some crazy idea I had about a year ago, and I've been thinking about it for a while, and I decided to build it for the conference. It's far from feature complete, although I have an idea of how every single feature that we want in a real app would work. Suspense would work. Data fetching in components is actually already implemented, but suspense isn't, so I couldn't demo that.

So just async components are a first-class feature because it's all server-side. It breaks many CSS selectors. As you notice in the HTML, there's a whole bunch of extra tags, extra elements. So if you do any direct child selectors or direct sibling selectors, those are not reliable. So I'm assuming you will use Atomic CSS. There is a way to work around it. I could also write a PostCSS plugin, which would make all of those work.

But I actually think it's better to use Atomic CSS and not rely on those selectors. It generates a bigger HTML file. I assume that's faster because that's been faster in other domains that I worked in, but this is new, so I don't know if it actually is faster. Custom elements may not be the best implementation detail. And there are actually some other similar solutions, which I'll get to in a second. But also, it might be a good solution because there's no double-data problem.

QnA

HTML Source of Truth

Short description:

Elimination of double-data issue in modern frameworks by integrating all content in HTML. Discussion on the motivation behind creating a new framework and its unique qualities.

But also, it might be a good solution because there's no double-data problem. So every modern framework today, when you server render, renders the HTML. Then it sends all of the data payload that you used for the server rendering and then sends all the components that were used to convert that data into this markup. And then it hydrates on the client, and then it just repeats everything twice. So you're sending all of the HTML twice, once as the HTML, once as the component. And you're sending all the data twice, once as the HTML and once as data. Here everything is just in the HTML and there's no duplication whatsoever. There are no hydration, there's no hydration, so everything is interactive. Which means there's no hydration errors. The HTML is the source of truth. Hi, I'm Naman, I accidentally created a quick JS clone. Thank you.

First of all, I want to ask a question for me, which is classic, like, oh I have a problem to solve, let's build a framework. And this kind of, it will lead into some of the other questions, but first of all, at what point did you go, okay, framework is time. At what point did the kind of problem you were trying to solve hit a point at which you're like, moving into a, building something new is the strategy? So I would say it didn't go in that way. I wasn't trying to solve a problem. This was an idea. And it was just, like, honestly, I don't know if this framework is a good idea or a bad idea as my conference ended. I know it has some interesting qualities about it. It's different from how everything works today. Eventually it may be good or bad, but I only built it because I was curious if it was possible. And that was like my journey of like, I had all these thoughts. I was watching Ryan Carnazzo's stream on YouTube, who's the creator of Solid.js. So I've just been seeing things in this industry, I've been getting all these ideas. I've been seeing people complain about things. And I was like, this idea formed over the last one year. I was like, let me see if it's possible. And turns out it is possible. So we'll see. That makes sense.

Debugging and Component Definition

Short description:

Discussion on debugging in the framework, highlighting the benefits of declarative nature for easier debugging. Clarification on the significance of function shape and the restriction on using arrow functions to define components.

That makes sense. All right, we've got some questions coming in. Thank you, and keep them coming. The first one is how do you perform debugging in your framework? There's lots of different dev tools that people can use built into the frameworks they have. So how would your approach debugging, and maybe it's something you're going to solve in the future? Yeah, so firstly, they are fully honestly, I haven't thought about it. Barely got the demo working. I got the last streaming demo working two hours ago. So it's not like very done or anything. But just thinking about it, since everything is so declarative, since the entire data flow is represented in HTML, I assume that debugging would be a lot easier, especially for performance. Because I don't know if people are familiar with all the tooling that David Cappiano of what do you call it? Next state. Next state. Yes. Fame is known for he builds really cool visualizations. To build cool visualizations, you need like static data that you can consume. Usually with code, that's not easy because you have code and you at best you get ASTs. But in here, you have the entire data stream, like the entire tree of data dependencies represented in your HTML. So there's a whole bunch of tooling possible where you could just be like, look at the HTML generator graph, you can see how well it's performing. And you can see which specific elements are slow and which elements are fast.

Oh, no, I love that. That's a very, very detailed answer. And the next one is someone's asking a clarification question, which is, did they understand correctly that the function shape is meaningful? So can't use arrow functions to define a component? Yes, that is that is currently the decision on how the compiler works. So Quick.js, which is very similar in many ways. They made some like the implementation is completely different. It was after I had built like 80 percent of it that it was pointed out to me that it's actually somewhat similar to Quick.js. They use a lot of wrapper functions and dollar signs to indicate what gets compiled and what doesn't get compiled. And I decided that I needed everything inside the component compiled anyway. So I'm saying components must be function declarations and everything inside the component must be arrow functions and everything will work fine.

That makes sense. We've got another one. This one's from Mikhail Vandaros Heinemann.

HTML Streaming and Component Usage

Short description:

Explanation on streaming HTML and JavaScript to the browser, clarifying the combination of both in the server-generated HTML. Details on using components in other files without loading all compiled atomic JavaScript each time.

If the front end logic changes nested DOM, nested DOM instead of text and the notation is JSX, are you streaming HTML or JavaScript to the browser? It's a combination of both. So again, in my HTML demo, yes, it was a very minimal demo. Hopefully it made some sense, but it's mostly just HTML streaming. Everything gets converted to HTML on the server, but the HTML contains little bits of JavaScript, which are the things that cannot be represented in HTML, which is again, event handlers and data transformations and that's it.

That makes sense. All right, we've got time for one more. How can you use the components in other files? Is all the compiled atomic JavaScript loaded every time we use them? No, only whatever is actually rendered gets streamed to the client. There's actually no bundler. You don't bundle your JavaScript to send to the client. You write server code and the server runtime generates HTML at runtime and that's it.

That's awesome. I'm sure there are many people who want some clarification, want to answer some more questions. They can find you over in the speakers Q&A spot. Thank you so much. We're going to have a short break. Remember, if you are taking the headphones off, leave them on your chair and let's give them a round of applause.