Skip to content

Commit

Permalink
Fix providesModuleNodeModules behavior to be more deterministic.
Browse files Browse the repository at this point in the history
Currently, the `providesModuleNodeModules` option allows for specifying an array of package names, that the packager will look for within node_modules, no matter how deep they're nested, and treat them as packages that use the `@providesModule` pragma.

In reality, this is not actually the behavior we want.

npm, because of how it handles dependencies, can do all kinds of arbitrary nesting of packages when `npm install` is run. This causes a problem for how we deal with `providesModuleNodeModules`. Specifically...take `fbjs`. Currently, React Native relies on using the Haste version of `fbjs` (will be resolved in facebook#5084). Thus if npm installs multiple copies of fbjs...which is a very common thing for it to do (as can be seen by this list of issues: https://github.com/facebook/react-native/issues?utf8=%E2%9C%93&q=naming+collision+detected), we get into a state where the packager fails and says that we have a naming collision.

Really, the behavior we want is for the packager to treat only a *single* copy of a given package, that we specify in the `Resolver` in the `providesModuleNodeModules` option, as the package that it uses when trying to resolve Haste modules.

This PR provides that behavior, by changing `providesModuleNodeModules` from a list of strings to a list of objects that have a `name` key, specifying the package name, as well as a `parent` key. If `parent` is null, it will look for the package at the top level (directly under `node_modules`). If `parent` is specified, it will use the package that is nested under that parent as the Haste module.

To anyone who reads this PR and is familiar with the differences between npm2 and npm3 -- this solution works under both, given the fact that we are now shipping the NPM Shrinkwrap file with React Native when it's installed through `npm`. In both the npm2 and npm3 case, node_modules specified by RN's package.json are nested underneath `react-native` in node_modules, thus allowing us to specify, for example, that we want to use React Native's copy of `fbjs` (not any other copy that may be installed) as the module used by the packager to resolve any `requires` that reference a module in `fbjs`.
  • Loading branch information
skevy committed Feb 17, 2016
1 parent f2b95f2 commit 4c84cfb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
Expand All @@ -16,16 +16,48 @@ class DependencyGraphHelpers {
this._assetExts = assetExts;
}

/**
* This method has three possible outcomes:
*
* 1) A file is not in "node_modules" at all (some type of file in the project).
* 2) The file is in "node_modules", and it's contained in one of the
* "providesModuleNodeModules" packages.
* 3) It's in "node_modules" but not in a "providesModuleNodeModule" package,
* so it's just a normal node_module.
*
* An item in the `providesModuleNodeModule` is an object containing two keys:
* * "name" - the package name
* * "parent" - if a string, it's the name of the parent package of the module in
* the dep tree. If null, it signifies that we should look for this package at
* the top level of the module tree.
*/
isNodeModulesDir(file) {
const index = file.lastIndexOf('/node_modules/');
const index = file.indexOf('/node_modules/');
if (index === -1) {
return false;
}

const parts = file.substr(index + 14).split(path.sep);
const dirs = this._providesModuleNodeModules;
for (let i = 0; i < dirs.length; i++) {
if (parts.indexOf(dirs[i]) > -1) {
const lastIndex = file.lastIndexOf('/node_modules/');
const fullPathParts = file.substr(index + 14).split(path.sep);
const packageParts = file.substr(lastIndex + 14).split(path.sep);
const packageName = packageParts[0];

const modules = this._providesModuleNodeModules;
for (let i = 0; i < modules.length; i++) {
const providesModuleNodeModule = modules[i];
const providesModulePackageName = providesModuleNodeModule.name;
const providesModuleParent = providesModuleNodeModule.parent;

let parent = null;
// if the top level package name is not the same as what we already
// computed the "packageName" to be, then make sure we record that
// that package is the parent
if (fullPathParts[0] !== packageName) {
parent = fullPathParts[0];
}

if (providesModulePackageName === packageName &&
providesModuleParent === parent) {
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ describe('DependencyGraph', function() {
cache: new Cache(),
fileWatcher,
providesModuleNodeModules: [
'haste-fbjs',
'react-haste',
'react-native',
{ name: 'haste-fbjs', parent: null },
{ name: 'react-haste', parent: null },
{ name: 'react-native', parent: null },
// Parse requires AsyncStorage. They will
// change that to require('react-native') which
// should work after this release and we can
Expand Down
15 changes: 11 additions & 4 deletions packager/react-packager/src/Resolver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,21 @@ class Resolver {
(opts.blacklistRE && opts.blacklistRE.test(filepath));
},
providesModuleNodeModules: [
'fbjs',
'react',
'react-native',
// Use the project's installed version of react-native
// as the "haste" version of `react-native`
// (and ignore any nested copies);
{ name: 'react-native', parent: null },
// Use the react peerDep for the "haste"
// version of `react`.
{ name: 'react', parent: null },
// We only want to use the fbjs underneath react-native
// as the "haste" version of `fbjs`.
{ name: 'fbjs', parent: 'react-native'},
// Parse requires AsyncStorage. They will
// change that to require('react-native') which
// should work after this release and we can
// remove it from here.
'parse',
{ name: 'parse', parent: null },
],
platforms: ['ios', 'android'],
preferNativePlatform: true,
Expand Down

0 comments on commit 4c84cfb

Please sign in to comment.