skip to content

For a React Native Web build, how does a Babel alias to react-native-web differ from a bundler alias, and what does babel-plugin-react-native-web add?

level: middleimportance: should knowfreq 24%

answer

  1. rewrite at compile time, not resolve time
  2. module-resolver with a ^...$ pattern
  3. one import per export
  4. CommonJS kills tree shaking
  5. commonjs option for the dist path

basics

~20 s

A Babel alias rewrites the import string during compilation, so any tool that runs Babel sees react-native-web. babel-plugin-react-native-web also rewrites each named import to its own module path, so unused components are dropped from the bundle.

solid answer

~40 s

A bundler alias changes how a specifier **resolves**; a Babel alias changes the **source text** before the bundler sees it, so it works for any pipeline that runs Babel. React Native Web documents `babel-plugin-module-resolver` with `alias: { '^react-native$': 'react-native-web' }`. Its own `babel-plugin-react-native-web` goes further: it aliases the package *and* turns `import { StyleSheet, View } from 'react-native'` into one default import per export from `react-native-web/dist/exports/StyleSheet` and `.../View`. That matters because React Native's Babel preset converts ES modules to CommonJS, which stops bundlers from tree-shaking the library's index; per-export imports keep unused components out. Names the plugin does not know fall back to the package index. Set `commonjs: true` when the bundler consumes CommonJS so paths point at `dist/cjs`. The internal paths are not a stable API: only the plugin should write them.

go deeper

for a junior

Recall that the alias can also be done in Babel, and that React Native Web ships a Babel plugin recommended for web builds.

for a middle

Explain compile-time versus resolve-time aliasing, the anchored module-resolver pattern, and the plugin's per-export rewrite.

for a senior

Explain why CommonJS output defeats tree shaking, when to set commonjs: true, and why internal dist paths must never appear in source.

for a principal

Decide which tools in the pipeline own the alias (bundler, tests, server rendering) so every environment sees the same module graph.

## Two places to alias There are two moments in a web build where `react-native` can be swapped for `react-native-web`: | Where | How | Sees it | |---|---|---| | **Module resolution** (bundler) | webpack `resolve.alias` with `'react-native$'` | only that bundler | | **Compilation** (Babel) | a Babel plugin rewrites the import string | every tool that runs the same Babel config | A Babel alias is useful when the bundler has no alias feature you control, or when several tools (a bundler, a test runner, a server-rendering build) share one Babel configuration. The trade-off is that it only covers files Babel actually processes: a dependency excluded from Babel still contains the original `react-native` import. ## The module-resolver alias React Native Web's setup guide shows `babel-plugin-module-resolver` with an anchored pattern: ```json { "plugins": [ ["module-resolver", { "alias": { "^react-native$": "react-native-web" } }] ] } ``` The `^` and `$` anchors play the same role as webpack's trailing `$`: only the exact specifier is replaced, so deep `react-native/...` imports are left alone. ## What babel-plugin-react-native-web adds React Native Web ships its own Babel plugin, `babel-plugin-react-native-web`, enabled with `plugins: ['react-native-web']`. It does two things: 1. **It aliases.** Imports, `require` calls and re-exports of `react-native` (or `react-native-web`) are redirected to `react-native-web`. 2. **It splits imports per export.** For every named import it knows, it emits a default import from that export's own module: ```javascript // before import { StyleSheet, View } from 'react-native'; // after import StyleSheet from 'react-native-web/dist/exports/StyleSheet'; import View from 'react-native-web/dist/exports/View'; ``` A name the plugin does not know (for example an Android-only API that React Native Web does not implement) is imported from the package index instead, where it resolves to `undefined`. ## Why the per-export rewrite matters - React Native's Babel preset converts ES modules to **CommonJS**. A bundler cannot tree-shake a CommonJS index, so `import { View } from 'react-native'` would otherwise pull in every component React Native Web exports. - With per-export paths, the bundle contains only the modules the app actually imports, which is the "prune modules not used by your application" step the React Native Web docs recommend. ## The commonjs option The plugin writes paths into `react-native-web/dist/exports/...` by default, which is the ES-module build. If the bundler consumes CommonJS, set the option: ```json { "plugins": [["react-native-web", { "commonjs": true }]] } ``` and the paths become `react-native-web/dist/cjs/exports/...`. Match the option to the module format your bundler actually consumes. ## Rules that keep it safe - **Never write the `dist/exports/...` paths by hand.** React Native Web's README says the internal paths are not stable; only the plugin should produce them. - **Use one alias mechanism per tool.** A Babel alias plus a bundler alias is harmless but confusing; pick one and document it. - **Other tools need their own mapping** unless they run the same Babel config: React Native Web's docs show Jest's `moduleNameMapper` with `'^react-native$'` and a Node module-alias setup for server rendering. - **Expo projects need none of this**; Expo's Metro configuration performs the alias for the web platform. ## How to check that pruning works 1. Build the web bundle for production with the plugin enabled. 2. Open a bundle analyser (or the bundler's stats) and look for React Native Web modules the app never imports, such as `VirtualizedList` in an app without lists. 3. If the whole library is present, look for an import the plugin could not rewrite: a namespace import (`import * as RN from 'react-native'`), a dependency that Babel does not process, or a mismatch between the `commonjs` option and the build being consumed. 4. Rebuild and compare; the difference is the saving the per-export rewrite provides. ## Choosing between them For a webpack-based web target, the usual setup is both: the webpack alias for resolution and `babel-plugin-react-native-web` for pruning. For a toolchain without a configurable resolver, the Babel plugin alone does both jobs.

  • What does babel-plugin-react-native-web do with import { ToastAndroid } from 'react-native'?
    It does not know `ToastAndroid` as a React Native Web export, so it rewrites the import to come from the package index instead of a per-export path. React Native Web's index does not export it either, so the value is `undefined` at runtime and the first call throws a `TypeError`.
  • Why is the output of babel-plugin-react-native-web not something you should copy into source files?
    Because the `react-native-web/dist/exports/...` paths are internal and, per the plugin's README, not stable. A library upgrade can move them. Source code should always import from `react-native` and let the plugin produce the internal paths at build time.

saying these in an interview costs you the question

  • A Babel alias also covers dependencies that Babel never processes.
  • Named imports from react-native tree-shake fine even after conversion to CommonJS.
  • You should import react-native-web/dist/exports/View directly for smaller bundles.
  • babel-plugin-react-native-web only optimises; you still need a separate alias.
  • The commonjs option makes the plugin output ES modules.