react-native/local-cli/util/Config.js

249 lines
7.9 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const blacklist = require('metro-bundler/src/blacklist');
Add --maxWorkers flag and allow transformers to run in-band. Summary: This diff cleans up some cruft and adds some features: * It removes the usage of an env variable to control workers. * It removes the lazy and handwavy calculation on how many workers to use for jest-haste-map. Jest itself uses the maximum amount of workers available and it has never been reported as an issue – especially since it is a one-time startup cost of about 3 seconds on a cold cache only. * It adds a `--max-workers` flag to replace the env variable. This one is able to control both the number of workers for `jest-haste-map` as well as the transformers. * It makes the transformers run in the parent process if 1 or fewer workers are are specified. This should help with debugging. Once you approve this diff, I will publish a new version of metro to npm and update the version used in RN and remove the use of the env variable altogether: https://our.intern.facebook.com/intern/biggrep/?corpus=xplat&filename=&case=false&view=default&extre=&s=REACT_NATIVE_MAX_WORKERS&engine=apr_strmatch&context=false&filter[uninteresting]=false&filter[intern]=false&filter[test]=false&grep_regex= Note: the process of adding a CLI option is really broken. Commander also has a weird API. We should consider building a better public API for Metro and then consider how to build a new CLI on top of it and simplify our internal integration. I really don't like how Metro is integrated across pieces of the RN cli in ways that is hard to manage. But that is a larger task for another time :) Reviewed By: jeanlauliac Differential Revision: D5217726 fbshipit-source-id: 74efddbb87755a9e744c816fbc62efa21f6a79bf
2017-06-13 16:11:57 +00:00
const findSymlinksPaths = require('./findSymlinksPaths');
const fs = require('fs');
const getPolyfills = require('../../rn-get-polyfills');
const invariant = require('fbjs/lib/invariant');
const path = require('path');
const {providesModuleNodeModules} = require('metro-bundler/src/defaults');
const RN_CLI_CONFIG = 'rn-cli.config.js';
import type {
GetTransformOptions,
PostMinifyProcess,
PostProcessModules,
PostProcessBundleSourcemap
} from 'metro-bundler/src/Bundler';
import type {HasteImpl} from 'metro-bundler/src/node-haste/Module';
import type {TransformVariants} from 'metro-bundler/src/ModuleGraph/types.flow';
import type {PostProcessModules as PostProcessModulesForBuck} from 'metro-bundler/src/ModuleGraph/types.flow.js';
/**
* Configuration file of the CLI.
*/
export type ConfigT = {
extraNodeModules: {[id: string]: string},
/**
* Specify any additional asset file extensions to be used by the packager.
* For example, if you want to include a .ttf file, you would return ['ttf']
* from here and use `require('./fonts/example.ttf')` inside your app.
*/
getAssetExts: () => Array<string>,
/**
* Returns a regular expression for modules that should be ignored by the
* packager on a given platform.
*/
getBlacklistRE(): RegExp,
/**
* Specify whether or not to enable Babel's behavior for looking up .babelrc
* files. If false, only the .babelrc file (if one exists) in the main project
* root is used.
*/
getEnableBabelRCLookup(): boolean,
pass polyfillModuleNames into packager Summary: After examining how React Native sets up `process.env.NODE_ENV` using `global.__DEV__` from `prelude_dev.js` or `prelude.js` by treating them like polyfills I decided to use the same approach for environment variables. I setup my own rn-project.config.js file like so: ``` const blacklist = require('react-native/packager/blacklist'); const pathJoin = require('path').join; module.exports = { getBlacklistRE: function() { return blacklist([/build\/.*/, /app\/assets\/webpack.*/]); }, polyfillModuleNames: [pathJoin(__dirname, 'globals.js')] }; ``` I ran the packaging server using: `react-native start --config=config/react-native/rn-project.config.js --reset-cache` I expected my polyfillModuleNames to be passed into the Packager properly and be handled the same way the built-in polyfills worked. Unfortunately I noticed the Packager wasn't actually getting `opt.polyfillModuleNames`. Digging into the code a bit, it seems the local-cli wasn't passing the polyfillModuleNames from the config. There are no specs for runServer.js but this change can be tested by using a config that contains polyfillModuleNames. Sample config and run command provided above simple `global.js` provided below: ``` global.process = global.process ? global.process : {}; global.process.env = global.process.env ? global.process.env : {}; global.process.env['PROJECT_ENV'] = 'staging'; ``` Closes https://github.com/facebook/react-native/pull/13725 Differential Revision: D5077615 Pulled By: jeanlauliac fbshipit-source-id: f66a8a8bda2702cd9a4e5b92f5335f43ab2f9089
2017-05-17 11:56:03 +00:00
/**
* Specify any additional polyfill modules that should be processed
* before regular module loading.
*/
getPolyfillModuleNames: () => Array<string>,
pass polyfillModuleNames into packager Summary: After examining how React Native sets up `process.env.NODE_ENV` using `global.__DEV__` from `prelude_dev.js` or `prelude.js` by treating them like polyfills I decided to use the same approach for environment variables. I setup my own rn-project.config.js file like so: ``` const blacklist = require('react-native/packager/blacklist'); const pathJoin = require('path').join; module.exports = { getBlacklistRE: function() { return blacklist([/build\/.*/, /app\/assets\/webpack.*/]); }, polyfillModuleNames: [pathJoin(__dirname, 'globals.js')] }; ``` I ran the packaging server using: `react-native start --config=config/react-native/rn-project.config.js --reset-cache` I expected my polyfillModuleNames to be passed into the Packager properly and be handled the same way the built-in polyfills worked. Unfortunately I noticed the Packager wasn't actually getting `opt.polyfillModuleNames`. Digging into the code a bit, it seems the local-cli wasn't passing the polyfillModuleNames from the config. There are no specs for runServer.js but this change can be tested by using a config that contains polyfillModuleNames. Sample config and run command provided above simple `global.js` provided below: ``` global.process = global.process ? global.process : {}; global.process.env = global.process.env ? global.process.env : {}; global.process.env['PROJECT_ENV'] = 'staging'; ``` Closes https://github.com/facebook/react-native/pull/13725 Differential Revision: D5077615 Pulled By: jeanlauliac fbshipit-source-id: f66a8a8bda2702cd9a4e5b92f5335f43ab2f9089
2017-05-17 11:56:03 +00:00
/**
* Specify any additional platforms to be used by the packager.
* For example, if you want to add a "custom" platform, and use modules
* ending in .custom.js, you would return ['custom'] here.
*/
getPlatforms: () => Array<string>,
getProjectRoots(): Array<string>,
/**
* Specify any additional node modules that should be processed for
* providesModule declarations.
*/
getProvidesModuleNodeModules?: () => Array<string>,
/**
* Specify any additional source file extensions to be used by the packager.
* For example, if you want to include a .ts file, you would return ['ts']
* from here and use `require('./module/example')` to require the file with
* path 'module/example.ts' inside your app.
*/
getSourceExts: () => Array<string>,
/**
* Returns the path to a custom transformer. This can also be overridden
* with the --transformer commandline argument.
*/
getTransformModulePath: () => string,
getTransformOptions: GetTransformOptions,
/**
* Returns the path to the worker that is used for transformation.
*/
getWorkerPath: () => ?string,
/**
* An optional list of polyfills to include in the bundle. The list defaults
* to a set of common polyfills for Number, String, Array, Object...
*/
getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
/**
* An optional function that can modify the code and source map of bundle
* after the minifaction took place. (Function applied per module).
*/
postMinifyProcess: PostMinifyProcess,
/**
* An optional function that can modify the module array before the bundle is
* finalized.
*/
postProcessModules: PostProcessModules,
/**
* An optional function that can modify the code and source map of the bundle
* before it is written. Applied once for the entire bundle, only works if
* output is a plainBundle.
*/
postProcessBundleSourcemap: PostProcessBundleSourcemap,
/**
* Same as `postProcessModules` but for the Buck worker. Eventually we do want
* to unify both variants.
*/
postProcessModulesForBuck: PostProcessModulesForBuck,
/**
* A module that exports:
* - a `getHasteName(filePath)` method that returns `hasteName` for module at
* `filePath`, or undefined if `filePath` is not a haste module.
*/
hasteImpl?: HasteImpl,
transformVariants: () => TransformVariants,
};
function getProjectPath() {
if (__dirname.match(/node_modules[\/\\]react-native[\/\\]local-cli[\/\\]util$/)) {
// Packager is running from node_modules.
// This is the default case for all projects created using 'react-native init'.
return path.resolve(__dirname, '../../../..');
} else if (__dirname.match(/Pods[\/\\]React[\/\\]packager$/)) {
// React Native was installed using CocoaPods.
return path.resolve(__dirname, '../../../..');
}
return path.resolve(__dirname, '../..');
}
const resolveSymlink = (roots) =>
roots.concat(
findSymlinksPaths(
path.join(getProjectPath(), 'node_modules'),
roots
)
);
/**
* Module capable of getting the configuration out of a given file.
*
* The function will return all the default configuration, as specified by the
* `DEFAULTS` param overriden by those found on `rn-cli.config.js` files, if any. If no
* default config is provided and no configuration can be found in the directory
* hierarchy, an error will be thrown.
*/
const Config = {
DEFAULTS: ({
extraNodeModules: Object.create(null),
getAssetExts: () => [],
getBlacklistRE: () => blacklist(),
getEnableBabelRCLookup: () => true,
getPlatforms: () => [],
getPolyfillModuleNames: () => [],
getProjectRoots: () => {
const root = process.env.REACT_NATIVE_APP_ROOT;
if (root) {
return resolveSymlink([path.resolve(root)]);
}
return resolveSymlink([getProjectPath()]);
},
getProvidesModuleNodeModules: () => providesModuleNodeModules.slice(),
getSourceExts: () => [],
getTransformModulePath: () => require.resolve('metro-bundler/src/transformer.js'),
getTransformOptions: async () => ({}),
getPolyfills,
postMinifyProcess: x => x,
postProcessModules: modules => modules,
postProcessModulesForBuck: modules => modules,
postProcessBundleSourcemap: ({code, map, outFileName}) => ({code, map}),
transformVariants: () => ({default: {}}),
getWorkerPath: () => null,
}: 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.loadFile(configPath, startDir), projectPath};
},
findOptional(startDir: string): ConfigT {
const configPath = findConfigPath(startDir);
return configPath
? this.loadFile(configPath, startDir)
: {...Config.DEFAULTS};
},
loadFile(pathToConfig: string): ConfigT {
//$FlowFixMe: necessary dynamic require
const config: {} = require(pathToConfig);
return {...Config.DEFAULTS, ...config};
},
};
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;