From ab8120a7fa0aa56d1f20a8ad8a7933220d3dd5ab Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Tue, 14 Jan 2025 07:00:52 -0800 Subject: [PATCH] Bootstrap experimental types build for main package (#48661) Summary: Changelog: [Internal] Differential Revision: D68102875 --- scripts/build/build.js | 33 ++++++++++++++--- scripts/build/buildRNTypes.js | 67 +++++++++++++++++++++++++++++++++++ scripts/build/config.js | 12 ++++++- 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 scripts/build/buildRNTypes.js diff --git a/scripts/build/build.js b/scripts/build/build.js index 4cb9de68a0a29c..da52c3d3fc4e76 100644 --- a/scripts/build/build.js +++ b/scripts/build/build.js @@ -10,6 +10,7 @@ */ const {PACKAGES_DIR, REPO_ROOT} = require('../consts'); +const buildRNTypes = require('./buildRNTypes'); const { buildConfig, getBabelConfig, @@ -35,15 +36,16 @@ const IGNORE_PATTERN = '**/__{tests,mocks,fixtures}__/**'; const config = { allowPositionals: true, options: { - help: {type: 'boolean'}, check: {type: 'boolean'}, + experimentalBuildRNTypes: {type: 'boolean'}, + help: {type: 'boolean'}, }, }; async function build() { const { positionals: packageNames, - values: {help, check}, + values: {check, experimentalBuildRNTypes, help}, } = parseArgs(config); if (help) { @@ -54,6 +56,13 @@ async function build() { By default, builds all packages defined in ./scripts/build/config.js. If a a package list is provided, builds only those specified. + + Options: + --check Validate that no build artifacts have been accidentally + committed. + --experimentalBuildRNTypes + [Experimental] Enable source code -> type translation + output for the react-native package. `); process.exitCode = 0; return; @@ -69,6 +78,16 @@ async function build() { let ok = true; for (const packageName of packagesToBuild) { + if (packageName === 'react-native' && !experimentalBuildRNTypes) { + if (packagesToBuild.length === 1) { + console.warn( + 'Building the react-native package must be enabled using ' + + '--experimentalBuildRNTypes.', + ); + } + continue; + } + if (check) { ok &&= await checkPackage(packageName); } else { @@ -83,7 +102,7 @@ async function checkPackage(packageName /*: string */) /*: Promise */ { const artifacts = await exportedBuildArtifacts(packageName); if (artifacts.length > 0) { console.log( - `${chalk.bgRed(packageName)}: has been build and the ${chalk.bold('build artifacts')} committed to the repository. This will break Flow checks.`, + `${chalk.bgRed(packageName)}: has been built and the ${chalk.bold('build artifacts')} committed to the repository. This will break Flow checks.`, ); return false; } @@ -91,7 +110,13 @@ async function checkPackage(packageName /*: string */) /*: Promise */ { } async function buildPackage(packageName /*: string */) { - const {emitTypeScriptDefs} = getBuildOptions(packageName); + const {target, emitTypeScriptDefs} = getBuildOptions(packageName); + + if (target === 'react-native-emit-types') { + await buildRNTypes(); + return; + } + const entryPoints = await getEntryPoints(packageName); const files = glob diff --git a/scripts/build/buildRNTypes.js b/scripts/build/buildRNTypes.js new file mode 100644 index 00000000000000..b9da1a64aafdb0 --- /dev/null +++ b/scripts/build/buildRNTypes.js @@ -0,0 +1,67 @@ +/** + * 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 {PACKAGES_DIR} = require('../consts'); +const translate = require('flow-api-translator'); +const {promises: fs} = require('fs'); +const glob = require('glob'); +const path = require('path'); + +const TYPES_DIR = 'new-types'; +const PACKAGE_NAME = 'react-native'; + +// Start with Animated only as it using the export syntax. +const PATHS = ['Libraries/Animated']; + +async function buildRNTypes() { + const files = PATHS.flatMap(src_path => + glob.sync(path.resolve(PACKAGES_DIR, PACKAGE_NAME, src_path, '**/*.js'), { + nodir: true, + }), + ); + + console.log('Building RN types...'); + for (const file of files) { + // Don't build tests + if (/\/__tests__\//.test(file)) { + continue; + } + + const buildPath = getBuildPath(file); + const source = await fs.readFile(file, 'utf-8'); + const prettierConfig = {parser: 'babel'}; + + await fs.mkdir(path.dirname(buildPath), {recursive: true}); + + try { + await fs.writeFile( + buildPath, + await translate.translateFlowToTSDef(source, prettierConfig), + ); + } catch (e) { + console.error(`Failed to build ${file.replace(PACKAGES_DIR, '')}`); + } + } +} + +function getBuildPath(file /*: string */) /*: string */ { + const packageDir = path.join(PACKAGES_DIR, PACKAGE_NAME); + + return path.join( + packageDir, + file + .replace(packageDir, TYPES_DIR) + .replace(/\.flow\.js$/, '.js') + .replace(/\.js$/, '.d.ts'), + ); +} + +module.exports = buildRNTypes; diff --git a/scripts/build/config.js b/scripts/build/config.js index e889eb4d1b7986..cf40abddbeffdb 100644 --- a/scripts/build/config.js +++ b/scripts/build/config.js @@ -18,7 +18,12 @@ const {ModuleResolutionKind} = require('typescript'); /*:: export type BuildOptions = $ReadOnly<{ // The target runtime to compile for. - target: 'node', + target: + // Node.js + 'node' | + // [Experimental] react-native package only: Skip build, emit translated + // TypeScript types for 3P use. + 'react-native-emit-types', // Whether to emit Flow definition files (.js.flow) (default: true). emitFlowDefs?: boolean, @@ -58,6 +63,9 @@ const buildConfig /*: BuildConfig */ = { emitTypeScriptDefs: true, target: 'node', }, + 'react-native': { + target: 'react-native-emit-types', + }, }, }; @@ -83,6 +91,8 @@ function getBabelConfig( switch (target) { case 'node': return require('./babel/node.config.js'); + case 'react-native-emit-types': + return {}; // stub } }