Skip to content

Commit

Permalink
Add shared monorepo build setup (facebook#38718)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#38718

> NOTE: Replaces facebook#38240

## Context

RFC: Decoupling Flipper from React Native core: react-native-community/discussions-and-proposals#641

## Changes

To support incoming new React Native packages around debugging (including migrating over [`react-native-community/cli-plugin-metro`](https://github.com/react-native-community/cli/tree/main/packages/cli-plugin-metro)) — which target Node.js and require a build step, this PR adds a minimal shared build setup across the `react-native` monorepo.

The setup is closely inspired/based on the build scripts in Jest, Metro, and React Native CLI — and is a simple set of script wrappers around Babel. These are available as build commands at the root of the repo:

- `yarn build` — Builds all configured packages. Functionally, this:
  - Outputs a `dist/` directory with built files.
  - Rewrites package.json `"exports"` to update every `./src/*` reference to `./dist/*` (source of truth).
- `scripts/build/babel-register.js` — Allows running all Node.js entry points from source, similar to the current setup in [facebook/metro](https://github.com/facebook/metro). (Example entry point file in this PR: `packages/dev-middleware/src/index.js`)

Build configuration (i.e. Babel config) is shared as a set standard across the monorepo, and **packages are opted-in to requiring a build**, configured in `scripts/build.config.js`.

```
const buildConfig /*: BuildConfig */ = {
  // The packages to include for build and their build options
  packages: {
    'dev-middleware': {target: 'node'},
  },
};
```

For now, there is a single `target: 'node'` option — this is necessary as `react-native`, unlike the above other projects, is a repository with packages targeting several runtimes. We may, in future, introduce a build step for other, non-Node, packages — which may be useful for things such as auto-generated TypeScript definitions.

 {F1043312771}

**Differences from the Metro setup**

- References (and compiles out) repo-local `scripts/build/babel-register.js` — removing need for an npm-published dependency.

## Current integration points

- **CircleCI** — `yarn build` is added to the `build_npm_package` and `find_and_publish_bumped_packages` jobs.

**New Node.js package(s) are not load bearing quite yet**: There are not yet any built packages added to the dependencies of `packages/react-native/`, so this will be further tested in a later PR (and is actively being done in an internal commit stack).

### Alternative designs

**Per-package config file**

Replace `scripts/build/config.js` with a package-defined key in in `package.json`, similar to Jest's [`publishConfig`](https://github.com/jestjs/jest/blob/1f019afdcdfc54a6664908bb45f343db4e3d0848/packages/jest-cli/package.json#L87C3-L89C4).

```
"buildConfig": {
  "type": "node"
},
```

This would be the only customisation required, with a single Babel config still standardised. Another option this might receive in future is `enableTypeScriptCodgeen`.

**Rollup**

More sophisticated build tool for Node.js, used by the React codebase (albeit within a custom script setup as well).

**Lerna and Nx**

- Most sophisticated setup enabling caching and optimised cloud runs.
- Probably the most likely thing we'll move towards at a later stage.

Changelog: [Internal]

Differential Revision: D47760330

fbshipit-source-id: 2c0128f7a3c54e222d3f640f8d2bf48264f23813
  • Loading branch information
huntie authored and facebook-github-bot committed Aug 2, 2023
1 parent 1f24750 commit e4c22b6
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 35 deletions.
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ commands:
- ~/.cache/yarn
key: << parameters.yarn_base_cache_key >>-{{ arch }}-{{ checksum "yarn.lock" }}

build_packages:
steps:
- run:
name: Build packages
command: yarn build

brew_install:
parameters:
package:
Expand Down Expand Up @@ -1590,6 +1596,7 @@ jobs:
cp $HERMES_WS_DIR/hermes-runtime-darwin/hermes-ios-release.tar.gz ./packages/react-native/ReactAndroid/external-artifacts/artifacts/hermes-ios-release.tar.gz
- run_yarn
- build_packages
- download_gradle_dependencies

# START: Stables and nightlies
Expand Down Expand Up @@ -1685,6 +1692,7 @@ jobs:
steps:
- checkout
- run_yarn
- build_packages
- run:
name: Set NPM auth token
command: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc
Expand Down
40 changes: 40 additions & 0 deletions flow-typed/npm/parseargs_v0.11.x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

declare module '@pkgjs/parseargs' {
declare type ParseArgsOptionConfig = {
type: 'string' | 'boolean',
short?: string,
multiple?: boolean,
};

declare type ParseArgsOptionsConfig = {
[longOption: string]: ParseArgsOptionConfig,
};

declare export type ParseArgsConfig = {
strict?: boolean,
allowPositionals?: boolean,
tokens?: boolean,
options?: ParseArgsOptionsConfig,
args?: string[],
};

declare type ParsedResults = {
values: {
[longOption: string]: void | string | boolean | Array<string | boolean>,
},
positionals: string[],
...
};

declare export function parseArgs(config: ParseArgsConfig): ParsedResults;
}
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"scripts": {
"start": "cd packages/rn-tester && npm run start",
"android": "cd packages/rn-tester && npm run android",
"build": "node ./scripts/build/build.js",
"clean": "node ./scripts/build/clean.js",
"test": "jest",
"test-ci": "jest --maxWorkers=2 --ci --reporters=\"default\" --reporters=\"jest-junit\"",
"flow": "flow",
Expand Down Expand Up @@ -45,14 +47,20 @@
"@babel/eslint-parser": "^7.20.0",
"@babel/generator": "^7.20.0",
"@babel/plugin-transform-regenerator": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-flow": "^7.20.0",
"@definitelytyped/dtslint": "^0.0.127",
"@jest/create-cache-key-function": "^29.2.1",
"@pkgjs/parseargs": "^0.11.0",
"@react-native/metro-babel-transformer": "^0.73.11",
"@react-native/metro-config": "^0.73.0",
"@types/react": "^18.0.18",
"@typescript-eslint/parser": "^5.57.1",
"async": "^3.2.2",
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
"babel-plugin-transform-define": "^2.1.2",
"babel-plugin-transform-flow-enums": "^0.0.2",
"chalk": "^4.0.0",
"clang-format": "^1.8.0",
"connect": "^3.6.5",
"eslint": "^8.19.0",
Expand All @@ -70,13 +78,15 @@
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
"flow-bin": "^0.213.1",
"glob": "^7.1.1",
"hermes-eslint": "0.15.0",
"inquirer": "^7.1.0",
"jest": "^29.2.1",
"jest-junit": "^10.0.0",
"jscodeshift": "^0.14.0",
"metro-babel-register": "0.77.0",
"metro-memory-fs": "0.77.0",
"micromatch": "^4.0.4",
"mkdirp": "^0.5.1",
"mock-fs": "^5.1.4",
"prettier": "2.8.8",
Expand Down
13 changes: 0 additions & 13 deletions packages/dev-middleware/.babelrc

This file was deleted.

2 changes: 1 addition & 1 deletion packages/dev-middleware/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @flow
* @format
* @oncall react_native
*/
Expand Down
18 changes: 4 additions & 14 deletions packages/dev-middleware/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,19 @@
"directory": "packages/dev-middleware"
},
"license": "MIT",
"exports": "./dist/index.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn clean && babel src --out-dir dist",
"dev": "babel src --out-dir dist --source-maps --watch",
"clean": "rimraf dist",
"prepare": "yarn build"
},
"dependencies": {
"chrome-launcher": "^0.15.2",
"connect": "^3.6.5",
"node-fetch": "^2.2.0",
"temp-dir": "^2.0.0"
},
"devDependencies": {
"@babel/cli": "^7.20.0",
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-flow": "^7.20.0",
"rimraf": "^3.0.2"
},
"engines": {
"node": ">=18"
}
Expand Down
12 changes: 12 additions & 0 deletions packages/dev-middleware/src/index.flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

export {default as createDevMiddleware} from './createDevMiddleware';
12 changes: 10 additions & 2 deletions packages/dev-middleware/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @flow
* @format
* @oncall react_native
*/

export {default as createDevMiddleware} from './createDevMiddleware';
/*::
export type * from './index.flow';
*/

if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
require('../../../scripts/build/babel-register').registerForMonorepo();
}

module.exports = require('./index.flow');
54 changes: 54 additions & 0 deletions scripts/build/babel-register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/

const path = require('path');
const {PACKAGES_DIR} = require('./build');
const {buildConfig, getBabelConfig} = require('./config');

let isRegisteredForMonorepo = false;

/**
* Calling this function enables all Node.js packages to run from source when
* developing in the React Native repo.
*
* A call should located in each entry point file in a package (i.e. for all
* paths in "exports"), inside a special `if` condition that will be compiled
* away on build.
*
* if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
* require('../../../scripts/build/babel-register').registerForMonorepo();
* }
*/
function registerForMonorepo() {
if (isRegisteredForMonorepo) {
return;
}

for (const [packageName, {target}] of Object.entries(buildConfig.packages)) {
if (target === 'node') {
registerPackage(packageName);
}
}

isRegisteredForMonorepo = true;
}

function registerPackage(packageName /*: string */) {
const packageDir = path.join(PACKAGES_DIR, packageName);

require('@babel/register')({
...getBabelConfig(packageName),
root: packageDir,
ignore: [/\/node_modules\//],
});
}

module.exports = {registerForMonorepo};
Loading

0 comments on commit e4c22b6

Please sign in to comment.