From 8cc1b7b916ced2fd4f0a96658011a7749f5a681a Mon Sep 17 00:00:00 2001 From: empyrical Date: Thu, 30 Aug 2018 16:19:38 -0700 Subject: [PATCH] Add support for out-of-tree platform plugins (#20825) Summary: This pull request adds the ability for a platform developer to provide a `"haste"` key under the `"rnpm"` key in their `package.json` which allows the packager to pick up that platform's javascript files. The intent is to remove the need to have custom platforms hardcoded in. This is inspired by the `"jest": { "haste": {} }` key used by jest. For example, React Native Dom would have an entry like: ```json { "rnpm": { "haste": { "providesModuleNodeModules": [ "react-native-dom" ], "platforms": [ "dom" ] } } } ``` Support for more keys (path blacklists perhaps?) could be added in the future. This succeeds #20662, as per a discussion I had with matthargett. I've got an open discussion over here as well: https://github.com/react-native-community/discussions-and-proposals/issues/21 Pull Request resolved: https://github.com/facebook/react-native/pull/20825 Differential Revision: D9596429 Pulled By: hramos fbshipit-source-id: a02f0da0bea8870bdc45d55e23da8ccbc36249f2 --- jest/__tests__/hasteImpl-test.js | 2 +- jest/hasteImpl.js | 29 ++++++++++---- local-cli/core/findPlugins.js | 67 +++++++++++++++++++++++--------- local-cli/core/index.js | 18 ++++++--- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/jest/__tests__/hasteImpl-test.js b/jest/__tests__/hasteImpl-test.js index de493aa9a153da..ce2718f41eb1a5 100644 --- a/jest/__tests__/hasteImpl-test.js +++ b/jest/__tests__/hasteImpl-test.js @@ -32,7 +32,7 @@ it('returns the correct haste name for a RN library file', () => { }); it('returns the correct haste name for a file with a platform suffix', () => { - for (const platform of ['android', 'ios', 'native', 'web', 'windows']) { + for (const platform of ['android', 'ios', 'native']) { expect( getHasteName( getPath( diff --git a/jest/hasteImpl.js b/jest/hasteImpl.js index 030beeb9ea611a..9d1861bc9f2428 100644 --- a/jest/hasteImpl.js +++ b/jest/hasteImpl.js @@ -11,12 +11,25 @@ 'use strict'; const path = require('path'); +const findPlugins = require('../local-cli/core/findPlugins'); -const ROOTS = [ - path.resolve(__dirname, '..') + path.sep, - path.resolve(__dirname, '../../react-native-windows') + path.sep, - path.resolve(__dirname, '../../react-native-dom') + path.sep, -]; +const plugins = findPlugins([path.resolve(__dirname, '../../../')]); + +// Detect out-of-tree platforms and add them to the whitelists +const pluginRoots /*: Array< + string, +> */ = plugins.haste.providesModuleNodeModules.map( + name => path.resolve(__dirname, '../../', name) + path.sep, +); + +const pluginNameReducers /*: Array< + [RegExp, string], +> */ = plugins.haste.platforms.map(name => [ + new RegExp(`^(.*)\.(${name})$`), + '$1', +]); + +const ROOTS = [path.resolve(__dirname, '..') + path.sep, ...pluginRoots]; const BLACKLISTED_PATTERNS /*: Array */ = [ /.*[\\\/]__(mocks|tests)__[\\\/].*/, @@ -36,8 +49,10 @@ const NAME_REDUCERS /*: Array<[RegExp, string]> */ = [ [/^(?:.*[\\\/])?([a-zA-Z0-9$_.-]+)$/, '$1'], // strip .js/.js.flow suffix [/^(.*)\.js(\.flow)?$/, '$1'], - // strip .android/.ios/.native/.web suffix - [/^(.*)\.(android|ios|native|web|windows|dom)$/, '$1'], + // strip platform suffix + [/^(.*)\.(android|ios|native)$/, '$1'], + // strip plugin platform suffixes + ...pluginNameReducers, ]; const haste = { diff --git a/local-cli/core/findPlugins.js b/local-cli/core/findPlugins.js index 595577ba8e875c..6c23c5d9f279a5 100644 --- a/local-cli/core/findPlugins.js +++ b/local-cli/core/findPlugins.js @@ -47,11 +47,37 @@ const findPlatformsInPackage = pjson => { return path.join(pjson.name, pjson.rnpm.platform); }; +const getEmptyPluginConfig = () => ({ + commands: [], + platforms: [], + haste: { + platforms: [], + providesModuleNodeModules: [], + }, +}); + +const findHasteConfigInPackageAndConcat = (pjson, haste) => { + if (!pjson.rnpm || !pjson.rnpm.haste) { + return; + } + let pkgHaste = pjson.rnpm.haste; + + if (pkgHaste.platforms) { + haste.platforms = haste.platforms.concat(pkgHaste.platforms); + } + + if (pkgHaste.providesModuleNodeModules) { + haste.providesModuleNodeModules = haste.providesModuleNodeModules.concat( + pkgHaste.providesModuleNodeModules, + ); + } +}; + const findPluginInFolder = folder => { const pjson = readPackage(folder); if (!pjson) { - return {commands: [], platforms: []}; + return getEmptyPluginConfig(); } const deps = union( @@ -59,24 +85,23 @@ const findPluginInFolder = folder => { Object.keys(pjson.devDependencies || {}), ); - return deps.reduce( - (acc, pkg) => { - let commands = acc.commands; - let platforms = acc.platforms; - if (isRNPMPlugin(pkg)) { - commands = commands.concat(pkg); - } - if (isReactNativePlugin(pkg)) { - const pkgJson = readPackage(path.join(folder, 'node_modules', pkg)); - if (pkgJson) { - commands = commands.concat(findPluginsInReactNativePackage(pkgJson)); - platforms = platforms.concat(findPlatformsInPackage(pkgJson)); - } + return deps.reduce((acc, pkg) => { + let commands = acc.commands; + let platforms = acc.platforms; + let haste = acc.haste; + if (isRNPMPlugin(pkg)) { + commands = commands.concat(pkg); + } + if (isReactNativePlugin(pkg)) { + const pkgJson = readPackage(path.join(folder, 'node_modules', pkg)); + if (pkgJson) { + commands = commands.concat(findPluginsInReactNativePackage(pkgJson)); + platforms = platforms.concat(findPlatformsInPackage(pkgJson)); + findHasteConfigInPackageAndConcat(pkgJson, haste); } - return {commands: commands, platforms: platforms}; - }, - {commands: [], platforms: []}, - ); + } + return {commands: commands, platforms: platforms, haste: haste}; + }, getEmptyPluginConfig()); }; /** @@ -89,5 +114,11 @@ module.exports = function findPlugins(folders) { return { commands: uniq(flatten(plugins.map(p => p.commands))), platforms: uniq(flatten(plugins.map(p => p.platforms))), + haste: { + platforms: uniq(flatten(plugins.map(p => p.haste.platforms))), + providesModuleNodeModules: uniq( + flatten(plugins.map(p => p.haste.providesModuleNodeModules)), + ), + }, }; }; diff --git a/local-cli/core/index.js b/local-cli/core/index.js index 7027706250a4c1..2e66b662bd5299 100644 --- a/local-cli/core/index.js +++ b/local-cli/core/index.js @@ -70,11 +70,11 @@ const defaultConfig = { hasteImplModulePath: require.resolve('../../jest/hasteImpl'), getPlatforms(): Array { - return ['ios', 'android', 'windows', 'web', 'dom']; + return ['ios', 'android', 'native', ...plugins.haste.platforms]; }, getProvidesModuleNodeModules(): Array { - return ['react-native', 'react-native-windows', 'react-native-dom']; + return ['react-native', ...plugins.haste.providesModuleNodeModules]; }, }; @@ -132,9 +132,17 @@ async function getCliConfig(): Promise { ); config.transformer.assetRegistryPath = ASSET_REGISTRY_PATH; - config.resolver.hasteImplModulePath = config.resolver.hasteImplModulePath || defaultConfig.hasteImplModulePath; - config.resolver.platforms = config.resolver.platforms || defaultConfig.getPlatforms(); - config.resolver.providesModuleNodeModules = config.resolver.providesModuleNodeModules || defaultConfig.getProvidesModuleNodeModules(); + config.resolver.hasteImplModulePath = + config.resolver.hasteImplModulePath || defaultConfig.hasteImplModulePath; + config.resolver.platforms = config.resolver.platforms + ? config.resolver.platforms.concat(defaultConfig.getPlatforms()) + : defaultConfig.getPlatforms(); + config.resolver.providesModuleNodeModules = config.resolver + .providesModuleNodeModules + ? config.resolver.providesModuleNodeModules.concat( + defaultConfig.getProvidesModuleNodeModules(), + ) + : defaultConfig.getProvidesModuleNodeModules(); return {...defaultRNConfig, ...config}; }