Skip to content

Commit

Permalink
Fix @providesModule not being ignored properly
Browse files Browse the repository at this point in the history
Summary:
There's quite a bit of code scattered around the packager regarding ignoring the `providesModule` Haste pragma in any file that isn't in `react-native`, `react-tools` or `parse`. There is even a (passing) test case.

However, there's an edge case.

Take, for example, `fbjs`. It has a module inside of it called `ErrorUtils`. `react-relay` requires this file normally, in Common.JS style, by doing `require('fbjs/libs/ErrorUtils')`. But when `react-native` attempts to require `ErrorUtils` using the HasteModule format (in it's JavaScript initialization), it resolves the `fbjs` `ErrorUtils` module, instead of RN's `ErrorUtils`.

This happens, it turns out, because when a module is read (in `Module._read`), it's not caring about whether or not it should pay attention to `providesModule`, and is just assigning the `providesModule` value as the id of the module no matter what. Then when `Module.getName` is called, it will always use that `data.id` that was set, thus creating the wrong dependency tree.

This
Closes #3625

Reviewed By: svcscm

Differential Revision: D2632317

Pulled By: vjeux

fb-gh-sync-id: efd8066eaf6f18fcf79698beab36cab90bf5cd6d
  • Loading branch information
skevy authored and facebook-github-bot-5 committed Dec 24, 2015
1 parent 5988591 commit 6cec263
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class AssetModule extends Module {
return Promise.resolve([]);
}

_read() {
read() {
return Promise.resolve({});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

const path = require('path');

class Helpers {
class DependencyGraphHelpers {
constructor({ providesModuleNodeModules, assetExts }) {
this._providesModuleNodeModules = providesModuleNodeModules;
this._assetExts = assetExts;
Expand Down Expand Up @@ -46,4 +46,4 @@ class Helpers {
}
}

module.exports = Helpers;
module.exports = DependencyGraphHelpers;
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class DeprecatedAssetMap {
debug('Conflcting assets', name);
}

this._map[name] = new AssetModule_DEPRECATED(file);
this._map[name] = new AssetModule_DEPRECATED({ file });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,7 @@ describe('DependencyGraph', function() {

pit('should selectively ignore providesModule in node_modules', function() {
var root = '/root';
var otherRoot = '/anotherRoot';
fs.__setMockFilesystem({
'root': {
'index.js': [
Expand All @@ -2371,13 +2372,17 @@ describe('DependencyGraph', function() {
'require("shouldWork");',
'require("dontWork");',
'require("wontWork");',
'require("ember");',
'require("internalVendoredPackage");',
'require("anotherIndex");',
].join('\n'),
'node_modules': {
'react-haste': {
'package.json': JSON.stringify({
name: 'react-haste',
main: 'main.js',
}),
// @providesModule should not be ignored here, because react-haste is whitelisted
'main.js': [
'/**',
' * @providesModule shouldWork',
Expand All @@ -2390,6 +2395,7 @@ describe('DependencyGraph', function() {
name: 'bar',
main: 'main.js',
}),
// @providesModule should be ignored here, because it's not whitelisted
'main.js':[
'/**',
' * @providesModule dontWork',
Expand All @@ -2411,28 +2417,63 @@ describe('DependencyGraph', function() {
name: 'ember',
main: 'main.js',
}),
// @providesModule should be ignored here, because it's not whitelisted,
// and also, the modules "id" should be ember/main.js, not it's haste name
'main.js':[
'/**',
' * @providesModule wontWork',
' */',
'hi();',
].join('\n'),
].join('\n')
},
},
// This part of the dep graph is meant to emulate internal facebook infra.
// By whitelisting `vendored_modules`, haste should still work.
'vendored_modules': {
'a-vendored-package': {
'package.json': JSON.stringify({
name: 'a-vendored-package',
main: 'main.js',
}),
// @providesModule should _not_ be ignored here, because it's whitelisted.
'main.js':[
'/**',
' * @providesModule internalVendoredPackage',
' */',
'hiFromInternalPackage();',
].join('\n'),
}
},
},
// we need to support multiple roots and using haste between them
'anotherRoot': {
'index.js': [
'/**',
' * @providesModule anotherIndex',
' */',
'wazup()',
].join('\n'),
}
});

var dgraph = new DependencyGraph({
...defaults,
roots: [root],
roots: [root, otherRoot],
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: ['shouldWork', 'dontWork', 'wontWork'],
dependencies: [
'shouldWork',
'dontWork',
'wontWork',
'ember',
'internalVendoredPackage',
'anotherIndex'
],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
Expand All @@ -2459,6 +2500,36 @@ describe('DependencyGraph', function() {
isPolyfill: false,
resolution: undefined,
},
{
id: 'ember/main.js',
path: '/root/node_modules/ember/main.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'internalVendoredPackage',
path: '/root/vendored_modules/a-vendored-package/main.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'anotherIndex',
path: '/anotherRoot/index.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const getPlatformExtension = require('../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const util = require('util');
const Helpers = require('./Helpers');
const DependencyGraphHelpers = require('./DependencyGraphHelpers');
const ResolutionRequest = require('./ResolutionRequest');
const ResolutionResponse = require('./ResolutionResponse');
const HasteMap = require('./HasteMap');
Expand Down Expand Up @@ -57,7 +57,7 @@ class DependencyGraph {
extractRequires,
};
this._cache = this._opts.cache;
this._helpers = new Helpers(this._opts);
this._helpers = new DependencyGraphHelpers(this._opts);
this.load().catch((err) => {
// This only happens at initialization. Live errors are easier to recover from.
console.error('Error building DependencyGraph:\n', err.stack);
Expand Down Expand Up @@ -97,7 +97,8 @@ class DependencyGraph {
this._moduleCache = new ModuleCache(
this._fastfs,
this._cache,
this._opts.extractRequires
this._opts.extractRequires,
this._helpers
);

this._hasteMap = new HasteMap({
Expand Down
23 changes: 16 additions & 7 deletions packager/react-packager/src/DependencyResolver/Module.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const extractRequires = require('./lib/extractRequires');

class Module {

constructor(file, fastfs, moduleCache, cache, extractor) {
constructor({ file, fastfs, moduleCache, cache, extractor, depGraphHelpers }) {
if (!isAbsolutePath(file)) {
throw new Error('Expected file to be absolute path but got ' + file);
}
Expand All @@ -27,17 +27,18 @@ class Module {
this._moduleCache = moduleCache;
this._cache = cache;
this._extractor = extractor;
this._depGraphHelpers = depGraphHelpers;
}

isHaste() {
return this._read().then(data => !!data.id);
return this.read().then(data => !!data.id);
}

getName() {
return this._cache.get(
this.path,
'name',
() => this._read().then(data => {
() => this.read().then(data => {
if (data.id) {
return data.id;
}
Expand Down Expand Up @@ -66,23 +67,31 @@ class Module {
}

getDependencies() {
return this._read().then(data => data.dependencies);
return this.read().then(data => data.dependencies);
}

getAsyncDependencies() {
return this._read().then(data => data.asyncDependencies);
return this.read().then(data => data.asyncDependencies);
}

invalidate() {
this._cache.invalidate(this.path);
}

_read() {
read() {
if (!this._reading) {
this._reading = this._fastfs.readFile(this.path).then(content => {
const data = {};

// Set an id on the module if it's using @providesModule syntax
// and if it's NOT in node_modules (and not a whitelisted node_module).
// This handles the case where a project may have a dep that has @providesModule
// docblock comments, but doesn't want it to conflict with whitelisted @providesModule
// modules, such as react-haste, fbjs-haste, or react-native or with non-dependency,
// project-specific code that is using @providesModule.
const moduleDocBlock = docblock.parseAsObject(content);
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
if (!this._depGraphHelpers.isNodeModulesDir(this.path) &&
(moduleDocBlock.providesModule || moduleDocBlock.provides)) {
data.id = /^(\S*)/.exec(
moduleDocBlock.providesModule || moduleDocBlock.provides
)[1];
Expand Down
40 changes: 21 additions & 19 deletions packager/react-packager/src/DependencyResolver/ModuleCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,52 @@ const path = require('path');

class ModuleCache {

constructor(fastfs, cache, extractRequires) {
constructor(fastfs, cache, extractRequires, depGraphHelpers) {
this._moduleCache = Object.create(null);
this._packageCache = Object.create(null);
this._fastfs = fastfs;
this._cache = cache;
this._extractRequires = extractRequires;
this._depGraphHelpers = depGraphHelpers;
fastfs.on('change', this._processFileChange.bind(this));
}

getModule(filePath) {
filePath = path.resolve(filePath);
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new Module(
filePath,
this._fastfs,
this,
this._cache,
this._extractRequires
);
this._moduleCache[filePath] = new Module({
file: filePath,
fastfs: this._fastfs,
moduleCache: this,
cache: this._cache,
extractor: this._extractRequires,
depGraphHelpers: this._depGraphHelpers
});
}
return this._moduleCache[filePath];
}

getAssetModule(filePath) {
filePath = path.resolve(filePath);
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new AssetModule(
filePath,
this._fastfs,
this,
this._cache,
);
this._moduleCache[filePath] = new AssetModule({
file: filePath,
fastfs: this._fastfs,
moduleCache: this,
cache: this._cache,
});
}
return this._moduleCache[filePath];
}

getPackage(filePath) {
filePath = path.resolve(filePath);
if (!this._packageCache[filePath]) {
this._packageCache[filePath] = new Package(
filePath,
this._fastfs,
this._cache,
);
this._packageCache[filePath] = new Package({
file: filePath,
fastfs: this._fastfs,
cache: this._cache,
});
}
return this._packageCache[filePath];
}
Expand Down
12 changes: 6 additions & 6 deletions packager/react-packager/src/DependencyResolver/Package.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const path = require('path');

class Package {

constructor(file, fastfs, cache) {
constructor({ file, fastfs, cache }) {
this.path = path.resolve(file);
this.root = path.dirname(this.path);
this._fastfs = fastfs;
Expand All @@ -14,7 +14,7 @@ class Package {
}

getMain() {
return this._read().then(json => {
return this.read().then(json => {
var replacements = getReplacements(json);
if (typeof replacements === 'string') {
return path.join(this.root, replacements);
Expand All @@ -36,13 +36,13 @@ class Package {

isHaste() {
return this._cache.get(this.path, 'package-haste', () =>
this._read().then(json => !!json.name)
this.read().then(json => !!json.name)
);
}

getName() {
return this._cache.get(this.path, 'package-name', () =>
this._read().then(json => json.name)
this.read().then(json => json.name)
);
}

Expand All @@ -51,7 +51,7 @@ class Package {
}

redirectRequire(name) {
return this._read().then(json => {
return this.read().then(json => {
var replacements = getReplacements(json);

if (!replacements || typeof replacements !== 'object') {
Expand Down Expand Up @@ -81,7 +81,7 @@ class Package {
});
}

_read() {
read() {
if (!this._reading) {
this._reading = this._fastfs.readFile(this.path)
.then(jsonStr => JSON.parse(jsonStr));
Expand Down
2 changes: 1 addition & 1 deletion packager/react-packager/src/DependencyResolver/Polyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Module = require('./Module');

class Polyfill extends Module {
constructor({ path, id, dependencies }) {
super(path);
super({ file: path });
this._id = id;
this._dependencies = dependencies;
}
Expand Down
Loading

0 comments on commit 6cec263

Please sign in to comment.