From 5a191dadfc61b1ad1dc3b90560fd2ab1fb997370 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 11:39:39 -0700 Subject: [PATCH] [react-packager] Add support for nested node_modules Summary: @public The packager's resolver started out imitating node-haste, which meant that we didn't support nested modules. Now this is a problem. Bigger projects are bound to have versions of different versions of the same package at different levels of the dependency tree. This makes loading dependencies lazy for node_modules and implements the node resolution algorithm. However, it also mantains that some modules are still "haste" format, which currently defaults to "react-native" and "react-tools". Finally, this means ~5 seconds speed up on every server start. This should also have a big impact on open source users with projects with big node_modules. Test Plan: 1- test the app with --reset-cache 2- click around test and production apps 3- update the OSS library 4- create a new project 5- npm install multiple modules 6- create some version conflict in your project 7- make sure we do the "right" thing 8- test file changes to make sure it works --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 + .../__tests__/DependencyGraph-test.js | 1959 +++++++++++++++-- .../haste/DependencyGraph/index.js | 382 +++- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 2125 insertions(+), 377 deletions(-) create mode 100644 packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 90db1c4ad..3c1a1d26d 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,6 +8,9 @@ */ 'use strict'; +var Promise = require('bluebird'); +var isAbsolutePath = require('absolute-path'); + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -17,17 +20,13 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } + if (!isAbsolutePath(fields.path)) { + throw new Error('Expected absolute path but found: ' + fields.path); + } this.path = fields.path; - if (!fields.dependencies) { - throw new Error('Missing required fields dependencies'); - } this.dependencies = fields.dependencies; - this.resolveDependency = fields.resolveDependency; - - this.entry = fields.entry || false; - this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -50,12 +49,30 @@ function ModuleDescriptor(fields) { this._fields = fields; } +ModuleDescriptor.prototype.loadDependencies = function(loader) { + if (!this.dependencies) { + if (this._loadingDependencies) { + return this._loadingDependencies; + } + + var self = this; + this._loadingDependencies = loader(this).then(function(dependencies) { + self.dependencies = dependencies; + }); + return this._loadingDependencies; + } + + return Promise.resolve(this.dependencies); +}; + ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; + var ret = {}; + Object.keys(this).forEach(function(prop) { + if (prop[0] !== '_' && typeof this[prop] !== 'function') { + ret[prop] = this[prop]; + } + }, this); + return ret; }; module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js new file mode 100644 index 000000000..99bed5df7 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('absolute-path') + .dontMock('../ModuleDescriptor'); + + +describe('ModuleDescriptor', function() { + var ModuleDescriptor; + var Promise; + + beforeEach(function() { + ModuleDescriptor = require('../ModuleDescriptor'); + Promise = require('bluebird'); + }); + + describe('constructor', function() { + it('should validate fields', function() { + /* eslint no-new:0*/ + expect(function() { + new ModuleDescriptor({}); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + }); + }).toThrow(); + + var m = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + expect(m.toJSON()).toEqual({ + altId:undefined, + dependencies: undefined, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + }); + }); + + describe('loadDependencies', function() { + pit('should load dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + + pit('should load cached dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + return mod.loadDependencies(function() { + throw new Error('no!'); + }); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + }); +}); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index c247e59d3..6c0fd05f6 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,11 +10,12 @@ jest .dontMock('../index') + .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); + .dontMock('../../../ModuleDescriptor'); jest.mock('fs'); @@ -34,6 +35,14 @@ describe('DependencyGraph', function() { }; }); + // There are a lot of crap in ModuleDescriptors, this maps an array + // to get the relevant data. + function getDataFromModules(modules) { + return modules.map(function(module) { + return module.toJSON(); + }); + } + describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -58,11 +67,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -95,11 +126,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -126,20 +179,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'] + dependencies: ['./a.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -167,15 +231,31 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -205,20 +285,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'] + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -253,8 +344,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', @@ -264,7 +355,13 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ] + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -272,20 +369,32 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -317,14 +426,20 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -332,6 +447,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'image!a', @@ -339,6 +458,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -368,11 +491,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -402,13 +547,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -433,13 +596,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -468,14 +648,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -503,13 +700,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -534,13 +748,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -568,10 +799,21 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -612,18 +854,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', + { + id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'] + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'c', + { + id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -659,17 +915,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage', + { + id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -693,12 +963,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'] + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); }); @@ -732,16 +1009,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -776,16 +1066,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -820,24 +1124,56 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', + altId: undefined, path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', + altId: undefined, path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/other', + { + id: 'aPackage/other', + altId: undefined, path: '/root/aPackage/other.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -870,16 +1206,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/client', + { + id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -912,16 +1264,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -956,16 +1322,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1000,16 +1379,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1054,28 +1446,58 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1120,20 +1542,576 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'] + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + describe('node_modules', function() { + pit('should work with nested node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with specific paths', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/lol', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with browser field', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + browser: { + './lol': './wow' + } + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + 'wow.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + browser: './main2', + }), + 'main2.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/wow', + path: '/root/node_modules/foo/node_modules/bar/wow.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main2', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('node_modules should support multi level', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': '', + }, + }, + 'path': { + 'to': { + 'bar.js': [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")', + ].join('\n'), + }, + 'node_modules': {}, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + altId: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + 'require("submodule");', + ].join('\n'), + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule dontWork', + ' */', + 'hi();', + ].join('\n'), + }, + 'submodule': { + 'package.json': JSON.stringify({ + name: 'submodule', + main: 'main.js', + }), + 'main.js': 'log()', + }, + } + }, + 'ember': { + 'package.json': JSON.stringify({ + name: 'ember', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule wontWork', + ' */', + 'hi();', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + altId:'react-tools/main', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'submodule/main', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { + // For example SourceMap.js implements it's own require system. + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/lol");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1187,22 +2165,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1239,22 +2231,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1291,19 +2297,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1342,7 +2360,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1354,27 +2372,54 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -1400,32 +2445,50 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'] + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1452,30 +2515,49 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'] + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - }, - { id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'index', + altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -1520,7 +2602,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1532,21 +2614,42 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'] + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1584,25 +2687,407 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates package.json', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); + triggerFileChange('change', 'index.js', root); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('changes to browser field', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('removes old package from cache', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package main changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + 'browser.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ + name: 'foo', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser', + altId: undefined, + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 0881e5dc7..fc899cb2f 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,3 +1,6 @@ +// TODO +// Fix it to work with tests + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -19,6 +22,7 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); +var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -45,7 +49,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + _providesModuleNodeModules: { + type: 'array', + default: ['react-tools', 'react-native'], + }, }); function DependecyGraph(options) { @@ -69,6 +77,8 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); + this._providesModuleNodeModules = opts._providesModuleNodeModules; + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -90,59 +100,101 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } + return this.load().then(function() { + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); + } - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } - var self = this; - var deps = []; - var visited = Object.create(null); + var self = this; + var deps = []; + var visited = Object.create(null); - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; - // Recursively collect the dependency list. - function collect(module) { - deps.push(module); + // Recursively collect the dependency list. + function collect(mod) { + deps.push(mod); - module.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(module, id); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - module.id - ); - return; + if (mod.dependencies == null) { + return mod.loadDependencies(function() { + return readFile(mod.path, 'utf8').then(function(content) { + return extractRequires(content); + }); + }).then(function() { + return iter(mod); + }); } - if (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } + return iter(mod); + } + + function iter(mod) { + var p = Promise.resolve(); + mod.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(mod, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + mod.id + ); + return; + } + + p = p.then(function() { + if (!visited[realId(dep)]) { + visited[realId(dep)] = true; + return collect(dep); + } + return null; + }); + }); + return p; + } + + visited[realId(module)] = true; + return collect(module).then(function() { + return deps; }); - } - - visited[module.id] = true; - collect(module); - - return deps; + }.bind(this)); }; +function browserFieldRedirect(packageJson, modulePath, isMain) { + if (packageJson.browser && typeof packageJson.browser === 'object') { + if (isMain) { + var tmpMain = packageJson.browser[modulePath] || + packageJson.browser[sansExtJs(modulePath)] || + packageJson.browser[withExtJs(modulePath)]; + if (tmpMain) { + return tmpMain; + } + } else { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + return path.join(packageJson._root, tmpModulePath); + } + } + } + return modulePath; +} + /** * Given a module descriptor `fromModule` return the module descriptor for * the required module `depModuleId`. It could be top-level or relative, @@ -157,7 +209,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); + debug('WARNING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -177,19 +229,39 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } + + var packageName = depModuleId.replace(/\/.+/, ''); + packageJson = this._lookupNodePackage(fromModule.path, packageName); + + if (packageJson != null && packageName !== depModuleId) { + modulePath = path.join( + packageJson._root, + path.relative(packageName, depModuleId) + ); + + modulePath = browserFieldRedirect(packageJson, modulePath); + + dep = this._graph[withExtJs(modulePath)]; + if (dep != null) { + return dep; + } + } + // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (this._moduleById[sansExtJs(depModuleId)]) { + if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - - // We are being forgiving here and raising an error because we could be - // processing a file that uses it's own require system. if (packageJson == null) { + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; + } + + // We are being forgiving here and not raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null || packageName !== depModuleId) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -198,33 +270,8 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main; - - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - if (packageJson.browser && typeof packageJson.browser === 'object') { - var tmpMain = packageJson.browser[main] || - packageJson.browser[withExtJs(main)] || - packageJson.browser[sansExtJs(main)]; - if (tmpMain) { - main = tmpMain; - } - } - - modulePath = withExtJs(path.join(packageJson._root, main)); - dep = this._graph[modulePath]; - - // Some packages use just a dir and rely on an index.js inside that dir. - if (dep == null) { - dep = this._graph[path.join(packageJson._root, main, 'index.js')]; - } - + // We are requiring node or a haste package via it's main file. + dep = this._resolvePackageMain(packageJson); if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -248,15 +295,7 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - - if (packageJson.browser && typeof packageJson.browser === 'object') { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - modulePath = path.join(packageJson._root, tmpModulePath); - } - } + modulePath = browserFieldRedirect(packageJson, modulePath); // JS modules can be required without extensios. if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { @@ -291,6 +330,25 @@ DependecyGraph.prototype.resolveDependency = function( } }; +DependecyGraph.prototype._resolvePackageMain = function(packageJson) { + var main; + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + main = browserFieldRedirect(packageJson, main, true); + + var modulePath = withExtJs(path.join(packageJson._root, main)); + + return this._graph[modulePath] || + // Some packages use just a dir and rely on an index.js inside that dir. + this._graph[path.join(packageJson._root, main, 'index.js')]; +}; + /** * Intiates the filewatcher and kicks off the search process. */ @@ -339,9 +397,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -358,10 +416,8 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - var packagePath; - for (var i = 0; i < files.length ; i++) { + for (var i = 0; i < files.length; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -406,12 +462,16 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; + if (!this._isInNodeModules(packageJson._root)) { + this._packagesById[packageJson.name] = packageJson; + } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; + if (!this._isInNodeModules(packageJson._root)) { + delete this._packagesById[packageJson.name]; + } }; /** @@ -441,6 +501,14 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (this._isInNodeModules(modulePath)) { + moduleData.id = this._lookupName(modulePath); + moduleData.dependencies = null; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -484,6 +552,10 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; + if (this._isInNodeModules(module.path)) { + return; + } + // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -504,6 +576,10 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; + if (this._isInNodeModules(module.path)) { + return; + } + if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -527,28 +603,14 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = this._packageByRoot; + return lookupPackage(path.dirname(modulePath), this._packageByRoot); +}; - /** - * Auxiliary function to recursively lookup a package. - */ - function lookupPackage(currDir) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir)); - } - } - } - - return lookupPackage(path.dirname(modulePath)); +/** + * Find the nearest node package to a module. + */ +DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { + return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); }; /** @@ -573,12 +635,14 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; + var packageJson; + if (isPackage) { + packageJson = this._packageByRoot[path.dirname(absPath)]; + } + if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } + if (isPackage && packageJson) { + this._removePackageFromIndices(packageJson); } else { var module = this._graph[absPath]; if (module == null) { @@ -591,7 +655,14 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - return self._processPackage(absPath); + self._removePackageFromIndices(packageJson); + return self._processPackage(absPath) + .then(function(p) { + return self._resolvePackageMain(p); + }) + .then(function(mainModule) { + return self._processModule(mainModule.path); + }); } return self._processModule(absPath); }); @@ -675,6 +746,25 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; +DependecyGraph.prototype._isInNodeModules = function(file) { + var inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + var dirs = this._providesModuleNodeModules; + + for (var i = 0; i < dirs.length; i++) { + var index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; +}; + /** * Extract all required modules from a `code` string. */ @@ -784,6 +874,54 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } +function realId(module) { + if (module._realId) { + return module._realId; + } + + var hash = crypto.createHash('md5'); + hash.update(module.id); + hash.update(module.path); + Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); + return module._realId; +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupPackage(currDir, packageByRoot) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir), packageByRoot); + } + } +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupNodePackage(currDir, packageByRoot, packageName) { + if (currDir === '/') { + return null; + } + var packageRoot = path.join(currDir, 'node_modules', packageName); + + var packageJson = packageByRoot[packageRoot]; + if (packageJson) { + return packageJson; + } else { + return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); + } +} + function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9bc8b8b95..1f8c95479 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index da68785ea..d7a8c0eb1 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -91,9 +91,8 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); + return depGraph.getOrderedDependencies(main) + .then(function(dependencies) { var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev);