Write once, feels native

A React optimizing compiler for shipping better, faster.

Tamagui compiler enables much more code share between native & web without costing perf vs individually written apps. It speeds up web 1-3x, native ~0.25x.

Let's start with a choice all frontend developers make implicitly, presented three ways:

You can only choose two:

Multi-platform

Single codebase

Feels native

Here's the same two-of-three, but in a diagram that reveals a center - a center that's been attempted largely unsuccessfully since the dawn of GUI:

The Frontend Trilemma: Choose two of DX, UX, and multi-platform (or run out of
time).

If you've internalized that, you can re-phrase the two-of-three constratints as a simple single choice in your frontend strategy. Note, the numbers are contextualized to React Native developers today, though they hold somewhat generally:

Choose one:

1

2

3

Strategy

Universal

Lean

Big-Budget

Native + Web

Code Sharing

> 70%

-

< 30%

Feels native

Ship Fast

You either target more platforms with one codebase and sacrifice UX and performance, or target each platform individually but double your time to ship each feature.

Code Sharing should be expanded to Code Sharing without sacrificing UX or performance, which is why for the "Big-budget" option is typically ends up below 50%. We'll go into more on why in the next section.

Many people choose the former, and it's unfortunate but understandable. For a variety of reasons: you're a small team, you have to ship, and supporting more platforms or testing your idea out is more important than absolute speed. Life's tough.

But I don't think it's necessary for React to be slow, even when sharing a lot of code. Even using fancy CSS-in-JS solutions. We just need some help - and compilers will do just that.

First, a chart:

Sharing code between web and native without sacrificing native feel has gone way up with the
release of React Native, and later with the release of React Native Web

You can define your own "feels native" and set the Y axis appropriately, even making small if you want. I put a general upward trend as all sort of amazing library authors have made sharing so much more performance and well-designed, a few being React Navigation  (+ Solito ), React Native Skia  or Reanimated  and many  many  many  many  more .

Why we can't share effectively today

With React Native Web today it's hard to share more than a pretty small fraction of your frontend without immediately sacrificing look and feel.

This is mostly because of the web. The web is extra sensitive to performance, especially when it comes to Javascript, but the CSS-in-JS strategy of React Native Web ends up shipping a lot of it.

It's not just light bundles that are the limiting factor. Way too many platform features aren't supported in React Native StyleSheet, leading to lots of JS logic in places it shouldn't ever be.

All of the following being moved into CSS would unlock a huge amount of performance:

  • Inline or logically determined styles (often written as objects to save time)
  • Responsive styles (media queries)
  • Themes, colors, sizing (CSS variables)
  • Interactive styles (pseudo styles)

Media queries are especially table-stakes for sharing code, but running them as hooks de-opts web-apps such that even simpler apps bring modern browsers to a halt on resize. Pseudo styles and theme styles are also in places very sensitive to re-renders (especially as interactive changes often animate).

How Tamagui compiles

Tamagui fixes many of these problems with a compiler. It optimizes four things:

  • Static styles via the styled function
  • Dynamic (inline) styles, using partial evaluation
  • Hooks for themes or media queries
  • Tree-depth (via tree-flattening)

It turns this:

Input
import { Text, YStack } from '@tamagui/core'
const App = (props) => (
<YStack padding={props.big ? '$5' : '$3'} {...(props.colored && { backgroundColor: 'green', })} >
<Text color="green" $large={{ color: 'yellow', $hover: { color: 'green', }, }} >
Lorem ipsum dolor.
</Text>
</YStack>
)

Intos something like this:

import { YStack, concatClassName } from 'tamagui'
const App = (props) => (
<div className={concatClassName( _cn + (props.big ? _cn2 : _cn3) + (' ' + (props.colored ? _cn4 : ' ')) )} >
<p className={_cn5}>Lorem ipsum dolor.</p>
</div>
)
const _cn5 = ' _c-scmqyp _d-1471scf _ff-xeweqh _fs-7uzi8p _lh-1l6ykvy'
const _cn4 = ' _bc-1542mo4'
const _cn3 = ' _pb_-12bic3x _pl_-7ztw5e _pr-g6vdx7 _pt-1vq430g'
const _cn2 = ' _pb-z3qxl0 _pl-14km6ah _pr-1qpq1qc _pt-1medp4i'
const _cn = ' _bc-abc123 _d-6koalj _fd-eqz5dr _fls-1q142lx '

Along with CSS (which you can see, alongside more examples see on the homepage).

The tree flattening here (turning Text into p) dramatically increases render performance on the web, above the already large gains from CSS compilation. This is a benchmark of rendering a single view that has a few "variants." Typically, your app would have to render a large View with many hooks. With Tamagui, it just hands a div over to React:

Tamagui

0.02ms

react-native-web

0.063ms

Dripsy

0.108ms

NativeBase

0.73ms

Meanwhile, on Native, because we can't optimize to anything beyond vanilla React Native code, the gains are less. Still, the results are impressive given you are now getting a huge amount of features and nice syntax that normally would eat up a lot of performance, and the net result is basically even with hand-optimized React Native:

Tamagui

108ms

Native

106ms

NativeBase

247ms

You can see the full Benchmarks with explanations here.

More detailed docs on how the compiler, optimizer and partial evaluation works will come in a follow-up article.

I think with a compiler, we can dramatically improve code sharing without sacrificing rendering to native, and feeling great on each platform, enabling a new option:

1

2

3

4

Strategy

Universal

Lean

Big-Budget

Universal + Compiler

Native + Web

Code Sharing

> 70%

-

< 30%

~60-90%

Ship Fast

Feels native

wwwwwwwwwwwwwwwwwww

Give Tamagui a try with npm create tamagui-app.