Skip to content

Babel plugin

Unistyles Babel plugin

Unistyles 3.0 relies heavily on the Babel plugin, which helps convert your code in a way that allows binding the ShadowNode with Unistyle. Before reading this guide, make sure to check the Look under the hood guide.

Our golden rule is to never introduce any component that could pollute your native view hierarchy. In other words, if you use a View, it will be rendered as-is in the native view hierarchy.

Let’s discuss the responsibilities of the Babel plugin:

1. Detecting StyleSheet dependencies

Each StyleSheet is different. One might rely on a theme, another on miniRuntime, and so on. The same applies to styles. Each style depends on different things. For example, you can wrap your app in a View that safeguards your app from rendering behind the notch or navigation bar. Another style might be used in your Typography component and provides text color based on the apps’ theme.

Should the Typography style re-calculate on an insets change? Or should the View that relies on insets re-render on a theme change?

We don’t think that’s a good idea. The first responsibility of the Babel plugin is to detect all dependencies in your StyleSheet. This ensures that only the relevant styles are recalculated when necessary.

// Babel: depends on theme
const stylesheet = StyleSheet.create(theme => ({
container: {
// Babel: depends on theme
backgroundColor: theme.colors.background
},
text: {
// Babel: static (no dependencies)
fontSize: 12
}
}))
// Babel: depends on theme and miniRuntime
const stylesheet = StyleSheet.create((theme, rt) => ({
container: {
// Babel: depends on theme and insets
paddingTop: rt.insets.top,
paddingBottom: rt.insets.bottom,
backgroundColor: theme.colors.background
},
text: (fontSize: number) => ({
// Babel: depends on theme
color: theme.colors.text,
// Babel: depends on fontScale
fontSize: rt.fontScale >= 3
? fontSize * 1.5
: fontSize * 0.8
})
}))

2. Attaching unique id to each StyleSheet

This helps us identify your StyleSheet while you’re developing your app and trigger multiple hot-reloads. Such identification is required to swap your StyleSheet with another one, ensuring that you get up-to-date values during reloads. This feature does not affect your app in production, as the bundle never reloads in that environment.

3. Component factory (borrowing ref)

This is the most crucial part—without it, Unistyles won’t be able to update your views from C++. In the early versions of Unistyles 3.0, we tried solving this problem by using the ref prop, but it wasn’t reliable enough. Many developers use different style syntaxes, making it impossible to support all of them.

Instead, we decided to leave the user’s ref as is and transfer the implementation from Babel to our component factory. This way we have more control and we have an unified way of registering your ShadowNodes.

The component factory is a function that takes your component and renders it with an overridden ref prop:

const factory = Component => <Component ref={someMagic} {...props} />;

Let’s go through some examples so you can better understand how this works:

Your code
import { View } from 'react-native'
const ref = useRef()
<View ref={ref} />
Babel transform
import { View } from 'react-native-unistyles/src/components/native/View'
const ref = useRef()
// no changes
<View ref={ref} />

We also support other components to extract ShadowNode from them:

Your code
import { Pressable, Image } from 'react-native'
<Pressable
ref={ref => {
doSomething(ref)
}}
onPress={() => {}}
/>
<Image source={require('./image.png')} style={styles.image} ref={ref2} />
Babel transform
import { Pressable } from 'react-native-unistyles/components/native/Pressable'
import { Image } from 'react-native-unistyles/components/native/Image'
// no changes
<Pressable
ref={ref => {
doSomething(ref)
}}
onPress={() => {}}
/>
<Image source={require('./image.png')} style={styles.image} ref={ref2} />

4. Creating scopes for stateless variants

When you use variants, each time you call useVariants, a new scope is created. This scope contains a local copy of stylesheet that won’t affect other components. This feature is similar to time travel, allowing you to explore different states of your styles with different calls to useVariants.

From your perspective, using variants is simple: you just need to call the useVariants hook:

styles.useVariants({
size: 'small'
})

Behind the scenes, we create a scoped constant that can be accessed anywhere within the same block:

const _styles = styles
{
const styles = _styles.useVariants({
size: 'small'
})
// Your code here
}

This approach also works seamlessly with console.log, allowing you to inspect styles at any point:

// Styles without variants
console.log(styles.container)
styles.useVariants({
size: 'small'
})
// Styles with variants: small
console.log(styles.container)
styles.useVariants({
size: 'large'
})
// Styles with variants: large
console.log(styles.container)

By leveraging such scopes, we ensure support for any level of nesting!

Extra configuration

The Babel plugin comes with a few additional options to extend its usage.

autoProcessImports

By default babel plugin will look for any react-native-unistyles import to start processing your file. However, in some cases you might want to process files that miss such import:

  • ui-kits that aggregates Unistyles components
  • monorepos that use Unistyles under absolute path like @codemask/styles

If that’s your case, you can configure the Babel plugin to process them.

autoProcessPaths

By default babel plugin will ignore node_modules. However similar to autoProcessImports, you can configure it to process extra paths.

Under these paths we will replace react-native imports with react-native-unistyles factories that will borrow components refs read more.

Defaults to:

['react-native-reanimated/src/component', 'react-native-gesture-handler/src/components']

debug

In order to list detected dependencies by the Babel plugin you can enable the debug flag. It will console.log name of the file and component with Unistyles dependencies.

Usage in babel.config.js

You can apply any of the options above as follows:

babel.config.js
/** @type {import('react-native-unistyles/plugin').UnistylesPluginOptions} */
const unistylesPluginOptions = {
autoProcessImports: ['@react-native-ui-kit', '@codemask/styles'],
autoProcessPaths: ['external-library/components'],
debug: true,
}
module.exports = function (api) {
api.cache(true)
return {
plugins: [
['react-native-unistyles/plugin', unistylesPluginOptions]
]
}
}