Intro to Mitosis: The universal reactive transformer
Mitosis.js is a compiler tool that consumes a universal component syntax and outputs framework-specific code. That means you can write application functionality once and generate it to React, Svelte, or Angular, among others. What distinguishes Mitosis from other compile-time frameworks is its “write once, run anywhere” approach to compilation. Mitosis is an impressive engineering feat, and it has applications anywhere you need to abstract front-end frameworks into pluggable components.
The hidden benefit of Mitosis is its revelation of the common aspects and unity in front-end JavaScript frameworks. This is a new model that could yield unexpected insights and new directions for future JavaScript development.
What is Mitosis.js?
Mitosis is a project from the folks at Builder.io, who also developed envelope-stretching projects like the Qwik.js framework and Partytown. Builder is itself a good example of the kind of application that benefits from Mitosis. In short, Builder allows you to visually design UI layouts across various underlying framework implementations. It needs a common language to process and output those diverse frameworks, and that language is Mitosis.
As of this writing, Mitosis supports the following front-end JavaScript frameworks:
Mitosis also supports outputting to straight HTML and has Qwik.js on the roadmap. Also, Mitosis is the translation bridge that Builder uses between third-party design tool Figma. That is to say, the abstraction layer is useful in taking design output and transforming it into the desired target framework.
Mitosis is syntactically a subset of JSX, or JavaScript XML. This makes sense for a couple of reasons. JSX is the syntax in React, the most common and influential JavaScript framework. Also, at the end of the day, JSX is a fairly good distillation of the elements of a reactive UI descriptor. Specifically, Mitosis uses a JSX variant inspired by the one used in Solid.js.
Listing 1 is a simple example that shows off a few conventions of Mitosis’s JSX variant.
Listing 1. Basic list output in Mitosis (TypeScript)
import useStore from '@builder.io/mitosis';
type Props =
writer: string;
;
export default function SongList(props: Props)
const state = useStore(
songs: [
title: "Strawberry Fields", writer: "John Lennon" ,
title: "Penny Lane", writer: "Paul McCartney" ,
title: "Dark Horse", writer: "George Harrison" ,
title: "It don't come Easy", writer: "Ringo Starr"
],
);
return (
<div>
<For each=state.songs>(song, index) => <div>song.title</div></For>
</div>
);
Listing 1 takes a list of objects (songs
) and outputs a property from each one (song.title
). There are few things to note in this sample. First, the file exports a default function. Therefore, it is defining a functional component. Mitosis will transform this component to the right structure for its target framework.
Next, note that the component utilizes a hook, useStore
. This hook works analogously to the one found in React. The code then uses the state to iterate over the songs with a <For>
component. Iterating over collections is one of those areas of diversity in frameworks and the <For>
component offers a simple, unified way to express it.
Also, observe the standard handling of component properties, via the props
argument to the function (with its attendant TypeScript type definition of Props
).
Run the compiler
To put this component through the Mitosis compiler (or, strictly speaking, transpiler), we can set up a simple Node Package Manager (NPM) project. To start, initiate a project (npm init
), then install the Mitosis libraries by entering
npm install @builder.io/mitosis-cli @builder.io/mitosis
The Mitosis compiler will automatically find the files we want to compile based on a mitosis.config.js
file. We’ll use the simple one shown in Listing 2.
Listing 2. Mitosis.config.js
module.exports =
files: 'src/**',
targets: ['vue3', 'solid', 'svelte', 'react', 'angular'],
;
Listing 2 tells where the sources are to be found (src/**
) and what output frameworks to use.
Our component is in TypeScript, so we’ll need a simple tsconfig.json
file, as well:
Listing 3. tsconfig.js
"compilerOptions":
"jsx": "preserve",
"jsxImportSource": "@builder.io/mitosis"
Listing 3 tells the TypeScript command-line interface (tsc-cli
) how to handle the JSX it encounters. In this case, it leaves the syntax as-is (preserve
) and defines the module to use for import (@builder.io/mitosis
). See the JSX overview and code sample in the TypeScript documentation for details.
Mitosis with React
Now we’re ready to get some output. Run npm exec mitosis build
. This will drop files into the output/
directory, one branch for each target framework. Let’s take a peek at the /output/react/src/components/Songs.jsx
version, which will look something like Listing 4.
Listing 4. React version of Songs.jsx
import * as React from "react";
import useState from "react";
function SongList(props)
const [songs, setSongs] = useState(() => [
title: "Strawberry Fields", writer: "John Lennon"
,
title: "Penny Lane", writer: "Paul McCartney"
,
title: "Dark Horse", writer: "George Harrison"
,
title: "It don't come Easy", writer: "Ringo Starr"
]);
return /* @__PURE__ */ React.createElement("div", null, songs == null ? void 0 : songs.map((song, index) => /* @__PURE__ */ React.createElement("div", null, song.title)));
export
SongList as default
;
So, we can see that Mitosis has switched to using the React implementation of useState
and has opted for using React.createElement
to define the div
and song.map()
to iterate over the collection. It exports the component as a default module. This looks like valid React so far, but let’s check it.
We can go to another directory and spin up a create-react-app
real quick (see the Create React App page for details), then go to the new directory that has just been created. In the /src
directory, we’ll copy over the output/react/src/components/Songs.jsx
file from our Mitosis project. We open App.jsx
and import the new component by adding import “./Songs.jsx”
as Songs
, then go into the template markup and use the component somewhere with <Songs />
.
Now, we can run the app with npm start
. Check the output at localhost:3000
and you’ll see the list of song names on the page.
Nice. Now we know that Mitosis is working with React. In a real-world situation, we could readily build a pipeline to add Mitosis to our build process.
Mitosis with Svelte
Let’s use a quick shortcut to see how Mitosis works with Svelte. Copy the contents of /output/svelte/src/components/Songs.svelte
(noticing that Mitosis has given the proper extension to the file). Go to the Svelte playground and paste the source into the left-hand code panel. After a moment, you will see the song list on the right side of the screen.
Mitosis is generating correct Svelte. If you’re curious, Listing 5 shows the idiomatic Svelte iteration for the <For>
component.
Listing 5. Song iterator in Svelte
#each songs as song, index
<div>song.title</div>
/each
And Vue, Angular, SolidJS
You can take similar steps to verify the correctness of each of the other output targets.
Configuration and plugins
Mitosis is intended to be quite flexible. In particular, the Mitosis playground demonstrates the ability to change a configuration to select not only different frameworks but different characteristics within them. For instance, you can pick a state provider in React, choosing between useState
, Mobx, and Solid. You also can select different styling solutions, like Emotion CSS, Styled Components, and Styled JSX.
Mitosis also supports the ability to define plugins that run arbitrary code at strategic moments, like before and after the underlying JSON data structure is generated.
Consuming framework code
You might wonder if it is possible to flip Mitosis’s functionality from producing to consuming framework code. As an example, could we take a UI defined in a framework implementation and parse it into the Mitosis JSON model? That would not only let us two-directionally translate between Mitosis and a framework but actually translate between different frameworks via the Mitosis model.
I asked Builder.io’s founder, Steve Sewell whether Mitosis could understand framework code. Here’s what he said:
[Framework parsing] is definitely the biggest request we get. Right now most frameworks are a bit too freeform (not enough constraints) to do this reliably. That said Svelte is the best candidate for that, which is actively being worked on, we call it sveltosis.
Conclusion
Mitosis is currently still in beta. That being said, it has more than 6,000 stars on GitHub and is in active use at Builder.io. Perhaps the most interesting thing about Mitosis is that it describes a reactive user interface as JSON. It represents declaratively in data the complex functionality at the heart of front-end frameworks, which provides a foundation for developing a universal model of front-end development frameworks. Mitosis’s cross-framework approach to JavaScript compilation points to the possibility of meta frameworks and platforms that developers could use to compose applications at a higher level of abstraction.
Copyright © 2022 IDG Communications, Inc.