-
Notifications
You must be signed in to change notification settings - Fork 70
Fix providesModuleNodeModules behavior to be more deterministic. #33
Fix providesModuleNodeModules behavior to be more deterministic. #33
Conversation
NICE |
* or examples). | ||
*/ | ||
_resolveHastePackages(packages) { | ||
const _packagePathForPackage = ({ name, parent }, rootDir) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you drop the leading underscore? :)
9661c67
to
ed4669e
Compare
packagePath = path.join(rootDir, 'node_modules', name); | ||
} | ||
|
||
if (packagePath[packagePath.length - 1] === path.sep) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think .endsWith
here would be fine too :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read this correctly, this can only ever be true if name ends with path.sep
. Does that happen or is this defensive programming? Maybe it’s better sanitizing at the top of the function? Does it matter at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can happen in the case of a providesModuleNodeModule entry that looks like:
{ name: 'some-module', parent: ['parentPackage1', 'parentPackage2'] }
, as the path will end with /node_modules/
Alright, looks great to me. I'd be happy to merge. I think it makes sense to add one or two tests for |
isNodeModulesDir(file) { | ||
const index = file.lastIndexOf('/node_modules/'); | ||
const index = file.indexOf('/node_modules/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file.indexOf(
${path.sep}node_modules${path.sep});
? that’s so ugly that you might want to put a constant at the top of the file :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thank you for this PR!
? Add some meaningful tests, please, and make sure you actually see them fail once without your code changes. |
As @cpojer mentioned, this code has some hefty micro optimizations, especially around the consider turning these: parent.join(path.sep + 'node_modules' + path.sep)
// …
packagePath = path.join(rootDir, 'node_modules', name);
// …
const index = file.indexOf('/node_modules/'); into const NODE_MODULES = path.sep + 'node_modules' + path.sep;
// …
parent.join(NODE_MODULES)
// …
packagePath = rootDir + NODE_MODULES + name;
// …
const index = file.indexOf(NODE_MODULES); |
@davidaurelio yep that case is exactly what this PR is meant to address -- as I say in my PR description, the issue with how this was being handled before was that the providesModuleNodeModules option wasn't deterministic enough to guarantee that we were looking for the correct version of a package that was listed (such as fbjs)...it would just treat all of versions that it found as Haste packages, resulting in duplicate errors. This PR resolves that. Per both you and @cpojer's comments, I will add a few tests (and will make sure they're valid tests haha). Also, I've already noticed the perf issue and am in the process of profiling the new impl of this method against the old one, and am determined to at least make it match the old perf (if not better it slightly). |
I spoke to @cpojer offline about what I'm working on re this PR as well...if it weren't for silly time zones it probably would have been done before you even got a chance to do your review of this :-p Thanks for your comments :-) |
Just updated stuff @davidaurelio @cpojer. I made the fixes requested in both of your comments and also added tests (both for DependencyGraphHelpers directly as well as another test in DependencyGraph-tests...which I confirmed was broken with the current code in master). In addition, I was able to make a pretty significant perf improvement. Instead of looping through and doing string comparisons in I'm able to get a > 90% perf improvement on that method using the regex technique. I also confirmed that this perf improvement exists on both Node 4 and 5. |
4f0118f
to
4a8458c
Compare
if (parts.indexOf(dirs[i]) > -1) { | ||
return false; | ||
} | ||
if (this._hasteRegex.test(file)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return !this._hasteRegex.test(file);
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Definitely. My bad.
Nice, thanks for the work and for making the perf better :) I'll defer to @davidaurelio to take another look and merge it. |
4a8458c
to
6e092bb
Compare
Ok, got rid of the |
6e092bb
to
081ee58
Compare
@skevy thank you for taking the time to wrap this up. Lgtm. The leading underscore convention for module-private functions looks a little awkward to me, but I can live with it. I’m just. figuring out the best order to merge all open PRs. Patch-level stuff first. Given that this changes the format of Your patch has conflicts. |
@skevy are you rebasing this so @davidaurelio can merge it? |
c97b296
to
91520b6
Compare
@davidaurelio @cpojer sorry for the delay on this. was traveling. Fixed conflicts and got rid of that underscore in the module private function. Should be good to go. |
efd561b
to
7cdf088
Compare
7cdf088
to
fc3cf7f
Compare
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 #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`.
fc3cf7f
to
7707b17
Compare
Fix providesModuleNodeModules behavior to be more deterministic.
Published as v2.6.0 |
This PR completely breaks Jest 0.9 unfortunately. We need to revert and re-publish or fix this ASAP (and the next version of Jest should lock node-haste to ~ instead of ^). Pretty sure the same thing will come up in react-native too. See jestjs/jest#771 – the git repo included ( https://github.com/AdamKyle/Toy-Box ) shows well how it breaks when you remove everything inside of |
--- Relates to facebook/react-native#5985 ---
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 withprovidesModuleNodeModules
. Specifically...takefbjs
. Currently, React Native relies on using the Haste version offbjs
(will be resolved in #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 theprovidesModuleNodeModules
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 aname
key, specifying the package name, as well as aparent
key. Ifparent
is null, it will look for the package at the top level (directly undernode_modules
). Ifparent
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 underneathreact-native
in node_modules, thus allowing us to specify, for example, that we want to use React Native's copy offbjs
(not any other copy that may be installed) as the module used by the packager to resolve anyrequires
that reference a module infbjs
.