Skip to content

Commit c1a650d

Browse files
huntiefacebook-github-bot
authored andcommitted
Add "noflow" entry point mode to react-native
Summary: Reference impl for react-native-community/discussions-and-proposals#949 (exporting, then abandoning). Differential Revision: D82653038
1 parent 8dfd548 commit c1a650d

6 files changed

Lines changed: 167 additions & 8 deletions

File tree

.gitignore

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,13 @@ fix_*.patch
174174
/private/react-native-fantom/.out/
175175
/private/react-native-fantom/tester/build/
176176

177-
# [Experimental] Generated TS type definitions
178-
/packages/**/types_generated/
177+
# JS build output
178+
/packages/*/dist/
179+
/packages/*/dist_noflow/
180+
/packages/debugger-shell/build/
181+
182+
# Generated TS type definitions
183+
/packages/*/types_generated/
179184

180185
# Python
181186
__pycache__/

packages/react-native/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
"exports": {
3232
".": {
3333
"react-native-strict-api": "./types_generated/index.d.ts",
34+
"react-native-noflow": "./dist_noflow/index.js",
3435
"types": "./types/index.d.ts",
3536
"default": "./index.js"
3637
},
3738
"./*": {
3839
"react-native-strict-api": null,
40+
"react-native-noflow": "./dist_noflow/*.js",
3941
"types": "./*.d.ts",
4042
"default": "./*.js"
4143
},
@@ -75,6 +77,7 @@
7577
"files": [
7678
"build.gradle.kts",
7779
"cli.js",
80+
"dist_noflow",
7881
"flow",
7982
"gradle.properties",
8083
"gradle/libs.versions.toml",

packages/virtualized-lists/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@
2222
"exports": {
2323
".": {
2424
"react-native-strict-api": "./types_generated/index.d.ts",
25+
"react-native-noflow": "./dist_noflow/index.js",
2526
"types": "./index.d.ts",
2627
"default": "./index.js"
2728
},
2829
"./*": {
2930
"types": null,
31+
"react-native-noflow": "./dist_noflow/*.js",
3032
"default": "./*.js"
3133
},
3234
"./package.json": "./package.json"
3335
},
3436
"files": [
37+
"dist_noflow",
3538
"index.js",
3639
"index.d.ts",
3740
"Lists",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
// A config which strips and transforms Flow features, to be received by either
12+
// Flow/JS compatible Babel presets.
13+
14+
// IMPORTANT: We've given no public guarantee that we will preserve this
15+
// transform. The app/frameworks requirement is still to apply all of
16+
// @react-native/babel-preset.
17+
18+
import type {BabelCoreOptions} from '@babel/core';
19+
20+
const config: BabelCoreOptions = {
21+
sourceMaps: true,
22+
presets: [require.resolve('@babel/preset-flow')],
23+
plugins: [require.resolve('babel-plugin-syntax-hermes-parser')],
24+
};
25+
26+
module.exports = config;

scripts/build/build.js

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
require('../shared/babelRegister').registerForScript();
1212

13+
/*::
14+
import type {BuildOptions} from './config';
15+
*/
16+
1317
const {PACKAGES_DIR, REPO_ROOT} = require('../shared/consts');
1418
const {
1519
buildConfig,
@@ -30,8 +34,9 @@ const {parseArgs, styleText} = require('util');
3034

3135
const SRC_DIR = 'src';
3236
const BUILD_DIR = 'dist';
37+
const NOFLOW_BUILD_DIR = 'dist_noflow';
3338
const JS_FILES_PATTERN = '**/*.js';
34-
const IGNORE_PATTERN = '**/__{tests,mocks,fixtures}__/**';
39+
const IGNORE_PATTERN = '**/__{tests,mocks,fixtures,flowtests}__/**';
3540

3641
const config = {
3742
allowPositionals: true,
@@ -77,6 +82,13 @@ async function build() {
7782

7883
let ok = true;
7984
for (const packageName of packagesToBuild) {
85+
const {target} = getBuildOptions(packageName);
86+
87+
if (target === 'noflow') {
88+
await buildNoFlowTarget(packageName);
89+
continue;
90+
}
91+
8092
await buildPackage(packageName, prepack);
8193
}
8294

@@ -163,8 +175,9 @@ async function buildFile(
163175
) {
164176
const {silent = false} = options;
165177
const packageName = getPackageName(file);
166-
const buildPath = getBuildPath(file);
167-
const {emitFlowDefs, emitTypeScriptDefs} = getBuildOptions(packageName);
178+
const buildOptions = getBuildOptions(packageName);
179+
const {emitFlowDefs, emitTypeScriptDefs} = buildOptions;
180+
const buildPath = getBuildPath(file, buildOptions);
168181

169182
const logResult = ({copied, desc} /*: {copied: boolean, desc?: string} */) =>
170183
silent ||
@@ -226,6 +239,87 @@ async function buildFile(
226239
logResult({copied: true});
227240
}
228241

242+
async function buildNoFlowTarget(packageName /*: string */) {
243+
try {
244+
const buildOptions = getBuildOptions(packageName);
245+
const {srcOverride} = buildOptions;
246+
247+
process.stdout.write(
248+
`${packageName} ${styleText('yellow', '(noflow)')} ${styleText('dim', '.').repeat(63 - packageName.length)} `,
249+
);
250+
251+
const files = glob.sync(
252+
path.resolve(
253+
PACKAGES_DIR,
254+
packageName,
255+
...(srcOverride != null
256+
? ['{', srcOverride, '}']
257+
: [SRC_DIR, '**/*.js']),
258+
),
259+
{
260+
nodir: true,
261+
ignore: [IGNORE_PATTERN],
262+
},
263+
);
264+
265+
for (const file of files) {
266+
const filePath = path.normalize(file);
267+
const buildPath = getBuildPath(filePath, buildOptions);
268+
const prettierConfig = {parser: 'babel'};
269+
const source = await fs.readFile(file, 'utf-8');
270+
271+
await fs.mkdir(path.dirname(buildPath), {recursive: true});
272+
273+
// If file contains `@noflow`, copy only
274+
if (/@noflow/.test(source)) {
275+
await fs.copyFile(file, buildPath);
276+
continue;
277+
}
278+
279+
// Apply Flow transforms
280+
const babelResult = await babel.transformFileAsync(
281+
filePath,
282+
getBabelConfig(packageName),
283+
);
284+
const transformed = await prettier.format(
285+
babelResult.code,
286+
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
287+
* https://fburl.com/workplace/6291gfvu */
288+
prettierConfig,
289+
);
290+
291+
// Write transformed file with source map comment
292+
const relativeSourcePath = path.relative(
293+
path.dirname(buildPath),
294+
filePath,
295+
);
296+
const sourceMapComment = `\n//# sourceMappingURL=${path.basename(buildPath)}.map`;
297+
await fs.writeFile(buildPath, transformed + sourceMapComment);
298+
299+
// Write source map file
300+
if (babelResult.map) {
301+
const sourceMapPath = buildPath + '.map';
302+
const sourceMap = {
303+
...babelResult.map,
304+
sources: [relativeSourcePath],
305+
};
306+
await fs.writeFile(sourceMapPath, JSON.stringify(sourceMap, null, 2));
307+
}
308+
}
309+
310+
process.stdout.write(
311+
styleText(['reset', 'inverse', 'bold', 'green'], ' DONE '),
312+
);
313+
} catch (e) {
314+
process.stdout.write(
315+
styleText(['reset', 'inverse', 'bold', 'red'], ' FAIL ') + '\n',
316+
);
317+
throw e;
318+
} finally {
319+
process.stdout.write('\n');
320+
}
321+
}
322+
229323
/*::
230324
type PackageJson = {
231325
name: string,
@@ -356,13 +450,19 @@ function getPackageName(file /*: string */) /*: string */ {
356450
return path.relative(PACKAGES_DIR, file).split(path.sep)[0];
357451
}
358452

359-
function getBuildPath(file /*: string */) /*: string */ {
453+
function getBuildPath(
454+
file /*: string */,
455+
{srcOverride, target} /*: BuildOptions */,
456+
) /*: string */ {
360457
const packageDir = path.join(PACKAGES_DIR, getPackageName(file));
361458

362459
return path.join(
363460
packageDir,
364461
file
365-
.replace(path.join(packageDir, SRC_DIR), BUILD_DIR)
462+
.replace(
463+
path.join(packageDir, srcOverride ? '' : SRC_DIR),
464+
target === 'noflow' ? NOFLOW_BUILD_DIR : BUILD_DIR,
465+
)
366466
.replace('.flow.js', '.js'),
367467
);
368468
}

scripts/build/config.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,24 @@ const {ModuleResolutionKind} = require('typescript');
1414

1515
export type BuildOptions = Readonly<{
1616
// The target runtime to compile for.
17-
target: 'node',
17+
target:
18+
| 'node'
19+
// A special compile target aligning with the "react-native-noflow" exports
20+
// condition. This entry point allows compatible native parsers (e.g. Bun
21+
// and SWC) to consume React Native without Flow types. Output will be stored
22+
// in `dist_noflow/`.
23+
| 'noflow',
1824

1925
// Whether to emit Flow definition files (.js.flow) (default: true).
2026
emitFlowDefs?: boolean,
2127

2228
// Whether to emit TypeScript definition files (.d.ts) (default: false).
2329
emitTypeScriptDefs?: boolean,
30+
31+
// Source dir glob override (default: 'src/**/*'). This is intended to provide
32+
// compatibility for the react-native package only. This setting is ignored
33+
// unless using the 'noflow' compile target.
34+
srcOverride?: string | null,
2435
}>;
2536

2637
export type BuildConfig = Readonly<{
@@ -57,16 +68,25 @@ const buildConfig: BuildConfig = {
5768
emitTypeScriptDefs: true,
5869
target: 'node',
5970
},
71+
'react-native': {
72+
srcOverride: 'Libraries/**/*.js,src/**/*.js,index.js',
73+
target: 'noflow',
74+
},
6075
'react-native-compatibility-check': {
6176
emitTypeScriptDefs: true,
6277
target: 'node',
6378
},
79+
'virtualized-lists': {
80+
srcOverride: 'Lists/**/*.js,Utilities/**/*.js,index.js',
81+
target: 'noflow',
82+
},
6483
},
6584
};
6685

6786
const defaultBuildOptions = {
6887
emitFlowDefs: true,
6988
emitTypeScriptDefs: false,
89+
srcOverride: null,
7090
};
7191

7292
function getBuildOptions(
@@ -86,6 +106,8 @@ function getBabelConfig(
86106
switch (target) {
87107
case 'node':
88108
return require('./babel/node.config.js');
109+
case 'noflow':
110+
return require('./babel/noflow.config.js');
89111
}
90112
}
91113

0 commit comments

Comments
 (0)