Files
react-native/scripts/build/README.md
Alex Hunt cc9164c337 Move JavaScript exports rewrites to publishConfig/prepack (#54857)
Summary:
### Motivation

Updates the shared JavaScript build setup to use the modern `publishConfig` convention.

This:

- Simplifies the build script.
- Makes the production values for `"exports"` more understandable in place (especially by separating from exports conditions).
- Prevents us from creating a dirty file state when running `yarn build`.

### Changes

- Add `publishConfig` to each `package.json` listing production `"exports"` targets.
- Add `scripts/build/prepack.js` script to action `publishConfig` (now on `npm pack`, `npm publish` exclusively).
- Remove `"exports"` rewriting (and un-rewriting safeguards) from build script.

**Note on `"prepack"`**

Slightly unfortunately, `publishConfig` doesn't work consistently between package managers currently, including npm — so this does not work implicitly (but may in future).

We're instead following `publishConfig` as a convention, and explicitly implementing a full copy (theoretically forking us towards pnpm and Yarn v4's approach).

However, I believe this is:

- Worthwhile, for the motivations above — and in particular being able to understand the final shape of `"exports"` (independent from the dimension of conditional exports, which may come into play later).
- Completely inspectable/maintainable as an explicit implementation (`scripts/build/prepack.js`).

Changelog: [Internal]

Pull Request resolved: https://github.com/facebook/react-native/pull/54857

Test Plan:
### CI

✅ GitHub Actions

### End-to-end release test script

(Note: Rebased on `0.83-stable` when tested)

```
yarn test-release-local -t "RNTestProject" -p "iOS" -c $GITHUB_TOKEN
```

{F1984106139}

✅ Test script runs `npm publish` on packages to a local proxy.

{F1984106146}

✅ Installed packages have `publishConfig` `"exports"` values applied

NOTE: ⬆️ This is **exactly** the same output as before.

 {F1984106148}

✅ `/tmp/RNTestProject` runs using built + proxy-published + proxy-installed packages

Reviewed By: cipolleschi

Differential Revision: D88963450

Pulled By: huntie

fbshipit-source-id: f328252cf93a1f1039b79d7f369d1e6e7e5b4b52
2025-12-12 04:08:59 -08:00

3.5 KiB

scripts/build

Shared build setup for the React Native monorepo.

Overview

These scripts form the modern build setup for JavaScript (Flow) packages in react-native, exposed as yarn build.

Tip

Generally, React Native maintainers do not need to run yarn build, as all packages will run from source during development. Please continue reading if you are adding/removing a package or modifying its build configuration.

Key info

  • Which packages are included?
    • Currently, only Node.js-targeting packages are included, configured in config.js.
    • We don't yet include runtime packages (targeting Metro). These are instead transformed in user space via @react-native/babel-preset.
  • When does the build run?
    • Packages are built in CI workflows — both for integration/E2E tests, and before publishing to npm.

Usage

💡 Reminder: 99% of the time, there is no need to use yarn build, as all packages will run from source during development.

Build commands are exposed as npm scripts at the repo root.

# Build all packages
yarn build

# Build a specific package
yarn build dev-middleware

# Clean build directories
yarn clean

Once built, developing in the monorepo should continue to work — now using the compiled version of each package.

Configuration

Monorepo packages must be opted in for build, configured in config.js (where build options are also documented).

const buildConfig /*: BuildConfig */ = {
  'packages': {
    'dev-middleware': {
      emitTypeScriptDefs: true,
      target: 'node',
    },
    ...

Required package structure

Opting a package into the yarn build setup requires a strict file layout. This is done to simplify config and to force consistency across the monorepo.

packages/
  example-pkg/
    src/             # All source files
      index.js       # Entry point wrapper file (calls babel-register.js) (compiled away)
      index.flow.js  # Entry point implementation in Flow
      [other files]
    package.json     # Includes "exports" field, ideally only src/index.js

Notes:

  • We make use of "wrapper files" (.js → .js.flow) for each package entry point, to enable running from source with zero config. To validate these, package entry points must be explicitly defined via "exports".
  • To minimize complexity, prefer only a single entry of {".": "src/index.js"} in "exports" for new packages.

Build behavior

Running yarn build will compile each package following the below steps, depending on the configured target and other build options.

  • Create a dist/ directory, replicating each source file under src/:
    • For every @flow file, strip Flow annotations using flow-api-extractor.
    • For each entry point in "exports", remove the .js wrapper file and compile from the .flow.js source.
  • If configured, emit a Flow (.js.flow) or TypeScript (.d.ts) type definition file per source file, using flow-api-extractor.

Together, this might look like the following:

packages/
  example-pkg/
    dist/
      index.js       # Compiled source file (from index.flow.js)
      index.js.flow  # Flow definition file
      index.d.ts     # TypeScript definition file
      [other transformed files]
    package.json     # "publishConfig" will override exports to "dist/" on publish

Link: Example dist/ output on npm.