diff --git a/docs/autolinking.md b/docs/autolinking.md index f01ab2474..09f272959 100644 --- a/docs/autolinking.md +++ b/docs/autolinking.md @@ -94,11 +94,12 @@ On the iOS side, you will need to ensure you have a Podspec to the root of your A library can add a `react-native.config.js` configuration file, which will customize the defaults. -## How do I disable autolinking for unsupported package? +## How can I disable autolinking for unsupported library? During the transition period some packages may not support autolinking on certain platforms. To disable autolinking for a package, update your `react-native.config.js`'s `dependencies` entry to look like this: ```js +// react-native.config.js module.exports = { dependencies: { 'some-unsupported-package': { @@ -109,3 +110,18 @@ module.exports = { }, }; ``` + +## How can I autolink a local library? + +We can leverage CLI configuration to make it "see" React Native libraries that are not part of our 3rd party dependencies. To do so, update your `react-native.config.js`'s `dependencies` entry to look like this: + +```js +// react-native.config.js +module.exports = { + dependencies: { + 'local-rn-library': { + root: '/root/libraries', + }, + }, +}; +``` diff --git a/docs/projects.md b/docs/projects.md index caee753ba..abc704497 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -109,7 +109,7 @@ For example, you could set: ```js module.exports = { dependencies: { - ['react-native-webview']: { + 'react-native-webview': { platforms: { ios: null, }, @@ -120,6 +120,18 @@ module.exports = { in order to disable linking of React Native WebView on iOS. +Another use-case would be supporting local libraries that are not discoverable for autolinking, since they're not part of your `dependencies` or `devDependencies`: + +```js +module.exports = { + dependencies: { + 'local-rn-library': { + root: '/root/libraries', + }, + }, +}; +``` + The object provided here is deep merged with the dependency config. Check [`projectConfig`](platforms.md#projectconfig) and [`dependencyConfig`](platforms.md#dependencyConfig) return values for a full list of properties that you can override. > Note: This is an advanced feature and you should not need to use it mos of the time. diff --git a/packages/cli/src/tools/config/__tests__/index-test.js b/packages/cli/src/tools/config/__tests__/index-test.js index bb19f1787..c65f138e2 100644 --- a/packages/cli/src/tools/config/__tests__/index-test.js +++ b/packages/cli/src/tools/config/__tests__/index-test.js @@ -285,8 +285,6 @@ test.skip('should skip packages that have invalid configuration', () => { }); test('does not use restricted "react-native" key to resolve config from package.json', () => { - jest.resetModules(); - writeFiles(DIR, { 'node_modules/react-native-netinfo/package.json': `{ "react-native": "src/index.js" @@ -302,3 +300,53 @@ test('does not use restricted "react-native" key to resolve config from package. expect(dependencies).toHaveProperty('react-native-netinfo'); expect(spy).not.toHaveBeenCalled(); }); + +test('supports dependencies from user configuration with custom root and properties', () => { + writeFiles(DIR, { + 'node_modules/react-native/package.json': '{}', + 'native-libs/local-lib/ios/LocalRNLibrary.xcodeproj/project.pbxproj': '', + 'react-native.config.js': `module.exports = { + dependencies: { + 'local-lib': { + root: "${DIR}/native-libs/local-lib", + platforms: { + ios: { + podspecPath: "custom-path" + } + } + }, + } + }`, + 'package.json': `{ + "dependencies": { + "react-native": "0.0.1" + } + }`, + }); + + const {dependencies} = loadConfig(DIR); + expect(removeString(dependencies['local-lib'], DIR)).toMatchInlineSnapshot(` + Object { + "assets": Array [], + "hooks": Object {}, + "name": "local-lib", + "params": Array [], + "platforms": Object { + "android": null, + "ios": Object { + "folder": "<>/native-libs/local-lib", + "libraryFolder": "Libraries", + "pbxprojPath": "<>/native-libs/local-lib/ios/LocalRNLibrary.xcodeproj/project.pbxproj", + "plist": Array [], + "podfile": null, + "podspecPath": "custom-path", + "projectName": "LocalRNLibrary.xcodeproj", + "projectPath": "<>/native-libs/local-lib/ios/LocalRNLibrary.xcodeproj", + "sharedLibraries": Array [], + "sourceDir": "<>/native-libs/local-lib/ios", + }, + }, + "root": "<>/native-libs/local-lib", + } + `); +}); diff --git a/packages/cli/src/tools/config/index.js b/packages/cli/src/tools/config/index.js index c6e5a088f..a9924aa95 100644 --- a/packages/cli/src/tools/config/index.js +++ b/packages/cli/src/tools/config/index.js @@ -73,7 +73,7 @@ function loadConfig(projectRoot: string = process.cwd()): ConfigT { ? path.resolve(projectRoot, userConfig.reactNativePath) : resolveReactNativePath(projectRoot); }, - dependencies: {}, + dependencies: userConfig.dependencies, commands: userConfig.commands, get assets() { return findAssets(projectRoot, userConfig.assets); @@ -97,90 +97,94 @@ function loadConfig(projectRoot: string = process.cwd()): ConfigT { let depsWithWarnings = []; - const finalConfig = findDependencies(projectRoot).reduce( - (acc: ConfigT, dependencyName) => { - let root; - let config; - try { - root = resolveNodeModuleDir(projectRoot, dependencyName); - const output = readDependencyConfigFromDisk(root); - config = output.config; + const finalConfig = [ + ...Object.keys(userConfig.dependencies), + ...findDependencies(projectRoot), + ].reduce((acc: ConfigT, dependencyName) => { + const localDependencyRoot = + userConfig.dependencies[dependencyName] && + userConfig.dependencies[dependencyName].root; + let root; + let config; + try { + root = + localDependencyRoot || + resolveNodeModuleDir(projectRoot, dependencyName); + const output = readDependencyConfigFromDisk(root); + config = output.config; - if (output.legacy) { - const pkg = require(path.join(root, 'package.json')); - const link = - pkg.homepage || `https://npmjs.com/package/${dependencyName}`; - depsWithWarnings.push([dependencyName, link]); - } - } catch (error) { - logger.warn( - inlineString(` - Package ${chalk.bold( - dependencyName, - )} has been ignored because it contains invalid configuration. - - Reason: ${chalk.dim(error.message)} - `), - ); - return acc; + if (output.legacy && !localDependencyRoot) { + const pkg = require(path.join(root, 'package.json')); + const link = + pkg.homepage || `https://npmjs.com/package/${dependencyName}`; + depsWithWarnings.push([dependencyName, link]); } + } catch (error) { + logger.warn( + inlineString(` + Package ${chalk.bold( + dependencyName, + )} has been ignored because it contains invalid configuration. - /** - * @todo: remove this code once `react-native` is published with - * `platforms` and `commands` inside `react-native.config.js`. - */ - if (dependencyName === 'react-native') { - if (Object.keys(config.platforms).length === 0) { - config.platforms = {ios, android}; - } - if (config.commands.length === 0) { - config.commands = [...ios.commands, ...android.commands]; - } + Reason: ${chalk.dim(error.message)}`), + ); + return acc; + } + + /** + * @todo: remove this code once `react-native` is published with + * `platforms` and `commands` inside `react-native.config.js`. + */ + if (dependencyName === 'react-native') { + if (Object.keys(config.platforms).length === 0) { + config.platforms = {ios, android}; + } + if (config.commands.length === 0) { + config.commands = [...ios.commands, ...android.commands]; } + } - const isPlatform = Object.keys(config.platforms).length > 0; + const isPlatform = Object.keys(config.platforms).length > 0; - /** - * Legacy `rnpm` config required `haste` to be defined. With new config, - * we do it automatically. - * - * @todo: Remove this once `rnpm` config is deprecated and all major RN libs are converted. - */ - const haste = config.haste || { - providesModuleNodeModules: isPlatform ? [dependencyName] : [], - platforms: Object.keys(config.platforms), - }; + /** + * Legacy `rnpm` config required `haste` to be defined. With new config, + * we do it automatically. + * + * @todo: Remove this once `rnpm` config is deprecated and all major RN libs are converted. + */ + const haste = config.haste || { + providesModuleNodeModules: isPlatform ? [dependencyName] : [], + platforms: Object.keys(config.platforms), + }; - return (assign({}, acc, { - dependencies: assign({}, acc.dependencies, { - // $FlowExpectedError: Dynamic getters are not supported - get [dependencyName]() { - return getDependencyConfig( - root, - dependencyName, - finalConfig, - config, - userConfig, - isPlatform, - ); - }, - }), - commands: [...acc.commands, ...config.commands], - platforms: { - ...acc.platforms, - ...config.platforms, + return (assign({}, acc, { + dependencies: assign({}, acc.dependencies, { + // $FlowExpectedError: Dynamic getters are not supported + get [dependencyName]() { + return getDependencyConfig( + root, + dependencyName, + finalConfig, + config, + userConfig, + isPlatform, + ); }, - haste: { - providesModuleNodeModules: [ - ...acc.haste.providesModuleNodeModules, - ...haste.providesModuleNodeModules, - ], - platforms: [...acc.haste.platforms, ...haste.platforms], - }, - }): ConfigT); - }, - initialConfig, - ); + }), + commands: [...acc.commands, ...config.commands], + platforms: { + ...acc.platforms, + ...config.platforms, + }, + haste: { + providesModuleNodeModules: [ + ...acc.haste.providesModuleNodeModules, + ...haste.providesModuleNodeModules, + ], + platforms: [...acc.haste.platforms, ...haste.platforms], + }, + }): ConfigT); + }, initialConfig); if (depsWithWarnings.length) { logger.warn( diff --git a/packages/cli/src/tools/config/readConfigFromDisk.js b/packages/cli/src/tools/config/readConfigFromDisk.js index 76a9c7e38..197ed237b 100644 --- a/packages/cli/src/tools/config/readConfigFromDisk.js +++ b/packages/cli/src/tools/config/readConfigFromDisk.js @@ -140,7 +140,24 @@ const loadProjectCommands = ( function readLegacyDependencyConfigFromDisk( rootFolder: string, ): ?UserDependencyConfigT { - const {rnpm: config} = require(path.join(rootFolder, 'package.json')); + let config = {}; + + try { + config = require(path.join(rootFolder, 'package.json')).rnpm; + } catch (error) { + // package.json is usually missing in local libraries that are not in + // project "dependencies", so we just return a bare config + return { + dependency: { + platforms: {}, + assets: [], + hooks: {}, + params: [], + }, + commands: [], + platforms: {}, + }; + } if (!config) { return undefined; diff --git a/packages/cli/src/tools/config/schema.js b/packages/cli/src/tools/config/schema.js index a6ee6d4a4..8bb0174c7 100644 --- a/packages/cli/src/tools/config/schema.js +++ b/packages/cli/src/tools/config/schema.js @@ -106,6 +106,7 @@ export const projectConfig = t t.string(), t .object({ + root: t.string(), platforms: map(t.string(), t.any()).keys({ ios: t .object({ diff --git a/types/index.js b/types/index.js index 6d9015f36..32ba63ef2 100644 --- a/types/index.js +++ b/types/index.js @@ -179,8 +179,8 @@ export type UserDependencyConfigT = { // Additional dependency settings dependency: { platforms: { - android: DependencyParamsAndroidT, - ios: ProjectParamsIOST, + android?: DependencyParamsAndroidT, + ios?: ProjectParamsIOST, [key: string]: any, }, assets: string[],