W3CSVG

Getting high on Component-Based Deployments (CBD)

Disclaimer: This article talks about React to examplify the concepts. It doesn’t mean that this couldn’t be done with any other framework.

Today most CI systems for React applications looks something like this: Push code, merge code, install dependencies, run tests, create container, install dependencies again, deploy container to fresh nodes, tear down old nodes. etc.

But what if we could skip all these steps? What if we could just deploy a React component directly? Without building a container, deploying it or tearing down anything? What if instead of having a container registry there was some kind of component registry?

Let’s recap why we containerize our applications in the first place: The core arguments for containers is that they isolate a single application and its dependencies. But what if we could do that with components? What if we could achieve portability and virtualization at the javascript runtime level?

A simple example

Let’s explore this idea with a simple example.

import { Button } from "./components";

export default function Page() {
  return (
    <div>
      <Button>Hello World</Button>
    </div>
  );
}

Here we’re importing and displaying a button which is a React component. But what if we want to change that button. Why do we have to go through the ordeal of building and deploying a new container? Wouldn’t it be nice, if we could rebuild the button, push it to a component registry and tell the application to start using the new button. How about the component is imported from a component registry rather than a local file?

const registry = componentRegistry("http://cbd.design:4200");

export default function Page() {
  const Button = registry.get("Button");
  return (
    <div>
      <Button>Hello World</Button>
    </div>
  );
}

A more realistic example

Imagine a landing page that is made up from sections. Or, really take any website made by a typical page builder, like Squarespace. Each section is a “building block” and can be implemented as a React component. Rather than replacing basic UI components like a button, it would make more sense to replace components at the page level. The idea of Component-Based Deployments is that these sections can be developed and deployed independently without the need to redeploy the applications itself. This is possible because components are not coupled to each other.

Towards Component-Based Deployment

What this means is that, essentially the applications becomes the runtime and we deploy to that runtime, like we deploy a lambda function to an AWS runtime. Think of the following analogy:

Artifact Registry Runtime
Docker Container Container Registry Docker Deamon
Lambda Lambda S31 AWS Lambda Runtime
CBD Component Component Registry Node

All of these virtualization environments have an artifact, a place where artifacts are stored and a runtime for the artifact. Component-Based Deployments (or CBD for short) is simply changing the frame of reference.

Now, what’s stopping us from doing this? This seems like a logical thing to do…

Well, there are many reasons why this initially sounds like a bad idea. Some questions might be:

  • First, how do we deal with dependencies? Aren’t Docker containers completely isolated whereas React components are usually not at all?

  • Second, how is that compatible with existing CI pipelines? How do we test a component and more importantly how do we test a component within its context?

  • Third, how would that work with a browser? and bundlers? and code splitting? or CSS? Also, isn’t that just like running HMR (Hot-module replacement) in production?

The answer is: We deal with these questions using the same methods that we use today. We just adopt them to this new paradigm. So lets see how we can apply these methods to Component-Based Deployments.

Publishing workflow

Consider the following workflow, assuming a hypothetical command line tool cbd:

cbd build component/button
cbd push component/button

which would build a single component and push it to a component registry (notice the similarity with Git, Docker or NPM). This takes care of isolating the component and transpiling it if necessary. We then simply push it to the component registry.

Component registry

At minimum a component registry would need to fullfil the basic requirements of an artifact store, like publishing, storing and retrieving components. But component registries could have many specialized features that are tailored to UI components, like:

  • Storybook-like UI for viewing components
  • Versioning of components (which would allow rolling back a single component rather than a whole application)
  • QA environments for components (which would allow to test components in isolation)
  • Documentation

What if components could be associated with content types? or implement standard interfaces? For example a component registry could have 3 different components that are able to represent a content type “video” (as a thumbnail, as a lede, as a video player, etc.)

The following concept shows an example how a component registry could look like:

Testing workflow

Further, consider the following workflow for testing a component:

cbd lint component/button
cbd test component/button

This deals with everything we do today: Linting, unit testing, but also integration testing. Once all tests succeeded we can be confident that our components will behave as expected given certain inputs.

Consuming components

While building, publishing and testing seems relatively straightforward, the questions of consuming is really at the heart of the problem. If we start treating Node like a container runtime, we also need to make sure it fullfills the requirements of a stable runtime environment. We need to have trust that pulling a new component into the runtime won’t break anything, the same way we have trust, that pushing code to AWS Lambda will run reliably or spinning up a Docker container won’t randomly crash the Docker deamon.

The problem that needs to be solved

What we need is a super stable runtime environment that can

  • … verify component are authentic
  • … handle broken components
  • … deal with versioning
  • … hot-swap components
  • … roll-back components
  • … “not crash” and prevent memory leaks

Also, we need a way to get components into the runtime by either:

  • … using a well-protected API
  • … subscribing the runtime to an event stream

Additionally there are broadly two use cases: Server-side rendering and client-side rendering. While server-side rendering has more flexibility, client side rendering has more constraints. Ideally we only want to push code to the client that is:

  • compiled and minified
  • code-split into chunks
  • inlined for critical rendering above the fold
  • lazy-loadable

A lot of this is handeled by build tools. And those build tools would have to change to support this new paradigm. However, “no-bundle” native ESM modules, which most modern browsers support, and HTTP/2 could potentially help to solve these problem.

Now the difficult part is that in a lot of cases components are not entirely isolated from the rest of the application. And this is where the real problems lie. We need a runtime that:

  • … can deal with side effects
  • … can deal with global state
  • … can deal with global styles
  • … can deal with global configuration
  • … can deal with events
  • … can deal with asynchronously loaded components

Now, does this exist yet? Obviously not. But can it be built? Absolutely.

Conclusion

This article introduced the concept of Component-Based Deployment (CBD): The idea of abstracting infrastructure to the highest possible level. Similar to how we deploy lambda functions or Docker containers, in a CBD world we deploy React components to a stable runtime. We could even call it “serverless react”.

Now you might ask: Isn’t that what web components try to achieve? And the answer is yes and no. Web components could be part of the solution, but don’t really address any of the operational questions. Web components certainly have great properties in terms of isolation in the browser, however they do not support server-side rendering.

Also, while the idea of a component registry is certainly not new (for example webcomponents.org has an extensive library of web components), it is not exactly solving the problem. Companies and individuals would want to run their own component registries (in the same way that people using AWS ECS or Lambda don’t publish their code to a public registry in most cases).

To conclude: Component-Based Deployment could completely change how web applications get delivered. Deployments of new features could happen within seconds rather than hours. Roll-backs could happen within seconds and only roll back a single faulty component rather than the whole application. QA could shift focus on component testing. And edge computing could greatly benefit as well. Imagine pulling in a new component from the registry for a quick A/B test within a particular region.

Ultimatly though decentralized and distributed ecosystems of freely interchangeable components could emerge. Think of a market place for landing page building blocks for example (like Docker hub) and how that would change how we build web applications.

FAQ

How is this different from Hot Module Replacement (HMR)?

HMR aims at a better developer experience by allowing developers to hot-swap modules without a full browser reload. This is not the same as swaping modules for production use, which generally would require a full page reload and has entirely different requirements in terms of robustness. A notification to the user that the app was updated could still be a good idea.

Wouldn’t it be easier to just restart Node every time there is a code change?

While this solves a lot of problems, hot-swapping is definitly a solvable problem. For example it has been solved in Erlang/Elixier. As a side note, I made a drop-in replacemant for nodemon, that takes the approach of restarting node on code changes rather than rebuilding a container: https://github.com/tw00/nodemon-remote - it can be used for other things as well, but that was the primary motivation.


  1. See (this stack overflow answer)[https://stackoverflow.com/a/47042416]. ↩︎