From 2636a2a3959420dc0033090b160c498732cf3040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 30 Jun 2019 19:14:31 +0200 Subject: [PATCH 1/3] feat: support local RN libraries in autolinking --- docs/autolinking.md | 32 +++++++++++++++++- docs/projects.md | 21 +++++++++++- .../src/tools/config/__tests__/index-test.js | 33 +++++++++++++++++++ packages/cli/src/tools/config/index.js | 2 +- 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/autolinking.md b/docs/autolinking.md index f01ab2474..cdd8af6cc 100644 --- a/docs/autolinking.md +++ b/docs/autolinking.md @@ -94,7 +94,7 @@ 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: @@ -109,3 +109,33 @@ module.exports = { }, }; ``` + +## How can I autolink a local library? + +Currently autolinking doesn't support local libraries out of the box. However, we can leverage CLI configuration to make it "see" the React Native libraries that are not part of our 3rd party dependencies. To make autolinking see custom libraries that are not in `node_modules`, update your `react-native.config.js`'s `dependencies` entry to look like this (adjust paths where necessary): + +```js +module.exports = { + dependencies: { + 'local-rn-library': { + platforms: { + ios: { + sourceDir: '/root/ios', + folder: '/root/ios', + pbxprojPath: 'root/ios/RNLibrary.xcodeproj/project.pbxproj', + podspecPath: 'root/RNLibrary.podspec', + projectPath: 'root/ios/RNLibrary.xcodeproj', + projectName: 'RNLibrary.xcodeproj', + libraryFolder: 'Libraries', + }, + android: { + sourceDir: '/root/android', + folder: '/root/android', + packageImportPath: 'import com.myproject.RNLibraryPackage;', + packageInstance: 'new RNLibraryPackage()', + }, + }, + }, + }, +}; +``` diff --git a/docs/projects.md b/docs/projects.md index caee753ba..15dca6a07 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,25 @@ 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': { + platforms: { + android: { + sourceDir: '/root/android', + folder: '/root/android', + packageImportPath: 'import com.myproject.RNLibraryPackage;', + packageInstance: 'new RNLibraryPackage()', + }, + }, + }, + }, +}; +``` + 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..d1b496217 100644 --- a/packages/cli/src/tools/config/__tests__/index-test.js +++ b/packages/cli/src/tools/config/__tests__/index-test.js @@ -302,3 +302,36 @@ 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 default dependencies from user configuration', () => { + const depsWithLocalLibrary = { + 'local-rn-library': { + platforms: { + ios: { + sourceDir: '/root/ios', + folder: '/root/ios', + pbxprojPath: 'root/ios/RNLibrary.xcodeproj/project.pbxproj', + podspecPath: 'root/RNLibrary.podspec', + projectPath: 'root/ios/RNLibrary.xcodeproj', + projectName: 'RNLibrary.xcodeproj', + libraryFolder: 'Libraries', + }, + android: { + sourceDir: '/root/android', + folder: '/root/android', + packageImportPath: 'import com.myproject.RNLibraryPackage;', + packageInstance: 'new RNLibraryPackage()', + }, + }, + }, + }; + + writeFiles(DIR, { + 'react-native.config.js': `module.exports = { + dependencies: ${JSON.stringify(depsWithLocalLibrary, null, 2)} + }`, + }); + + const {dependencies} = loadConfig(DIR); + expect(dependencies).toMatchObject(depsWithLocalLibrary); +}); diff --git a/packages/cli/src/tools/config/index.js b/packages/cli/src/tools/config/index.js index c6e5a088f..06612dfa7 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); From beb7c0c7305257f2681fbeb3e79fe0b67f438957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 30 Jun 2019 21:47:03 +0200 Subject: [PATCH 2/3] simplify local deps config --- docs/autolinking.md | 18 +- docs/projects.md | 9 +- .../src/tools/config/__tests__/index-test.js | 62 ++++--- packages/cli/src/tools/config/index.js | 156 +++++++++--------- .../src/tools/config/readConfigFromDisk.js | 19 ++- packages/cli/src/tools/config/schema.js | 1 + types/index.js | 4 +- 7 files changed, 139 insertions(+), 130 deletions(-) diff --git a/docs/autolinking.md b/docs/autolinking.md index cdd8af6cc..86986a019 100644 --- a/docs/autolinking.md +++ b/docs/autolinking.md @@ -118,23 +118,7 @@ Currently autolinking doesn't support local libraries out of the box. However, w module.exports = { dependencies: { 'local-rn-library': { - platforms: { - ios: { - sourceDir: '/root/ios', - folder: '/root/ios', - pbxprojPath: 'root/ios/RNLibrary.xcodeproj/project.pbxproj', - podspecPath: 'root/RNLibrary.podspec', - projectPath: 'root/ios/RNLibrary.xcodeproj', - projectName: 'RNLibrary.xcodeproj', - libraryFolder: 'Libraries', - }, - android: { - sourceDir: '/root/android', - folder: '/root/android', - packageImportPath: 'import com.myproject.RNLibraryPackage;', - packageInstance: 'new RNLibraryPackage()', - }, - }, + root: '/root/libraries', }, }, }; diff --git a/docs/projects.md b/docs/projects.md index 15dca6a07..abc704497 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -126,14 +126,7 @@ Another use-case would be supporting local libraries that are not discoverable f module.exports = { dependencies: { 'local-rn-library': { - platforms: { - android: { - sourceDir: '/root/android', - folder: '/root/android', - packageImportPath: 'import com.myproject.RNLibraryPackage;', - packageInstance: 'new RNLibraryPackage()', - }, - }, + root: '/root/libraries', }, }, }; diff --git a/packages/cli/src/tools/config/__tests__/index-test.js b/packages/cli/src/tools/config/__tests__/index-test.js index d1b496217..179a6be9f 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" @@ -304,34 +302,46 @@ test('does not use restricted "react-native" key to resolve config from package. }); test('supports default dependencies from user configuration', () => { - const depsWithLocalLibrary = { - 'local-rn-library': { - platforms: { - ios: { - sourceDir: '/root/ios', - folder: '/root/ios', - pbxprojPath: 'root/ios/RNLibrary.xcodeproj/project.pbxproj', - podspecPath: 'root/RNLibrary.podspec', - projectPath: 'root/ios/RNLibrary.xcodeproj', - projectName: 'RNLibrary.xcodeproj', - libraryFolder: 'Libraries', - }, - android: { - sourceDir: '/root/android', - folder: '/root/android', - packageImportPath: 'import com.myproject.RNLibraryPackage;', - packageInstance: 'new RNLibraryPackage()', - }, - }, - }, - }; - writeFiles(DIR, { + 'node_modules/react-native/package.json': '{}', + 'native-libs/local-lib/ios/LocalRNLibrary.xcodeproj/project.pbxproj': '', 'react-native.config.js': `module.exports = { - dependencies: ${JSON.stringify(depsWithLocalLibrary, null, 2)} + dependencies: { + 'local-lib': { + root: "${DIR}/native-libs/local-lib", + }, + } + }`, + 'package.json': `{ + "dependencies": { + "react-native": "0.0.1" + } }`, }); const {dependencies} = loadConfig(DIR); - expect(dependencies).toMatchObject(depsWithLocalLibrary); + 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": null, + "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 06612dfa7..a9924aa95 100644 --- a/packages/cli/src/tools/config/index.js +++ b/packages/cli/src/tools/config/index.js @@ -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[], From 9b3a67d181c873468e3efcb3ae6086c6f58ed9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 2 Jul 2019 09:01:59 +0200 Subject: [PATCH 3/3] address feedback --- docs/autolinking.md | 4 +++- packages/cli/src/tools/config/__tests__/index-test.js | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/autolinking.md b/docs/autolinking.md index 86986a019..09f272959 100644 --- a/docs/autolinking.md +++ b/docs/autolinking.md @@ -99,6 +99,7 @@ A library can add a `react-native.config.js` configuration file, which will cust 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': { @@ -112,9 +113,10 @@ module.exports = { ## How can I autolink a local library? -Currently autolinking doesn't support local libraries out of the box. However, we can leverage CLI configuration to make it "see" the React Native libraries that are not part of our 3rd party dependencies. To make autolinking see custom libraries that are not in `node_modules`, update your `react-native.config.js`'s `dependencies` entry to look like this (adjust paths where necessary): +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': { diff --git a/packages/cli/src/tools/config/__tests__/index-test.js b/packages/cli/src/tools/config/__tests__/index-test.js index 179a6be9f..c65f138e2 100644 --- a/packages/cli/src/tools/config/__tests__/index-test.js +++ b/packages/cli/src/tools/config/__tests__/index-test.js @@ -301,7 +301,7 @@ test('does not use restricted "react-native" key to resolve config from package. expect(spy).not.toHaveBeenCalled(); }); -test('supports default dependencies from user configuration', () => { +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': '', @@ -309,6 +309,11 @@ test('supports default dependencies from user configuration', () => { dependencies: { 'local-lib': { root: "${DIR}/native-libs/local-lib", + platforms: { + ios: { + podspecPath: "custom-path" + } + } }, } }`, @@ -334,7 +339,7 @@ test('supports default dependencies from user configuration', () => { "pbxprojPath": "<>/native-libs/local-lib/ios/LocalRNLibrary.xcodeproj/project.pbxproj", "plist": Array [], "podfile": null, - "podspecPath": null, + "podspecPath": "custom-path", "projectName": "LocalRNLibrary.xcodeproj", "projectPath": "<>/native-libs/local-lib/ios/LocalRNLibrary.xcodeproj", "sharedLibraries": Array [],