skip to content

Bundler Alias Setup

A web build aliases react-native to react-native-web in the bundler or Babel, which Expo does for you. Interviewers ask how that alias, server-rendered styles and shared monorepo code fit together.

on this pageshow

explore

questions

4

In a webpack build of a React Native codebase, how do you alias react-native to react-native-web, and why 'react-native$' and .web.js first?

level: middleimportance: must knowfreq 38%

answer

  1. one resolve.alias entry
  2. the dollar sign means exact match
  3. deep imports escape the alias
  4. first extension that matches wins
  5. some React Native packages ship untranspiled

basics

~10 s

Add resolve.alias { 'react-native$': 'react-native-web' } so only the exact react-native import is swapped, and list '.web.js' before '.js' in resolve.extensions so web-specific files win over shared ones.

solid answer

~40 s

`react-native-web` is installed next to `react-dom`, and webpack is told to resolve the package name to it: `resolve: { alias: { 'react-native$': 'react-native-web' } }`. The trailing `$` makes it an **exact match**, so only `import … from 'react-native'` is redirected; a deep import such as `react-native/Libraries/…` is *not* aliased and pulls React Native's own native-only source into the web build, which then fails (deep imports are also a type error under React Native 0.87's Strict TypeScript API). `resolve.extensions` lists `.web.js` before `.js` (and `.web.tsx` before `.tsx`) because webpack takes the **first extension that exists**: that order is what makes `Button.web.js` replace `Button.js` in the web bundle. Many React Native libraries publish untranspiled JSX or Flow, so their `node_modules` folders also need to go through `babel-loader`.

code

javascript · 29 lines
javascript
// web/webpack.config.js (fragment)
const path = require('path');
const appDirectory = path.resolve(__dirname, '../');

module.exports = {
  entry: path.resolve(appDirectory, 'index.web.js'),
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        include: [
          path.resolve(appDirectory, 'src'),
          path.resolve(appDirectory, 'node_modules/some-untranspiled-rn-lib'),
        ],
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['module:@react-native/babel-preset'],
            plugins: ['react-native-web'],
          },
        },
      },
    ],
  },
  resolve: {
    alias: { 'react-native$': 'react-native-web' },
    extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
  },
};

go deeper

for a junior

Recall that a web build maps the react-native import to react-native-web in the bundler, and that react-dom must be installed alongside it.

for a middle

Explain the exact-match alias, why deep imports escape it, and how the extension order makes .web files override shared ones.

for a senior

Cover the practical failures: untranspiled dependencies, deep imports in libraries, and a web-first extension list that includes the TypeScript variants.

for a principal

Judge whether a hand-rolled webpack setup is worth maintaining against a framework that owns the alias and resolution for every platform.

## What the alias does A React Native codebase imports its primitives from one package: `import { View, Text } from 'react-native'`. **React Native Web** (`react-native-web`, 0.21 here) exports the same names implemented with React DOM. A web build therefore does not change the application code; it changes **module resolution**, so the bundler hands the web implementation to every file that asks for `react-native`. With webpack the documented setup is a single alias: ```javascript // webpack.config.js module.exports = { resolve: { alias: { 'react-native$': 'react-native-web' }, extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'] } }; ``` `react-dom` must be installed too, because React Native Web renders through it. ## Why the key ends in a dollar sign In webpack's alias syntax, a trailing `$` means **exact match**. Only the bare specifier `react-native` is replaced: | Import in source | Resolves to | |---|---| | `from 'react-native'` | `react-native-web` | | `from 'react-native/Libraries/Utilities/codegenNativeComponent'` | React Native's own file, not aliased | Without the `$`, webpack would rewrite deep paths into `react-native-web/Libraries/...`, which do not exist, and the build would fail with confusing errors. With it, deep imports reach React Native's native source (Flow types, native module lookups) and fail anyway. The real fix is to have **no deep imports**: in React Native 0.87 they are already a type error under the Strict TypeScript API, and React Native Web cannot serve them. ## Why .web.js comes first `resolve.extensions` is an ordered list, and the resolver takes the **first file that exists**: 1. `import Button from './Button'` looks for `Button.web.tsx`, `Button.web.ts`, then `Button.tsx`, and so on. 2. If `Button.web.tsx` exists, it wins; the native file is never read. 3. Reverse the order and the shared `Button.tsx` would win every time, silently ignoring every web override. The same order must also include `.ts` and `.tsx` for a TypeScript codebase, each with its `.web.` variant first. ## The untranspiled-package problem React Native projects are normally bundled by Metro, which transpiles `node_modules` with React Native's Babel preset. Webpack setups usually skip `node_modules`. Many React Native libraries publish **untranspiled source** (JSX, Flow annotations, modern syntax), so a web build must: - include those specific packages in `babel-loader`'s `include` list; - use `@react-native/babel-preset` (the preset the React Native Web docs recommend) so the syntax matches what Metro would accept; - add the `react-native-web` Babel plugin for pruning unused exports. ## Diagnosing a broken alias | Symptom in the web build | Usual cause | |---|---| | Syntax error inside a `node_modules` React Native package | the package ships untranspiled JSX or Flow and is not in the loader's `include` list | | "Module not found" for a `react-native/Libraries/...` path | a deep import, which the exact-match alias deliberately leaves alone | | The web shows the native variant of a component | `.js` or `.tsx` is listed before its `.web.` counterpart | | Two copies of React Native Web in the bundle | a library depends on a different `react-native-web` version; dedupe it | | `undefined` is not a component at runtime | the import names an API React Native Web does not export | Reading the resolved path of the failing module (webpack's stats or the error's import trace) usually points straight at one of these rows. ## Checklist for a first web build - `react-dom` and `react-native-web` installed at versions matching the app's React. - `'react-native$'` alias and web-first extension order. - `babel-loader` covering app source and untranspiled dependencies. - An image loader for static assets imported with `require`. - A web entry (for example `index.web.js`) that registers the app with `AppRegistry` and runs it into a DOM element. - HTML root styles that give the app full height, since a root `View` with `flex: 1` needs a sized parent. ## Where this sits among the alternatives The same idea appears in every toolchain: Babel's module-resolver can do the alias at compile time, Jest can map it with `moduleNameMapper`, Node can do it with a module-alias package for server rendering, and Expo does it inside its Metro configuration so an Expo app needs no alias at all.

  • With webpack aliasing 'react-native$' to react-native-web, what happens to import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'?
    It is not aliased, because the `$` restricts the alias to the exact specifier. Webpack resolves React Native's own file, which is Flow-typed, native-only code, so the web build fails or the module breaks at runtime. The fix is to keep such imports in native-only files (`.native.tsx`) so the web bundle never reaches them.
  • In a webpack setup for React Native Web, why must some node_modules packages be passed through babel-loader?
    Because many React Native libraries publish untranspiled JSX, Flow or modern syntax, relying on Metro to transpile them. Webpack configurations usually exclude `node_modules`, so those packages fail to parse. Add each one to the loader's `include` list and use `@react-native/babel-preset` so it is compiled as Metro would.

saying these in an interview costs you the question

  • Without the $ the alias is safer because it also covers deep imports.
  • The order of resolve.extensions does not matter; webpack picks the most specific file.
  • The alias requires changing every import to react-native-web in source.
  • Deep imports from react-native/Libraries work on the web once the alias is set.
  • react-dom is optional because react-native-web renders by itself.
open as a page

To serve an Expo app's sign-up flow in a browser, what must be installed, and how does Expo alias react-native to react-native-web on Metro?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Install react-dom, react-native-web and @expo/metro-runtime with npx expo install, then run npx expo start --web. Expo CLI's Metro resolver aliases react-native to react-native-web for the web platform, so no webpack or Babel alias is needed.

open as a page

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%

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.

open as a page

When server-rendering a React Native Web sign-up page, how do AppRegistry.getApplication and getStyleElement produce HTML with styles, and why collect styles after rendering?

level: seniorimportance: nice to knowfreq 15%

basics

~10 s

AppRegistry.getApplication returns the app element and getStyleElement. Render the element to HTML first, then call getStyleElement, which reads React Native Web's style sheet at that moment, so it contains every rule the render needed.

open as a page