Bundlers

Metro

Metro already runs Babel over every file, so the motif transform for React Native is a Babel plugin — no separate Metro transformer. Add @usemotif/compiler-metro to babel.config.js and it hoists static styles into StyleSheet.create at build time.

Updated 3 days agoEdit on GitHubWeb & native

Install

example.tsx
yarn add -D @usemotif/compiler-metro

Add the plugin

@usemotif/compiler-metro default-exports a function that returns a Babel plugin tuple. Call it in the plugins array of babel.config.js.

babel.config.js
const motif = require('@usemotif/compiler-metro').default;
 
module.exports = function (api) {
api.cache(true);
return {
  presets: ['babel-preset-expo'],
  plugins: [motif()],
};
};

That is the whole integration. Metro picks the plugin up on the next build; there is no Metro config to touch and no virtual module to import.

What it does on native

The web extractor emits CSS. There is no CSS on native, so the native transform does different work: every call site whose style props are literal-only is collected into a single StyleSheet.create({ ... }) hoisted to the top of the file, and each style={} is rewritten to reference the hoisted entry. Dynamic prop bags are left for the runtime.

@usemotif/compiler-metro defaults target to 'native', which is what a React Native build always wants — so motif() with no arguments is the correct call. The package is a thin rename of @usemotif/compiler-babel with that default applied; keeping it separate gives Metro-specific configuration a home as it grows.

Sharing one config across platforms

A monorepo that builds the same source for web and native can pass target explicitly to keep a single Babel config honest about which build it is in.

babel.config.js
const motif = require('@usemotif/compiler-metro').default;
 
module.exports = function (api) {
api.cache(true);
const isNative = process.env.MOTIF_TARGET !== 'web';
return {
  presets: ['babel-preset-expo'],
  plugins: [motif({ target: isNative ? 'native' : 'web' })],
};
};

For a native-only app, leave target off and take the default.