From a32620dc3b7a0ebd53feeaf7794051705d80f49e Mon Sep 17 00:00:00 2001 From: Ives van Hoorne Date: Wed, 25 Jul 2018 05:44:40 -0700 Subject: [PATCH] Use new configuration in react-native public cli Summary: Change the public react-native CLI to use the new configuration of Metro. Reviewed By: rafeca Differential Revision: D8801217 fbshipit-source-id: 112e4812b430ebee1ed41489f803b90c182ccdb4 --- local-cli/bundle/buildBundle.js | 16 ++--- local-cli/cliEntry.js | 5 +- local-cli/core/index.js | 55 +++++++++-------- local-cli/dependencies/dependencies.js | 13 ++-- local-cli/server/runServer.js | 28 +++------ local-cli/server/server.js | 19 +++--- local-cli/util/Config.js | 85 +++++++------------------- 7 files changed, 81 insertions(+), 140 deletions(-) diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index 88c036ca49c6b1..9bc0e92255931d 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -14,15 +14,12 @@ const log = require('../util/log').out('bundle'); /* $FlowFixMe(site=react_native_oss) */ const Server = require('metro/src/Server'); -const {convert} = require('metro-config'); - /* $FlowFixMe(site=react_native_oss) */ const outputBundle = require('metro/src/shared/output/bundle'); +const {convert} = require('metro-config'); const path = require('path'); const saveAssets = require('./saveAssets'); -const {ASSET_REGISTRY_PATH} = require('../core/Constants'); - import type {RequestOptions, OutputOptions} from './types.flow'; import type {ConfigT} from 'metro-config/src/configTypes.flow'; @@ -48,6 +45,10 @@ async function buildBundle( sourceMapUrl = path.basename(sourceMapUrl); } + config.transformModulePath = args.transformer + ? path.resolve(args.transformer) + : config.transformModulePath; + const requestOpts: RequestOptions = { entryFile: args.entryFile, sourceMapUrl, @@ -56,13 +57,6 @@ async function buildBundle( platform: args.platform, }; - const transformModulePath = args.transformer - ? path.resolve(args.transformer) - : config.transformModulePath; - - config.transformModulePath = transformModulePath; - config.transformer.assetRegistryPath = ASSET_REGISTRY_PATH; - const {serverOptions} = convert.convertNewToOld(config); const server = new Server(serverOptions); diff --git a/local-cli/cliEntry.js b/local-cli/cliEntry.js index d342e9ed0d53a8..6de3fef6039c89 100644 --- a/local-cli/cliEntry.js +++ b/local-cli/cliEntry.js @@ -10,7 +10,7 @@ 'use strict'; -const config = require('./core'); +const {configPromise} = require('./core'); const assertRequiredOptions = require('./util/assertRequiredOptions'); /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error @@ -137,7 +137,8 @@ const addCommand = (command: CommandT, cfg: RNConfig) => { cmd.option('--config [string]', 'Path to the CLI configuration file'); }; -function run() { +async function run() { + const config = await configPromise; const setupEnvScript = /^win/.test(process.platform) ? 'setup_env.bat' : 'setup_env.sh'; diff --git a/local-cli/core/index.js b/local-cli/core/index.js index 4d94aaebef4bac..3756fc3eeec0b6 100644 --- a/local-cli/core/index.js +++ b/local-cli/core/index.js @@ -16,6 +16,7 @@ const findPlugins = require('./findPlugins'); const findAssets = require('./findAssets'); const ios = require('./ios'); const wrapCommands = require('./wrapCommands'); +const {ASSET_REGISTRY_PATH} = require('./Constants'); /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error * found when Flow v0.54 was deployed. To see the error delete this comment and @@ -28,7 +29,7 @@ const minimist = require('minimist'); const path = require('path'); import type {CommandT} from '../commands'; -import type {ConfigT} from 'metro'; +import type {ConfigT} from 'metro-config/src/configTypes.flow'; export type RNConfig = { ...ConfigT, @@ -36,10 +37,6 @@ export type RNConfig = { * Returns an object with all platform configurations. */ getPlatformConfig(): Object, - /** - * Returns an array of project commands used by the CLI to load - */ - getProjectCommands(): Array, /** * Returns project config from the current working directory */ @@ -69,7 +66,7 @@ const pluginPlatforms = plugins.platforms.reduce((acc, pathToPlatforms) => { ); }, {}); -const defaultRNConfig = { +const defaultConfig = { hasteImplModulePath: require.resolve('../../jest/hasteImpl'), getPlatforms(): Array { @@ -79,20 +76,9 @@ const defaultRNConfig = { getProvidesModuleNodeModules(): Array { return ['react-native', 'react-native-windows']; }, +}; - getProjectCommands(): Array { - const commands = plugins.commands.map(pathToCommands => { - const name = pathToCommands.split(path.sep)[0]; - - return attachPackage( - require(path.join(appRoot, 'node_modules', pathToCommands)), - require(path.join(appRoot, 'node_modules', name, 'package.json')), - ); - }); - - return flatten(commands); - }, - +const defaultRNConfig = { getPlatformConfig(): Object { return { ios, @@ -141,14 +127,33 @@ const defaultRNConfig = { /** * Loads the CLI configuration */ -function getCliConfig(): RNConfig { +async function getCliConfig(): Promise { const cliArgs = minimist(process.argv.slice(2)); - const config = - cliArgs.config != null - ? Config.load(path.resolve(__dirname, cliArgs.config)) - : Config.findOptional(__dirname); + const config = await Config.load(path.resolve(__dirname, cliArgs.config)); + + config.transformer.assetRegistryPath = ASSET_REGISTRY_PATH; + config.resolver.hasteImplModulePath = defaultConfig.hasteImplModulePath; + config.resolver.platforms = defaultConfig.getPlatforms(); + config.resolver.providesModuleNodeModules = defaultConfig.getProvidesModuleNodeModules(); return {...defaultRNConfig, ...config}; } -module.exports = getCliConfig(); +/** + * Returns an array of project commands used by the CLI to load + */ +function getProjectCommands(): Array { + const commands = plugins.commands.map(pathToCommands => { + const name = pathToCommands.split(path.sep)[0]; + + return attachPackage( + require(path.join(appRoot, 'node_modules', pathToCommands)), + require(path.join(appRoot, 'node_modules', name, 'package.json')), + ); + }); + + return flatten(commands); +} + +module.exports.configPromise = getCliConfig(); +module.exports.getProjectCommands = getProjectCommands; diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js index 32b0bf3146e16b..b3058a16578b59 100644 --- a/local-cli/dependencies/dependencies.js +++ b/local-cli/dependencies/dependencies.js @@ -16,8 +16,6 @@ const denodeify = require('denodeify'); const fs = require('fs'); const path = require('path'); -const {ASSET_REGISTRY_PATH} = require('../core/Constants'); - async function dependencies(argv, configPromise, args, packagerInstance) { const rootModuleAbsolutePath = args.entryFile; const config = await configPromise; @@ -31,12 +29,9 @@ async function dependencies(argv, configPromise, args, packagerInstance) { config.transformModulePath = args.transformer ? path.resolve(args.transformer) : config.transformModulePath; - config.transformer.transformModulePath = ASSET_REGISTRY_PATH; - - const {serverOptions: packageOpts} = convert.convertNewToOld(config); const relativePath = path.relative( - packageOpts.projectRoot, + config.projectRoot, rootModuleAbsolutePath, ); @@ -53,10 +48,12 @@ async function dependencies(argv, configPromise, args, packagerInstance) { ? fs.createWriteStream(args.output) : process.stdout; + const {serverOptions} = convert.convertNewToOld(config); + return Promise.resolve( (packagerInstance ? packagerInstance.getOrderedDependencyPaths(options) - : Metro.getOrderedDependencyPaths(packageOpts, options) + : Metro.getOrderedDependencyPaths(serverOptions, options) ).then(deps => { deps.forEach(modulePath => { // Temporary hack to disable listing dependencies not under this directory. @@ -64,7 +61,7 @@ async function dependencies(argv, configPromise, args, packagerInstance) { // (a) JS code to not depend on anything outside this directory, or // (b) Come up with a way to declare this dependency in Buck. const isInsideProjectRoots = - packageOpts.watchFolders.filter(root => modulePath.startsWith(root)) + config.watchFolders.filter(root => modulePath.startsWith(root)) .length > 0; if (isInsideProjectRoots) { diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js index e8d741fa63ede8..b849060946df72 100644 --- a/local-cli/server/runServer.js +++ b/local-cli/server/runServer.js @@ -21,11 +21,8 @@ const morgan = require('morgan'); const path = require('path'); const webSocketProxy = require('./util/webSocketProxy'); const MiddlewareManager = require('./middleware/MiddlewareManager'); -const {convertOldToNew} = require('metro-config/src/convertConfig'); -const {ASSET_REGISTRY_PATH} = require('../core/Constants'); - -import type {ConfigT} from 'metro'; +import type {ConfigT} from 'metro-config/src/configTypes.flow'; export type Args = {| +assetExts: $ReadOnlyArray, @@ -57,23 +54,14 @@ async function runServer(args: Args, config: ConfigT) { args.watchFolders.forEach(middlewareManager.serveStatic); + config.maxWorkers = args.maxWorkers; + config.server.port = args.port; + config.reporter = reporter; + config.server.enhanceMiddleware = middleware => + middlewareManager.getConnectInstance().use(middleware); + const serverInstance = await Metro.runServer({ - config: convertOldToNew({ - config: { - ...config, - assetRegistryPath: ASSET_REGISTRY_PATH, - enhanceMiddleware: middleware => - middlewareManager.getConnectInstance().use(middleware), - transformModulePath: args.transformer - ? path.resolve(args.transformer) - : config.getTransformModulePath(), - }, - maxWorkers: args.maxWorkers, - port: args.port, - reporter, - }), - - hmrEnabled: true, + config, host: args.host, secure: args.https, secureCert: args.cert, diff --git a/local-cli/server/server.js b/local-cli/server/server.js index 42423f5583759a..4865cbcc7a6920 100644 --- a/local-cli/server/server.js +++ b/local-cli/server/server.js @@ -13,7 +13,7 @@ const runServer = require('./runServer'); import type {RNConfig} from '../core'; -import type {ConfigT} from 'metro'; +import type {ConfigT} from 'metro-config/src/configTypes.flow'; import type {Args as RunServerArgs} from './runServer'; /** @@ -43,7 +43,7 @@ module.exports = { command: '--projectRoot [string]', description: 'Specify the main project root', default: (config: ConfigT) => { - return config.getProjectRoot(); + return config.projectRoot; }, }, { @@ -52,7 +52,7 @@ module.exports = { 'Specify any additional folders to be added to the watch list', parse: (val: string) => val.split(','), default: (config: ConfigT) => { - return config.getWatchFolders(); + return config.watchFolders; }, }, { @@ -60,21 +60,21 @@ module.exports = { description: 'Specify any additional asset extensions to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.getAssetExts(), + default: (config: ConfigT) => config.resolver.assetExts, }, { command: '--sourceExts [list]', description: 'Specify any additional source extensions to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.getSourceExts(), + default: (config: ConfigT) => config.resolver.sourceExts, }, { command: '--platforms [list]', description: 'Specify any additional platforms to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.getPlatforms(), + default: (config: ConfigT) => config.resolver.platforms, }, { command: '--providesModuleNodeModules [list]', @@ -82,10 +82,9 @@ module.exports = { 'Specify any npm packages that import dependencies with providesModule', parse: (val: string) => val.split(','), default: (config: RNConfig) => { - if (typeof config.getProvidesModuleNodeModules === 'function') { - return config.getProvidesModuleNodeModules(); - } - return null; + return config.resolver + ? config.resolver.providesModuleNodeModules + : undefined; }, }, { diff --git a/local-cli/util/Config.js b/local-cli/util/Config.js index 2e78e5041eb4fa..77f67e3791f25f 100644 --- a/local-cli/util/Config.js +++ b/local-cli/util/Config.js @@ -10,21 +10,16 @@ 'use strict'; const findSymlinkedModules = require('./findSymlinkedModules'); -const fs = require('fs'); const getPolyfills = require('../../rn-get-polyfills'); -const invariant = require('fbjs/lib/invariant'); const path = require('path'); -const {Config: MetroConfig, createBlacklist} = require('metro'); - -const RN_CLI_CONFIG = 'rn-cli.config.js'; - -import type {ConfigT as MetroConfigT} from 'metro'; +const {createBlacklist} = require('metro'); +const {loadConfig, mergeConfig} = require('metro-config'); /** * Configuration file of the CLI. */ -export type ConfigT = MetroConfigT; +import type {ConfigT} from 'metro-config/src/configTypes.flow'; function getProjectPath() { if ( @@ -70,68 +65,30 @@ const getBlacklistRE = () => { * hierarchy, an error will be thrown. */ const Config = { - DEFAULT: ({ - ...MetroConfig.DEFAULT, - getBlacklistRE, - getModulesRunBeforeMainModule: () => [ - require.resolve('../../Libraries/Core/InitializeCore'), - ], - getProjectRoots, - getPolyfills, - getWatchFolders: () => [getProjectPath()], - getResolverMainFields: () => ['react-native', 'browser', 'main'], - getTransformModulePath: () => - require.resolve('metro/src/reactNativeTransformer'), - }: ConfigT), - - find(startDir: string): ConfigT { - return this.findWithPath(startDir).config; - }, - - findWithPath(startDir: string): {config: ConfigT, projectPath: string} { - const configPath = findConfigPath(startDir); - invariant( - configPath, - `Can't find "${RN_CLI_CONFIG}" file in any parent folder of "${startDir}"`, - ); - const projectPath = path.dirname(configPath); - return {config: this.load(configPath, startDir), projectPath}; - }, - - findOptional(startDir: string): ConfigT { - const configPath = findConfigPath(startDir); - return configPath ? this.load(configPath, startDir) : {...Config.DEFAULT}; + DEFAULT: { + resolver: { + resolverMainFields: ['react-native', 'browser', 'main'], + blacklistRE: getBlacklistRE(), + }, + serializer: { + getModulesRunBeforeMainModule: () => [ + require.resolve('../../Libraries/Core/InitializeCore'), + ], + getPolyfills, + }, + + watchFolders: [getProjectPath(), ...getProjectRoots()], + transformModulePath: require.resolve('metro/src/reactNativeTransformer'), }, getProjectPath, getProjectRoots, - load(configFile: string): ConfigT { - return MetroConfig.load(configFile, Config.DEFAULT); + async load(configFile: string): Promise { + const config: ConfigT = await loadConfig({config: configFile}); + + return mergeConfig(config, this.DEFAULT); }, }; -function findConfigPath(cwd: string): ?string { - const parentDir = findParentDirectory(cwd, RN_CLI_CONFIG); - return parentDir ? path.join(parentDir, RN_CLI_CONFIG) : null; -} - -// Finds the most near ancestor starting at `currentFullPath` that has -// a file named `filename` -function findParentDirectory(currentFullPath, filename) { - const root = path.parse(currentFullPath).root; - const testDir = parts => { - if (parts.length === 0) { - return null; - } - - const fullPath = path.join(root, parts.join(path.sep)); - - var exists = fs.existsSync(path.join(fullPath, filename)); - return exists ? fullPath : testDir(parts.slice(0, -1)); - }; - - return testDir(currentFullPath.substring(root.length).split(path.sep)); -} - module.exports = Config;