From c67e1fe42fe2b31eb37b8aee648269f176401c62 Mon Sep 17 00:00:00 2001 From: empyrical Date: Fri, 8 May 2020 15:39:11 -0700 Subject: [PATCH] Codegen: Add prepublish script to build Flow files (#28827) Summary: *This is a follow-up to https://github.com/facebook/react-native/issues/28645, redone using a build script based off of Metro's build script instead of using `flow-remove-types` and `flow-copy-source`.* This pull request adds a build step to `react-native-codegen` that builds the Flow-annotated JS files so that users of the NPM module `react-native-codegen` do not need to use require hooks to be able to import it. A new build script, `scripts/build.js` is added that builds every JS file in `src/` into a `lib/` folder, and also copies over the original Flow annotated files to `lib/` with a `.js.flow` extension, so users of `react-native-codegen` can still typecheck against it using Flow. The shell scripts in `src` are also copied over. It is based off of the [build script from Metro](https://github.com/facebook/metro/blob/00867816eb9b2f69c8af9cebb523e9e4d280671a/scripts/build.js) ## Changelog [General] [Added] - Codegen: Add prepublish script to build Flow files Pull Request resolved: https://github.com/facebook/react-native/pull/28827 Test Plan: I am able to make use of the Codegen scripts without needing to use the `flow-node` CLI or the `flow-remove-types/register` require hook. Reviewed By: cpojer Differential Revision: D21412173 Pulled By: hramos fbshipit-source-id: 26ae67cdd04652ca4700a069a234a25558773cb1 --- .eslintignore | 1 + .gitignore | 3 +- packages/react-native-codegen/.babelrc | 11 ++ packages/react-native-codegen/.prettierrc | 7 ++ packages/react-native-codegen/package.json | 22 +++- .../react-native-codegen/scripts/build.js | 117 ++++++++++++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 packages/react-native-codegen/.babelrc create mode 100644 packages/react-native-codegen/.prettierrc create mode 100644 packages/react-native-codegen/scripts/build.js diff --git a/.eslintignore b/.eslintignore index bd780563158..a00ba35784f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,4 @@ Libraries/vendor/**/* packages/*/node_modules pr-inactivity-bookmarklet.js question-bookmarklet.js +packages/react-native-codegen/lib diff --git a/.gitignore b/.gitignore index 73882e2b7b5..43810959f7e 100644 --- a/.gitignore +++ b/.gitignore @@ -94,9 +94,10 @@ RNTester/Pods/* !RNTester/Pods/__offline_mirrors # react-native-codegen +/packages/react-native-codegen/lib /ReactCommon/fabric/components/rncore/ -/schema-rncore.json /schema-native-modules.json +/schema-rncore.json # Visual studio .vscode diff --git a/packages/react-native-codegen/.babelrc b/packages/react-native-codegen/.babelrc new file mode 100644 index 00000000000..b8ac81899b7 --- /dev/null +++ b/packages/react-native-codegen/.babelrc @@ -0,0 +1,11 @@ +{ + "plugins": [ + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-transform-async-to-generator", + "@babel/plugin-transform-destructuring", + "@babel/plugin-transform-flow-strip-types", + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-optional-chaining" + ] +} diff --git a/packages/react-native-codegen/.prettierrc b/packages/react-native-codegen/.prettierrc new file mode 100644 index 00000000000..20374fd919f --- /dev/null +++ b/packages/react-native-codegen/.prettierrc @@ -0,0 +1,7 @@ +{ + "requirePragma": true, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": false, + "jsxBracketSameLine": true +} diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 3c77e91f8bc..2bcba86fdef 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -7,13 +7,33 @@ "type": "git", "url": "git@github.com:facebook/react-native.git" }, + "scripts": { + "build": "yarn clean && node scripts/build.js --verbose", + "clean": "rm -rf lib", + "prepublish": "yarn run build" + }, "license": "MIT", "files": [ - "src" + "lib" ], "dependencies": { "flow-parser": "^0.121.0", "jscodeshift": "^0.9.0", "nullthrows": "^1.1.1" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "chalk": "^4.0.0", + "glob": "^7.1.1", + "micromatch": "^4.0.2", + "mkdirp": "^0.5.1", + "prettier": "1.19.1" } } diff --git a/packages/react-native-codegen/scripts/build.js b/packages/react-native-codegen/scripts/build.js new file mode 100644 index 00000000000..9eefd4fdbb8 --- /dev/null +++ b/packages/react-native-codegen/scripts/build.js @@ -0,0 +1,117 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +/** + * script to build (transpile) files. + * + * Based off of the build script from Metro, and tweaked to run in just one + * package instead of in a monorepo. Just run `build.js` and the JS files in + * `src/` will be built in `lib/`, and the original source files will be copied + * over as `Example.js.flow`, so consumers of this module can still make use of + * type checking. + * + * Call this script with the `--verbose` flag to show the full output of this + * script. + */ + +'use strict'; + +const babel = require('@babel/core'); +const chalk = require('chalk'); +const fs = require('fs'); +const glob = require('glob'); +const micromatch = require('micromatch'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const prettier = require('prettier'); +const prettierConfig = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '..', '.prettierrc'), 'utf8'), +); + +const SRC_DIR = 'src'; +const BUILD_DIR = 'lib'; +const JS_FILES_PATTERN = '**/*.js'; +const IGNORE_PATTERN = '**/__tests__/**'; +const PACKAGE_DIR = path.resolve(__dirname, '../'); + +const fixedWidth = str => { + const WIDTH = 80; + const strs = str.match(new RegExp(`(.{1,${WIDTH}})`, 'g')) || [str]; + let lastString = strs[strs.length - 1]; + if (lastString.length < WIDTH) { + lastString += Array(WIDTH - lastString.length).join(chalk.dim('.')); + } + return strs + .slice(0, -1) + .concat(lastString) + .join('\n'); +}; + +function getBuildPath(file, buildFolder) { + const pkgSrcPath = path.resolve(PACKAGE_DIR, SRC_DIR); + const pkgBuildPath = path.resolve(PACKAGE_DIR, BUILD_DIR); + const relativeToSrcPath = path.relative(pkgSrcPath, file); + return path.resolve(pkgBuildPath, relativeToSrcPath); +} + +function buildFile(file, silent) { + const destPath = getBuildPath(file, BUILD_DIR); + + mkdirp.sync(path.dirname(destPath)); + if (micromatch.isMatch(file, IGNORE_PATTERN)) { + silent || + process.stdout.write( + chalk.dim(' \u2022 ') + + path.relative(PACKAGE_DIR, file) + + ' (ignore)\n', + ); + } else if (!micromatch.isMatch(file, JS_FILES_PATTERN)) { + fs.createReadStream(file).pipe(fs.createWriteStream(destPath)); + silent || + process.stdout.write( + chalk.red(' \u2022 ') + + path.relative(PACKAGE_DIR, file) + + chalk.red(' \u21D2 ') + + path.relative(PACKAGE_DIR, destPath) + + ' (copy)' + + '\n', + ); + } else { + const transformed = prettier.format( + babel.transformFileSync(file, {}).code, + { + ...prettierConfig, + parser: 'babel', + }, + ); + fs.writeFileSync(destPath, transformed); + const source = fs.readFileSync(file).toString('utf-8'); + if (/\@flow/.test(source)) { + fs.createReadStream(file).pipe(fs.createWriteStream(destPath + '.flow')); + } + silent || + process.stdout.write( + chalk.green(' \u2022 ') + + path.relative(PACKAGE_DIR, file) + + chalk.green(' \u21D2 ') + + path.relative(PACKAGE_DIR, destPath) + + '\n', + ); + } +} + +const srcDir = path.resolve(__dirname, '..', SRC_DIR); +const pattern = path.resolve(srcDir, '**/*'); +const files = glob.sync(pattern, {nodir: true}); + +process.stdout.write(fixedWidth(`${path.basename(PACKAGE_DIR)}\n`)); + +files.forEach(file => buildFile(file, !process.argv.includes('--verbose'))); + +process.stdout.write(`[ ${chalk.green('OK')} ]\n`);