# Source: https://tamagui.dev/docs/intro/compiler-install.md

---
title: Tamagui Compiler
description: Adding the compiler to your apps
---

<IntroParagraph>
  The Tamagui Compiler significantly improves performance of both web and native
  applications through partial analysis and view flattening.
</IntroParagraph>

See the [Benchmarks](/docs/intro/benchmarks) or a more
[in-depth background](/docs/intro/why-a-compiler). Note that Tamagui features
work at compile-time and runtime, so installing the compiler is optional, and in
fact we recommend only setting it up once you're ready for production.

The compiler uses Babel to analyze JSX and `styled` functions, then attempts
statically analyze them and optimize them down to platform-native primitives.
The end result is less abstraction - like a `div` on web, or plain React Native
`View` on native:

<br />
<br />
<TamaguiExamplesCode />

<Notice theme="blue">
  The compiler generates built versions of your components and config into a
  `.tamagui` directory. You'll want to add that directory to your `.gitignore`.
</Notice>

## Install

There are plugins for a variety of bundlers, or you can use the `@tamagui/cli`
to compile in-place:

### Webpack

```bash
yarn add tamagui-loader
```

We have a full example of a plain Webpack or Vite setup in the simple starter
accessible through `npm create tamagui@latest`, which shows a complete
configuration with more detail.

Add `tamagui-loader` and set up your `webpack.config.js`.

You can set it up more manually like so:

```js
const { shouldExclude } = require('tamagui-loader')

const tamaguiOptions = {
  config: './tamagui.config.ts',
  components: ['tamagui'],
  importsWhitelist: ['constants.js', 'colors.js'],
  logTimings: true,
  disableExtraction: process.env.NODE_ENV === 'development',
  // optional advanced optimization of styled() definitions within your app itself, not just ones in your components option
  // default is false
  enableDynamicEvaluation: false,
}

module.exports = {
  resolve: {
    alias: {
      // Resolve react-native to react-native-web
      'react-native$': require.resolve('react-native-web'),
      // optional, for lighter svg icons on web
      'react-native-svg': require.resolve('@tamagui/react-native-svg'),
    },
  },
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        // you'll likely want to adjust this helper function,
        // but it serves as a decent start that you can copy/paste from
        exclude: (path) => shouldExclude(path, __dirname, tamaguiOptions),
        use: [
          // optionally thread-loader for significantly faster compile!
          'thread-loader',

          // works nicely alongside esbuild
          {
            loader: 'esbuild-loader',
          },

          {
            loader: 'tamagui-loader',
            options: tamaguiOptions,
          },
        ],
      },
    ],
  },
}
```

Or you can use the TamaguiPlugin which automates some of this setup for you:

```tsx
const { TamaguiPlugin } = require('tamagui-loader')

module.exports = {
  plugins: [
    new TamaguiPlugin({
      config: './tamagui.config.ts',
      components: ['tamagui'],
      importsWhitelist: ['constants.js', 'colors.js'],
      logTimings: true,
      disableExtraction: process.env.NODE_ENV === 'development',
    }),
  ],
}
```

Some notes on the options:

- _importsWhitelist_: Tamagui takes a conservative approach to partial
  evaluation, this field whitelists (matching against both .ts and .js) files to
  allow files that import them to read and use their values during compilation.
  Typically colors and constants files.
- _disableExtraction_: Useful for faster developer iteration as your design
  system hot reloads more reliably.

### Vite

See the [Vite guide](/docs/guides/vite) for more complete setup.

Add `@tamagui/vite-plugin` and update your `vite.config.ts`:

```tsx
import { tamaguiPlugin } from '@tamagui/vite-plugin'

export default defineConfig({
  plugins: [
    tamaguiPlugin({
      // points to your tamagui config file
      config: 'src/tamagui.config.ts',
      // points to any linked packages or node_modules
      // that have tamagui components to optimize
      components: ['tamagui'],
      // turns on the optimizing compiler
      optimize: true,
    }),
  ],
})
```

### Next.js

See the ([guide](/docs/guides/next-js)) for more complete setup.

Add `@tamagui/next-plugin` and configure your `next.config.js`. Here we show a
fuller scope of the options

```js
// note next-compose-plugins somewhat unmaintained
// you can use a simple two-liner instead, see:
// https://github.com/cyrilwanner/next-compose-plugins/issues/59#issuecomment-1192523231
const withPlugins = require('next-compose-plugins')
const { withTamagui } = require('@tamagui/next-plugin')

export default withPlugins([
  withTamagui({
    config: './tamagui.config.ts',
    components: ['tamagui'],

    // rest are all optional:

    // disable static extraction, faster to iterate in dev mode (default false)
    disableExtraction: process.env.NODE_ENV === 'development',

    // Exclude react-native-web modules to lighten bundle
    excludeReactNativeWebExports: ['Switch', 'ProgressBar', 'Picker'],

    // By default, we configure webpack to pass anything inside your root or design system
    // to the Tamagui loader. If you are importing files from an external package, use this:
    shouldExtract: (path: string, projectRoot: string) => {
      if (path.includes('../packages/myapp')) {
        return true
      }
    },

    // Advanced:

    // Many packages give difficulty to the nextjs server-side (node) runtime when un-bundled.
    // for example, tamagui configures aliases like react-native => react-native-web.
    // if you're running into a module that has errors importing react-native, you'll want to
    // use a custom shouldExcludeFromServer function to include it (or override the default).
    // this is the exact same return type as webpack.externals.
    // returning undefined will let tamagui handle it, boolean or other values to override.
    shouldExcludeFromServer: ({ fullPath, request }) => {
      if (fullPath.includes('my-module')) {
        return 'commonjs ' + commonjs
      }
      if (request === 'some-hard-to-bundle-package') {
        return true
      }
    },
  }),
])
```

Note: If running into issues, the environment variable `IGNORE_TS_CONFIG_PATHS`
to "true" can fix issues with Tamagui being resolved incorrectly.

See the [Next.js Guide](/docs/guides/next-js) for more details on setting up
your app.

### Babel / Metro

Note that the `@tamagui/babel-plugin` is completely optional, and on native
Tamagui doesn't optimize as much as on web, so leaving it out is actually
recommended to start. If later on you feel the need for a bit more speed, you
can try adding it.

```bash
yarn add @tamagui/babel-plugin
```

Add to your `babel.config.js`:

```js
module.exports = {
  plugins: [
    [
      '@tamagui/babel-plugin',
      {
        components: ['tamagui'],
        config: './tamagui.config.ts',
        importsWhitelist: ['constants.js', 'colors.js'],
        logTimings: true,
        disableExtraction: process.env.NODE_ENV === 'development',
      },
    ],
  ],
}
```

Currently the native compiler doesn't optimize as much as it could. It bails out
if it encounters any theme usage, like `<View backgroundColor="$background" />`.
If you are on version `1.75` or greater, you can test enabling this experimental
optimization by adding a new property with the key
`experimentalFlattenThemesOnNative` set to true in the above config object and
that will make the compiler to flat and extract any theme usage or dynamic
values.

### Expo

[Check out the Expo guide](/docs/guides/expo) for more information on setting up
Expo. It's as simple as adding the babel plugin.

### CLI-Based In-Place Compilation

For bundlers that don't have a Tamagui plugin yet (like Turbopack), or if you
prefer a simple setup, you can use `@tamagui/cli` to pre-compile your components
in-place before your build step.

This approach is meant for **production builds only** and should run in your
deployment pipeline, not during development. It rewrites files in place which
will mess up your working directory, but makes it highly compatible with any
bundler or tool. The downside is you don't get the helpful development
compatibility parts of the plugins, plus dev-mode debugging and `data-`
attributes.

#### Setup

1. Install:

```bash
yarn add -D @tamagui/cli
```

2. Create a `tamagui.build.ts` config file in your project root:

```ts
import type { TamaguiBuildOptions } from 'tamagui'

export default {
  config: './tamagui.config.ts',
  components: ['tamagui'],
  importsWhitelist: ['constants.js', 'colors.js'],
  outputCSS: './public/tamagui.css',
} satisfies TamaguiBuildOptions
```

3. Add a build script to your `package.json`:

```json
{
  "scripts": {
    "build": "tamagui build ./src && next build"
  }
}
```

#### Usage

```bash
# Build all components in a directory (web + native by default)
npx tamagui build ./src

# Build for web only
npx tamagui build --target web ./src

# Build for native only
npx tamagui build --target native ./src

# Build a specific file
npx tamagui build ./src/components/MyComponent.tsx

# Include/exclude patterns
npx tamagui build --include "components/**" --exclude "**/*.test.tsx" ./src

# Verify minimum optimizations (useful in CI)
npx tamagui build --target web --expect-optimizations 10 ./src
```

#### CI Verification with --expect-optimizations

The `--expect-optimizations` flag ensures your build is actually optimizing components. This is useful in CI to catch configuration issues:

```json
{
  "scripts": {
    "build": "tamagui build --target web --expect-optimizations 10 ./src && next build"
  }
}
```

If the compiler produces fewer than the expected number of optimizations, the build will fail with an error message showing the actual count. This helps catch:

- Misconfigured `components` array
- Wrong source paths
- Configuration files not being found

#### Platform-Specific File Handling

The CLI automatically handles platform-specific files (`.web.tsx`,
`.native.tsx`, `.ios.tsx`, `.android.tsx`):

- Files with `.web.tsx` or `.ios.tsx` extensions are optimized for web only
- Files with `.native.tsx` or `.android.tsx` extensions are optimized for native
  only
- Base files (`.tsx`) without platform-specific versions are optimized for all
  platforms
- If both `.web.tsx` and `.native.tsx` exist, the base `.tsx` file is skipped

#### Package.json Exports Support

The CLI supports `package.json` exports for path-specific imports. For example:

```json
{
  "exports": {
    ".": "./src/index.tsx",
    "./components/Button": "./src/Button.tsx"
  }
}
```

Both import styles work:

```tsx
import { Button } from '@my/ui'
import { Button } from '@my/ui/components/Button'
```

#### Integration Examples

This works with **any build tool** - just run `tamagui build` before your build
command. Here are some examples:

**Next.js with Turbopack** (Turbopack doesn't support plugins yet):

```json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "tamagui build --target web ./src && next build"
  }
}
```

**Vite, Remix, or any other bundler:**

```json
{
  "scripts": {
    "build": "tamagui build --target web ./src && vite build"
  }
}
```

**React Native / Expo:**

```json
{
  "scripts": {
    "build:ios": "tamagui build --target native ./src && eas build --platform ios",
    "build:android": "tamagui build --target native ./src && eas build --platform android"
  }
}
```

The Tamagui CLI optimizes your components in-place, then your bundler processes
the already-optimized files.

## Props

All compiler plugins accept the same options:

<PropsTable
  data={[
    {
      name: 'config',
      required: true,
      type: 'string',
      typeSimple: 'enum',
      description:
        'Relative path to your tamagui.config.ts file which should export default the result from createTamagui.',
    },
    {
      name: 'components',
      required: false,
      type: 'string[]',
      typeSimple: 'enum',
      default: "['tamagui']",
      description: `Array of npm modules containing Tamagui components which you'll be using in your app. For example:  if you are using the base Tamagui components. This directs the compiler to load and optimize.`,
    },
    {
      name: 'importsWhitelist',
      required: false,
      type: 'string[]',
      typeSimple: 'enum',
      description: `Array of whitelisted file paths (always end in .js) which the compiler may try and import and parse at build-time. It is normalized to ".js" ending for all file extensions (js, jsx, tsx, ts). This usually should be set to something like ['constants.js', 'colors.js'] for example, where you have a couple mostly static files of constants that are used as default values for styles.`,
    },
    {
      name: 'logTimings',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'true',
      description:
        'Tamagui outputs information for each file it compiles on how long it took to run, how many components it optimized, and how many it flattened. Set to false to disable these logs.',
    },
    {
      name: 'disable',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description: 'Disable everything - debug and extraction.',
    },
    {
      name: 'disableExtraction',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        'Disable extraction to CSS completely, instead fully relying on runtime. Setting this to true speed up development as generally your app will hot reload the Tamagui configuration itself.',
    },
    {
      name: 'disableDebugAttr',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        'If enabled along with disableExtraction, all parsing will turn off. Normally turning off disableExtraction will keep the helpful debug attributes in DOM',
    },
    {
      name: 'disableFlattening',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description: 'Turns off tree-flattening.',
    },
    {
      name: 'enableDynamicEvaluation',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        '(Experimental) Enables further extracting of any styled component, even if not in your components. See below for more information.',
    },
    {
      name: 'experimentalFlattenThemesOnNative',
      required: false,
      type: 'boolean',
      typeSimple: 'enum',
      default: 'false',
      description:
        '(Experimental) Enables further extracting of components that use theme values on native.',
    },
  ]}
/>

### Dynamic Evaluation

By default the Tamagui compiler only optimizes `styled` expressions found in the
modules defined by your `components` config. This means if you do an inline
`styled()` inside your actual app directory, it will default to runtime style
insertion.

This is typically Good Enough™️. As long as you define most of your common
components there, you'll get a very high hit rate of compiled styles being used
and runtime generation being skipped, as atomic styles with your design system
tokens will be mostly pre-generated.

Tamagui has _experimental_ support for loading any component, even if it occurs
somewhere outside your configured components modules. This is called "dynamic
loading", for now. You can enable it with the setting `enableDynamicEvaluation`
as seen above in the props table.

The way it works is, when the compiler detects a styled() expression outside one
of the defined component directories, it will run the following:

1. First, read the file and use a custom `babel` transform to _force_ all
   top-level variables to be exported.
2. Then, run `esbuild` and bundle the entire file to a temporary file in the
   same directory, something like `.tamagui-dynamic-eval-ComponentName.js`
3. Now, read the file in and load all new definitions found.
4. Finally, continue with optimization, using the newly optimized component.

You may see why this is experimental. It's very convenient as a developer, but
has a variety of edge cases that can be confusing or breaking, and we want to
avoid installation woes. Though it does continue on error and work generally, it
outputs warnings in Webpack currently due to our plugin not properly indicating
to Webpack about the new files (a fixable bug), which causes big yellow warning
output and a cache de-opt.

We're leaving this feature under the environment variable while it matures. Let
us know if you find it useful.

### Disabling the compiler

You can disable the compiler optimizations for an entire file with a comment at
the top of your file:

```tsx
// tamagui-ignore
```

You can disable the compiler optimization for a single component with the
boolean property `disableOptimization`:

```tsx
import { View } from '@tamagui/core'

export default () => <View disableOptimization />
```

### Web-only apps

If you want autocompleted imports of `react-native` without having to install
all the weight of react-native, you can set `react-native` version to `0.0.0`,
and add `@types/react-native` at the latest version.