Evaluating Mitosis as a Framework-Agnostic Compiler for the CMS Design System #3525
tamara-corbalt
announced in
RFCs
Replies: 1 comment
-
This is an excellent write up @tamara-corbalt! Great work. @kim-cmsds has floated ideas around different architectures for the design system, i.e. moving away from a mono-repo setup, and I even remember previous team members mentioning the idea. I'm still not totally sure if Mitosis seems like the tool for us, but it could be helpful to think through alternative architectures for our code base and how that might offer more flexibility to pursue and use tools like this in the future. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This RFC explores the potential of using Mitosis, a framework-agnostic compiler that converts component logic into framework-specific code. The broader goal is to find a better, and more sustainable way to generate component outputs beyond React — such as Angular and Web Components — in order to better support teams working in non-React environments.
Currently, we achieve this by exposing React components as web components using Preact as a middle layer — a solution that mostly works, but has introduced subtle complications around state management, and framework compatibility.
That’s why we’re exploring Mitosis as a potential alternative.
🚫 First, a common misconception
It’s tempting to imagine that Mitosis could be dropped into our codebase and simply “convert” our existing React components to Angular, Vue, or web components. Unfortunately, that’s not how it works.
Mitosis is a compiler, not a migration tool. It doesn’t analyze your existing React code. Instead, it requires you to author your components in a special syntax — written in a limited subset of JSX and TypeScript that avoids framework-specific hooks and APIs. These source files use the
.lite.tsx
extension and act as the single source of truth for each component.🛠️ How it works
When you run the Mitosis compiler, it:
Parses each
.lite.tsx
file into an intermediate JSON representation.Transforms that JSON into framework-specific output — such as React, Angular, Vue, or web components — depending on your configuration.
For the CMS Design System, the idea of supporting multiple frameworks from a single codebase is extremely appealing. It could reduce long-term maintenance overhead and open the door to broader adoption across teams that don’t use React.
But before we ride off into the sunset with Mitosis, there are some significant trade-offs and architectural questions we need to understand.
⚙️ Will Mitosis Work in our Monorepo?
To help answer this question, I'm going to walk through my first experiment with Mitosis.
In my test branch, I set up a basic Mitosis project inside a shared
scripts/
folder and configured the output to target a nested destination folder:packages/design-system/src/components/mitosis-output
.As part of the test, I created a simple new component called
Greet
and authored it as a.lite.tsx
file within the Mitosis project. In the Mitosis config, I specified bothreact
andangular
as output targets. When I ran the tool, it successfully compiled the component and generated framework-specific code in subfolders under the output directory.The first thing I noticed when I ran the above experiment is it will require a shift in how we organize component stories and tests.
Currently, our convention is to colocate each component’s
.tsx
file with its corresponding.stories.tsx
and.test.tsx
files. This approach won’t work with Mitosis-generated code, because the output directories are overwritten every time the build runs — meaning any manually added files in those directories will be lost.We’ll likely need to adopt a new pattern, where generated framework code lives in its own dedicated subdirectory (
react/
,angular/
, etc.), while tests and stories live in sibling folders (e.g.,react-stories/
,angular-tests/
) or in a shared location outside the generated output.🧩 Mitosis and Supporting a Family of Design Systems
Another important factor in our ecosystem is that we don’t just maintain a single design system — we support a family of design systems. The core
design-system
package serves as the foundation, but we also maintain child systems likeMedicare
,Healthcare
, andCMSgov
, each of which:Inherits shared components and base styles from
core
May override or extend core components (e.g., customizing a
HelpDrawer
)May introduce entirely unique components for their specific domains
With Mitosis, each component lives in a centralized source folder (e.g.,
src/greet.lite.tsx
) and is compiled into target-specific outputs. But it’s unclear how we’d cleanly support child-specific variants of a component and route those outputs to different child packages (e.g., `packages/ds-medicare-gov).This raises several open questions:
How would create and organize separate
.lite.tsx
source files per child design system components?How would the Mitosis config distinguish between core vs. child targets?
Can we split output into different packages per child design system?
This doesn’t mean it’s infeasible — but it’s a layer of non-trivial, architectural complexity we’d need to solve before committing to Mitosis.
🚧 Framework-Specific Dependencies
One of our the big barriers to entry with Mitosis is our reliance on React-specific APIs and libraries, which don’t translate across frameworks and are not supported in
.lite.tsx
files. Mitosis supports most DOM APIs and general-purpose JavaScript libraries likelodash
. However react-centric hooks and libraries are not supported.For example, our
Autocomplete
component uses theuseComboBox
hook fromreact-aria
to manage listbox accessibility. Becausereact-aria
is tightly coupled to React’s hook system, it cannot be used inside Mitosis components. Components likeAutocomplete
would require significant refactoring or replacement logic to become Mitosis-compatible.📋 Testing Workflow
Another concern I have with using Mitosis is around testing and debugging. Because Mitosis is a compiler — not a runtime, we can’t render or test
.lite.tsx
files directly. Instead, we have to compile the source into a specific framework (e.g., React, Angular, or Web Component) and then test the output in its native environment. This means maintaining separate test setups for each supported framework.If we decide to support Angular, for example, we’d need to implement an Angular-specific testing strategy to validate the generated output.
In practice, this makes debugging more involved — especially when a bug appears in only one target (e.g., Angular but not React). Since the generated output is overwritten every time the compiler runs, we can’t safely make edits to debug the issue. Instead, we’d need to trace the problem back to the
.lite.tsx
source, modify the logic there, re-run the compiler, and then re-test in the affected framework.🧪 A Quick Experiment: Rewriting the
Badge
Component in MitosisTo get a feel for how Mitosis works, I tried re-implementing one of our simplest components:
Badge
. This component mostly relies on props and conditional class names, which seemed like a great low-complexity test case. I omitted internationalization (i18n
) for now, since Mitosis doesn’t support translation libraries out of the box.Here was my first attempt:
However, when I compiled it, the output looked like this:
As you can see — none of the declared variables (allClasses, variation, etc.) made it into the compiled output. This was my first real “gotcha” moment. Because Mitosis is a compiler, not a runtime, it relies on static analysis to transform .lite.tsx files into framework-specific code. It only processes logic written in specific patterns and doesn't include variables declared outside the JSX tree unless they’re wrapped in compiler-aware constructs like useStore or other supported Mitosis hooks.
To resolve the issue, I refactored the logic inline within the
return
statement:This version compiled as expected, and the generated output included all the intended class logic.
✅ Small Win: I was able to import the Mitosis-generated React component into Storybook, where it rendered and behaved exactly as expected — a promising sign. I also brought the component into our existing unit test suite for
Badge
, ran the tests, and they passed successfully.See test branch
💡 Tl;DR
The biggest upside of Mitosis is the promise of a single source of truth for components that can be compiled into multiple frameworks — potentially reducing duplication and maintenance-overhead across React, Angular, or others frameworks we might want to support.
But that benefit comes with real trade-offs. Every component would need to be re-authored in a restricted syntax that avoids framework-specific patterns, which could mean significant rewrites for much of our system. We’d also need to restructure how we handle stories and tests, rethink our debugging workflow, and navigate new architectural complexity around supporting child design systems that inherit component functionality and styles from the core design system.
If we decide to pursue Mitosis, it will require deeper exploration and a willingness to reimagine the foundations of how our design system is structured and maintained.
Beta Was this translation helpful? Give feedback.
All reactions