diff --git a/blacklist.js b/blacklist.js index 9bfeda50..a2ba7167 100644 --- a/blacklist.js +++ b/blacklist.js @@ -11,7 +11,6 @@ // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - __dirname, 'website', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', diff --git a/debugger.html b/debugger.html index d72e40ea..0d008455 100644 --- a/debugger.html +++ b/debugger.html @@ -48,10 +48,11 @@ var messageHandlers = { loadScript(message.url, sendReply.bind(null, null)); }, 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; + var returnValue = null; try { if (window && window.require) { - returnValue = window.require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); + var module = window.require(message.moduleName); + returnValue = module[message.moduleMethod].apply(module, message.arguments); } } finally { sendReply(JSON.stringify(returnValue)); diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript index 4b718f5b..a8390790 100755 --- a/launchChromeDevTools.applescript +++ b/launchChromeDevTools.applescript @@ -11,7 +11,6 @@ on run argv set theURL to item 1 of argv tell application "Chrome" - activate if (count every window) = 0 then make new window @@ -40,6 +39,7 @@ on run argv set theWindow's active tab index to theTabIndex else tell window 1 + activate make new tab with properties {URL:theURL} end tell end if diff --git a/packager.js b/packager.js index 7dd013c6..f2d526e0 100644 --- a/packager.js +++ b/packager.js @@ -66,6 +66,9 @@ if (options.projectRoots) { if (__dirname.match(/node_modules\/react-native\/packager$/)) { // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + // packager is running from node_modules of another project + options.projectRoots = [path.resolve(__dirname, '../../..')]; } else { options.projectRoots = [path.resolve(__dirname, '..')]; } @@ -83,11 +86,15 @@ if (options.root) { if (options.assetRoots) { if (!Array.isArray(options.assetRoots)) { - options.assetRoots = options.assetRoots.split(','); + options.assetRoots = options.assetRoots.split(',').map(function (dir) { + return path.resolve(process.cwd(), dir); + }); } } else { if (__dirname.match(/node_modules\/react-native\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; } else { options.assetRoots = [path.resolve(__dirname, '..')]; } diff --git a/react-packager/.babelrc b/react-packager/.babelrc new file mode 100644 index 00000000..c1b12d81 --- /dev/null +++ b/react-packager/.babelrc @@ -0,0 +1,24 @@ +// Keep in sync with packager/transformer.js +{ + "retainLines": true, + "compact": true, + "comments": false, + "whitelist": [ + "es6.arrowFunctions", + "es6.blockScoping", + // This is the only place where we differ from transformer.js + "es6.constants", + "es6.classes", + "es6.destructuring", + "es6.parameters.rest", + "es6.properties.computed", + "es6.properties.shorthand", + "es6.spread", + "es6.templateLiterals", + "es7.trailingFunctionCommas", + "es7.objectRestSpread", + "flow", + "react" + ], + "sourceMaps": false +} diff --git a/react-packager/__mocks__/bluebird.js b/react-packager/__mocks__/bluebird.js deleted file mode 100644 index 9ac6e14b..00000000 --- a/react-packager/__mocks__/bluebird.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -jest.autoMockOff(); -module.exports = require.requireActual('bluebird'); -jest.autoMockOn(); diff --git a/react-packager/index.js b/react-packager/index.js index 6be11199..d4ea0dd3 100644 --- a/react-packager/index.js +++ b/react-packager/index.js @@ -8,6 +8,10 @@ */ 'use strict'; +require('babel/register')({ + only: /react-packager\/src/ +}); + useGracefulFs(); var Activity = require('./src/Activity'); diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index c6acc6a8..95916c9e 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -8,7 +8,7 @@ jest .mock('crypto') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('AssetServer', function() { var AssetServer; diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 9bae2682..2cd365fd 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -11,13 +11,13 @@ var declareOpts = require('../lib/declareOpts'); var getAssetDataFromName = require('../lib/getAssetDataFromName'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var fs = require('fs'); var crypto = require('crypto'); -var lstat = Promise.promisify(fs.lstat); -var readDir = Promise.promisify(fs.readdir); -var readFile = Promise.promisify(fs.readFile); +var stat = Promise.denodeify(fs.stat); +var readDir = Promise.denodeify(fs.readdir); +var readFile = Promise.denodeify(fs.readFile); module.exports = AssetServer; @@ -56,12 +56,15 @@ AssetServer.prototype._getAssetRecord = function(assetPath) { this._roots, path.dirname(assetPath) ).then(function(dir) { - return [ + return Promise.all([ dir, readDir(dir), - ]; - }).spread(function(dir, files) { + ]); + }).then(function(res) { + var dir = res[0]; + var files = res[1]; var assetData = getAssetDataFromName(filename); + var map = buildAssetMap(dir, files); var record = map[assetData.assetName]; @@ -98,14 +101,14 @@ AssetServer.prototype.getAssetData = function(assetPath) { return Promise.all( record.files.map(function(file) { - return lstat(file); + return stat(file); }) ); }).then(function(stats) { var hash = crypto.createHash('md5'); - stats.forEach(function(stat) { - hash.update(stat.mtime.getTime().toString()); + stats.forEach(function(fstat) { + hash.update(fstat.mtime.getTime().toString()); }); data.hash = hash.digest('hex'); @@ -114,21 +117,23 @@ AssetServer.prototype.getAssetData = function(assetPath) { }; function findRoot(roots, dir) { - return Promise.some( + return Promise.all( roots.map(function(root) { var absPath = path.join(root, dir); - return lstat(absPath).then(function(stat) { - if (!stat.isDirectory()) { - throw new Error('Looking for dirs'); - } - stat._path = absPath; - return stat; + return stat(absPath).then(function(fstat) { + return {path: absPath, isDirectory: fstat.isDirectory()}; + }, function (err) { + return {path: absPath, isDirectory: false}; }); - }), - 1 - ).spread( - function(stat) { - return stat._path; + }) + ).then( + function(stats) { + for (var i = 0; i < stats.length; i++) { + if (stats[i].isDirectory) { + return stats[i].path; + } + } + throw new Error('Could not find any directories'); } ); } diff --git a/react-packager/src/DependencyResolver/AssetModule.js b/react-packager/src/DependencyResolver/AssetModule.js new file mode 100644 index 00000000..bfe4b6f8 --- /dev/null +++ b/react-packager/src/DependencyResolver/AssetModule.js @@ -0,0 +1,46 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('promise'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule extends Module { + + isHaste() { + return Promise.resolve(false); + } + + getDependencies() { + return Promise.resolve([]); + } + + _read() { + return Promise.resolve({}); + } + + getName() { + return super.getName().then(id => { + const {name, type} = getAssetDataFromName(this.path); + return id.replace(/\/[^\/]+$/, `/${name}.${type}`); + }); + } + + getPlainObject() { + return this.getName().then(name => this.addReference({ + path: this.path, + isJSON: false, + isAsset: true, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: getAssetDataFromName(this.path).resolution, + id: name, + dependencies: [], + })); + } + + hash() { + return `AssetModule : ${this.path}`; + } +} + +module.exports = AssetModule; diff --git a/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js new file mode 100644 index 00000000..fd4cb708 --- /dev/null +++ b/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -0,0 +1,40 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('promise'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule_DEPRECATED extends Module { + isHaste() { + return Promise.resolve(false); + } + + getName() { + return Promise.resolve(this.name); + } + + getDependencies() { + return Promise.resolve([]); + } + + getPlainObject() { + const {name, resolution} = getAssetDataFromName(this.path); + + return Promise.resolve(this.addReference({ + path: this.path, + id: `image!${name}`, + resolution, + isAsset_DEPRECATED: true, + dependencies: [], + isJSON: false, + isPolyfill: false, + isAsset: false, + })); + } + + hash() { + return `AssetModule_DEPRECATED : ${this.path}`; + } +} + +module.exports = AssetModule_DEPRECATED; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js new file mode 100644 index 00000000..dbe7dc3a --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -0,0 +1,3241 @@ +/** + * 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('../index') + .dontMock('crypto') + .dontMock('absolute-path') + .dontMock('../docblock') + .dontMock('../../replacePatterns') + .dontMock('../../../lib/getAssetDataFromName') + .dontMock('../../fastfs') + .dontMock('../../AssetModule_DEPRECATED') + .dontMock('../../AssetModule') + .dontMock('../../Module') + .dontMock('../../Package') + .dontMock('../../ModuleCache'); + +jest.mock('fs'); + +describe('DependencyGraph', function() { + var DependencyGraph; + var fileWatcher; + var fs; + + beforeEach(function() { + fs = require('fs'); + DependencyGraph = require('../index'); + + fileWatcher = { + on: function() { + return this; + } + }; + }); + + describe('getOrderedDependencies', function() { + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + ]); + }); + }); + + pit('should get dependencies with the correct extensions', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'a.js.orig': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get json dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./a.json")', + 'require("./b")' + ].join('\n'), + 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./a.json', './b'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/a.json', + isJSON: true, + path: '/root/a.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get dependencies with deprecated assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!a")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with relative assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with assets and resolution', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png");', + 'require("./imgs/b.png");', + 'require("./imgs/c.png");', + ].join('\n'), + 'imgs': { + 'a@1.5x.png': '', + 'b@.7x.png': '', + 'c.png': '', + 'c@2x.png': '', + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [ + './imgs/a.png', + './imgs/b.png', + './imgs/c.png', + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a@1.5x.png', + resolution: 1.5, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/b.png', + path: '/root/imgs/b@.7x.png', + resolution: 0.7, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/c.png', + path: '/root/imgs/c.png', + resolution: 1, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('Deprecated and relative assets can live together', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")', + 'require("image!a")', + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get recursive dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("index")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages with a dot in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + 'require("x.y.z")', + ].join('\n'), + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + }, + 'x.y.z': { + 'package.json': JSON.stringify({ + name: 'x.y.z', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'x.y.z/main.js', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default main package to index.js', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': 'lol', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve using alternative ids', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': [ + '/**', + ' * @providesModule EpicModule', + ' */', + ].join('\n'), + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'EpicModule', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default use index.js if main is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'lib', + }), + lib: { + 'index.js': 'lol', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/lib/index.js', + path: '/root/aPackage/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to index if it is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'index.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'test/lib/index.js', + path: '/root/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to main if it is a dir w/ a package.json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'package.json': JSON.stringify({ + 'main': 'main.js', + }), + 'index.js': 'lol', + 'main.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore malformed packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': 'lol', + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('can have multiple modules with the same name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + 'c.js': [ + '/**', + ' * @providesModule c', + ' */', + ].join('\n'), + 'somedir': { + 'somefile.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("c")', + ].join('\n') + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/somedir/somefile.js', + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'c', + path: '/root/c.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('providesModule wins when conflict with package', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule aPackage', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage', + path: '/root/b.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should be forgiving with missing requires', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("lolomg")', + ].join('\n') + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + }); + }); + + pit('should work with packages with subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with packages with symlinked subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'symlinkedPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + }, + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { SYMLINK: '/symlinkedPackage' }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with relative modules in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'require("./subdir/lolynot")', + 'subdir': { + 'lolynot.js': 'require("../other")' + }, + 'other.js': 'some code' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/other.js', + path: '/root/aPackage/other.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support simple browser field in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser field in packages w/o .js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support mapping main in browser field json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping of files', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + './hello.js': './bye.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'require("../hello")', + }, + 'hello.js': 'hello', + 'bye.js': 'bye', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/not-node.js', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/dir/client.js', + path: '/root/aPackage/dir/client.js', + dependencies: ['../hello'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/bye.js', + path: '/root/aPackage/bye.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping for packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main2.js', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'submodule/main.js', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with node packages with a .js in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + ].join('\n'), + 'node_modules': { + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('file watch updating', function() { + var triggerFileChange; + var mockStat = { + isDirectory: () => false + }; + + beforeEach(function() { + var callbacks = []; + triggerFileChange = (...args) => + callbacks.map(callback => callback(...args)); + + fileWatcher = { + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + callbacks.push(callback); + return this; + } + }; + }); + + pit('updates module dependencies', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo': [ + '/**', + ' * @providesModule foo', + ' */', + '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('require("foo")', ''); + triggerFileChange('change', 'index.js', root, mockStat); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file change', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + '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('require("foo")', ''); + triggerFileChange('change', 'index.js', root, mockStat); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file delete', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + '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() { + delete filesystem.root.foo; + triggerFileChange('delete', 'foo.js', root); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + '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['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on deprecated asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots_DEPRECATED: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on relative asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./foo.png")' + ].join('\n'), + 'package.json': JSON.stringify({ + name: 'aPackage' + }), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('runs changes through ignore filter', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + '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'], + ignoreFilePath: function(filePath) { + if (filePath === '/root/bar.js') { + return true; + } + return false; + } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should ignore directory updates', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + '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() { + triggerFileChange('change', 'aPackage', '/root', { + isDirectory: function(){ return true; } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + 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, mockStat); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser.js', + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + }); +}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/react-packager/src/DependencyResolver/DependencyGraph/docblock.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js rename to react-packager/src/DependencyResolver/DependencyGraph/docblock.js diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js new file mode 100644 index 00000000..63ba7875 --- /dev/null +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -0,0 +1,556 @@ +/** + * 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'; + +const path = require('path'); +const Fastfs = require('../fastfs'); +const ModuleCache = require('../ModuleCache'); +const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); +const declareOpts = require('../../lib/declareOpts'); +const isAbsolutePath = require('absolute-path'); +const debug = require('debug')('DependencyGraph'); +const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const util = require('util'); +const Promise = require('promise'); +const _ = require('underscore'); + +const validateOpts = declareOpts({ + roots: { + type: 'array', + required: true, + }, + ignoreFilePath: { + type: 'function', + default: function(){} + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetRoots_DEPRECATED: { + type: 'array', + default: [], + }, + assetExts: { + type: 'array', + required: true, + }, + providesModuleNodeModules: { + type: 'array', + default: [ + 'react-tools', + 'react-native', + // Parse requires AsyncStorage. They will + // change that to require('react-native') which + // should work after this release and we can + // remove it from here. + 'parse', + ], + }, +}); + +class DependencyGraph { + constructor(options) { + this._opts = validateOpts(options); + this._hasteMap = Object.create(null); + this._immediateResolutionCache = Object.create(null); + this.load(); + } + + load() { + if (this._loading) { + return this._loading; + } + + const modulePattern = new RegExp( + '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' + ); + + this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { + pattern: modulePattern, + ignore: this._opts.ignoreFilePath, + }); + + this._fastfs.on('change', this._processFileChange.bind(this)); + + this._moduleCache = new ModuleCache(this._fastfs); + + this._loading = Promise.all([ + this._fastfs.build().then(() => this._buildHasteMap()), + this._buildAssetMap_DEPRECATED(), + ]); + + return this._loading; + } + + resolveDependency(fromModule, toModuleName) { + if (fromModule._ref) { + fromModule = fromModule._ref; + } + + const resHash = resolutionHash(fromModule.path, toModuleName); + + if (this._immediateResolutionCache[resHash]) { + return Promise.resolve(this._immediateResolutionCache[resHash]); + } + + const asset_DEPRECATED = this._resolveAsset_DEPRECATED( + fromModule, + toModuleName + ); + if (asset_DEPRECATED) { + return Promise.resolve(asset_DEPRECATED); + } + + const cacheResult = (result) => { + this._immediateResolutionCache[resHash] = result; + return result; + }; + + const forgive = () => { + console.warn( + 'Unable to resolve module %s from %s', + toModuleName, + fromModule.path + ); + return null; + }; + + if (!this._isNodeModulesDir(fromModule.path) + && toModuleName[0] !== '.' && + toModuleName[0] !== '/') { + return this._resolveHasteDependency(fromModule, toModuleName).catch( + () => this._resolveNodeDependency(fromModule, toModuleName) + ).then( + cacheResult, + forgive + ); + } + + return this._resolveNodeDependency(fromModule, toModuleName) + .then( + cacheResult, + forgive + ); + } + + getOrderedDependencies(entryPath) { + return this.load().then(() => { + const absolutePath = path.resolve(this._getAbsolutePath(entryPath)); + + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._opts.roots + ); + } + + const entry = this._moduleCache.getModule(absolutePath); + const deps = []; + const visited = Object.create(null); + visited[entry.hash()] = true; + + const collect = (mod) => { + deps.push(mod); + return mod.getDependencies().then( + depNames => Promise.all( + depNames.map(name => this.resolveDependency(mod, name)) + ).then((dependencies) => [depNames, dependencies]) + ).then(([depNames, dependencies]) => { + let p = Promise.resolve(); + dependencies.forEach((modDep, i) => { + if (modDep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`', + depNames[i], + mod.path + ); + return; + } + + p = p.then(() => { + if (!visited[modDep.hash()]) { + visited[modDep.hash()] = true; + return collect(modDep); + } + return null; + }); + }); + + return p; + }); + }; + + return collect(entry) + .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))); + }); + } + + _getAbsolutePath(filePath) { + if (isAbsolutePath(filePath)) { + return filePath; + } + + for (let i = 0; i < this._opts.roots.length; i++) { + const root = this._opts.roots[i]; + const absPath = path.join(root, filePath); + if (this._fastfs.fileExists(absPath)) { + return absPath; + } + } + + return null; + } + + _resolveHasteDependency(fromModule, toModuleName) { + toModuleName = normalizePath(toModuleName); + + let p = fromModule.getPackage(); + if (p) { + p = p.redirectRequire(toModuleName); + } else { + p = Promise.resolve(toModuleName); + } + + return p.then((realModuleName) => { + let dep = this._hasteMap[realModuleName]; + + if (dep && dep.type === 'Module') { + return dep; + } + + let packageName = realModuleName; + + while (packageName && packageName !== '.') { + dep = this._hasteMap[packageName]; + if (dep && dep.type === 'Package') { + break; + } + packageName = path.dirname(packageName); + } + + if (dep && dep.type === 'Package') { + const potentialModulePath = path.join( + dep.root, + path.relative(packageName, realModuleName) + ); + return this._loadAsFile(potentialModulePath) + .catch(() => this._loadAsDir(potentialModulePath)); + } + + throw new Error('Unable to resolve dependency'); + }); + } + + _redirectRequire(fromModule, modulePath) { + return Promise.resolve(fromModule.getPackage()).then(p => { + if (p) { + return p.redirectRequire(modulePath); + } + return modulePath; + }); + } + + _resolveNodeDependency(fromModule, toModuleName) { + if (toModuleName[0] === '.' || toModuleName[1] === '/') { + const potentialModulePath = isAbsolutePath(toModuleName) ? + toModuleName : + path.join(path.dirname(fromModule.path), toModuleName); + return this._redirectRequire(fromModule, potentialModulePath).then( + realModuleName => this._loadAsFile(realModuleName) + .catch(() => this._loadAsDir(realModuleName)) + ); + } else { + return this._redirectRequire(fromModule, toModuleName).then( + realModuleName => { + const searchQueue = []; + for (let currDir = path.dirname(fromModule.path); + currDir !== '/'; + currDir = path.dirname(currDir)) { + searchQueue.push( + path.join(currDir, 'node_modules', realModuleName) + ); + } + + let p = Promise.reject(new Error('Node module not found')); + searchQueue.forEach(potentialModulePath => { + p = p.catch( + () => this._loadAsFile(potentialModulePath) + ).catch( + () => this._loadAsDir(potentialModulePath) + ); + }); + + return p; + }); + } + } + + _resolveAsset_DEPRECATED(fromModule, toModuleName) { + if (this._assetMap_DEPRECATED != null) { + const assetMatch = toModuleName.match(/^image!(.+)/); + // Process DEPRECATED global asset requires. + if (assetMatch && assetMatch[1]) { + if (!this._assetMap_DEPRECATED[assetMatch[1]]) { + debug('WARINING: Cannot find asset:', assetMatch[1]); + return null; + } + return this._assetMap_DEPRECATED[assetMatch[1]]; + } + } + return null; + } + + _isAssetFile(file) { + return this._opts.assetExts.indexOf(extname(file)) !== -1; + } + + _loadAsFile(potentialModulePath) { + return Promise.resolve().then(() => { + if (this._isAssetFile(potentialModulePath)) { + const {name, type} = getAssetDataFromName(potentialModulePath); + const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type); + // We arbitrarly grab the first one, because scale selection + // will happen somewhere + const [assetFile] = this._fastfs.matches( + path.dirname(potentialModulePath), + pattern + ); + + if (assetFile) { + return this._moduleCache.getAssetModule(assetFile); + } + } + + let file; + if (this._fastfs.fileExists(potentialModulePath)) { + file = potentialModulePath; + } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { + file = potentialModulePath + '.js'; + } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { + file = potentialModulePath + '.json'; + } else { + throw new Error(`File ${potentialModulePath} doesnt exist`); + } + + return this._moduleCache.getModule(file); + }); + } + + _loadAsDir(potentialDirPath) { + return Promise.resolve().then(() => { + if (!this._fastfs.dirExists(potentialDirPath)) { + throw new Error(`Invalid directory ${potentialDirPath}`); + } + + const packageJsonPath = path.join(potentialDirPath, 'package.json'); + if (this._fastfs.fileExists(packageJsonPath)) { + return this._moduleCache.getPackage(packageJsonPath) + .getMain().then( + (main) => this._loadAsFile(main).catch( + () => this._loadAsDir(main) + ) + ); + } + + return this._loadAsFile(path.join(potentialDirPath, 'index')); + }); + } + + _buildHasteMap() { + let promises = this._fastfs.findFilesByExt('js', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHasteModule(file)); + + promises = promises.concat( + this._fastfs.findFilesByName('package.json', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHastePackage(file)) + ); + + return Promise.all(promises); + } + + _processHasteModule(file) { + const module = this._moduleCache.getModule(file); + return module.isHaste().then( + isHaste => isHaste && module.getName() + .then(name => this._updateHasteMap(name, module)) + ); + } + + _processHastePackage(file) { + file = path.resolve(file); + const p = this._moduleCache.getPackage(file, this._fastfs); + return p.isHaste() + .then(isHaste => isHaste && p.getName() + .then(name => this._updateHasteMap(name, p))) + .catch(e => { + if (e instanceof SyntaxError) { + // Malformed package.json. + return; + } + throw e; + }); + } + + _updateHasteMap(name, mod) { + if (this._hasteMap[name]) { + debug('WARNING: conflicting haste modules: ' + name); + if (mod.type === 'Package' && + this._hasteMap[name].type === 'Module') { + // Modules takes precendence over packages. + return; + } + } + this._hasteMap[name] = mod; + } + + _isNodeModulesDir(file) { + const inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + const dirs = this._opts.providesModuleNodeModules; + + for (let i = 0; i < dirs.length; i++) { + const index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; + } + + _processAsset_DEPRECATED(file) { + let ext = extname(file); + if (this._opts.assetExts.indexOf(ext) !== -1) { + let name = assetName(file, ext); + if (this._assetMap_DEPRECATED[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap_DEPRECATED[name] = new AssetModule_DEPRECATED(file); + } + } + + _buildAssetMap_DEPRECATED() { + if (this._opts.assetRoots_DEPRECATED == null || + this._opts.assetRoots_DEPRECATED.length === 0) { + return Promise.resolve(); + } + + this._assetMap_DEPRECATED = Object.create(null); + + const pattern = new RegExp( + '\.(' + this._opts.assetExts.join('|') + ')$' + ); + + const fastfs = new Fastfs( + this._opts.assetRoots_DEPRECATED, + this._opts.fileWatcher, + { pattern, ignore: this._opts.ignoreFilePath } + ); + + fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); + + return fastfs.build().then( + () => fastfs.findFilesByExts(this._opts.assetExts).map( + file => this._processAsset_DEPRECATED(file) + ) + ); + } + + _processAssetChange_DEPRECATED(type, filePath, root, fstat) { + const name = assetName(filePath); + if (type === 'change' || type === 'delete') { + delete this._assetMap_DEPRECATED[name]; + } + + if (type === 'change' || type === 'add') { + this._loading = this._loading.then( + () => this._processAsset_DEPRECATED(path.join(root, filePath)) + ); + } + } + + _processFileChange(type, filePath, root, fstat) { + // It's really hard to invalidate the right module resolution cache + // so we just blow it up with every file change. + this._immediateResolutionCache = Object.create(null); + + const absPath = path.join(root, filePath); + if ((fstat && fstat.isDirectory()) || + this._opts.ignoreFilePath(absPath) || + this._isNodeModulesDir(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + _.each(this._hasteMap, (mod, name) => { + if (mod.path === absPath) { + delete this._hasteMap[name]; + } + }); + + if (type === 'delete') { + return; + } + } + + if (extname(absPath) === 'js' || extname(absPath) === 'json') { + this._loading = this._loading.then(() => { + if (path.basename(filePath) === 'package.json') { + return this._processHastePackage(absPath); + } else { + return this._processHasteModule(absPath); + } + }); + } + } +} + +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + +function resolutionHash(modulePath, depName) { + return `${path.resolve(modulePath)}:${depName}`; +} + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +function normalizePath(modulePath) { + if (path.sep === '/') { + modulePath = path.normalize(modulePath); + } else if (path.posix) { + modulePath = path.posix.normalize(modulePath); + } + + return modulePath.replace(/\/$/, ''); +} + +util.inherits(NotFoundError, Error); + +module.exports = DependencyGraph; diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js new file mode 100644 index 00000000..3ae9354d --- /dev/null +++ b/react-packager/src/DependencyResolver/Module.js @@ -0,0 +1,133 @@ +'use strict'; + +const Promise = require('promise'); +const docblock = require('./DependencyGraph/docblock'); +const isAbsolutePath = require('absolute-path'); +const path = require('path'); +const replacePatterns = require('./replacePatterns'); + +class Module { + + constructor(file, fastfs, moduleCache) { + if (!isAbsolutePath(file)) { + throw new Error('Expected file to be absolute path but got ' + file); + } + + this.path = path.resolve(file); + this.type = 'Module'; + + this._fastfs = fastfs; + this._moduleCache = moduleCache; + } + + isHaste() { + return this._read().then(data => !!data.id); + } + + getName() { + return this._read().then(data => { + if (data.id) { + return data.id; + } + + const p = this.getPackage(); + + if (!p) { + // Name is full path + return this.path; + } + + return p.getName() + .then(name => { + if (!name) { + return this.path; + } + + return path.join(name, path.relative(p.root, this.path)); + }); + }); + } + + getPackage() { + return this._moduleCache.getPackageForModule(this); + } + + getDependencies() { + return this._read().then(data => data.dependencies); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path).then(content => { + const data = {}; + const moduleDocBlock = docblock.parseAsObject(content); + if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + data.id = /^(\S*)/.exec( + moduleDocBlock.providesModule || moduleDocBlock.provides + )[1]; + } + + // Ignore requires in generated code. An example of this is prebuilt + // files like the SourceMap library. + if ('extern' in moduleDocBlock) { + data.dependencies = []; + } else { + data.dependencies = extractRequires(content); + } + + return data; + }); + } + + return this._reading; + } + + getPlainObject() { + return Promise.all([ + this.getName(), + this.getDependencies(), + ]).then(([name, dependencies]) => this.addReference({ + path: this.path, + isJSON: path.extname(this.path) === '.json', + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + id: name, + dependencies + })); + } + + hash() { + return `Module : ${this.path}`; + } + + addReference(obj) { + Object.defineProperty(obj, '_ref', { value: this }); + return obj; + } +} + +/** + * Extract all required modules from a `code` string. + */ +var blockCommentRe = /\/\*(.|\n)*?\*\//g; +var lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code /*: string*/) /*: Array*/ { + var deps = []; + + code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { + deps.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { + deps.push(dep); + }); + + return deps; +} + +module.exports = Module; diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js new file mode 100644 index 00000000..3fa02cc1 --- /dev/null +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -0,0 +1,72 @@ +'use strict'; + +const AssetModule = require('./AssetModule'); +const Package = require('./Package'); +const Module = require('./Module'); +const path = require('path'); + +class ModuleCache { + + constructor(fastfs) { + this._moduleCache = Object.create(null); + this._packageCache = Object.create(null); + this._fastfs = fastfs; + 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); + } + return this._moduleCache[filePath]; + } + + getAssetModule(filePath) { + filePath = path.resolve(filePath); + if (!this._moduleCache[filePath]) { + this._moduleCache[filePath] = new AssetModule( + filePath, + this._fastfs, + this + ); + } + return this._moduleCache[filePath]; + } + + getPackage(filePath) { + filePath = path.resolve(filePath); + if (!this._packageCache[filePath]){ + this._packageCache[filePath] = new Package(filePath, this._fastfs); + } + return this._packageCache[filePath]; + } + + getPackageForModule(module) { + // TODO(amasad): use ES6 Map. + if (module.__package) { + if (this._packageCache[module.__package]) { + return this._packageCache[module.__package]; + } else { + delete module.__package; + } + } + + const packagePath = this._fastfs.closest(module.path, 'package.json'); + + if (!packagePath) { + return null; + } + + module.__package = packagePath; + return this.getPackage(packagePath); + } + + _processFileChange(type, filePath, root) { + const absPath = path.join(root, filePath); + delete this._moduleCache[absPath]; + delete this._packageCache[absPath]; + } +} + +module.exports = ModuleCache; diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js deleted file mode 100644 index 90db1c4a..00000000 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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'; - -function ModuleDescriptor(fields) { - if (!fields.id) { - throw new Error('Missing required fields id'); - } - this.id = fields.id; - - if (!fields.path) { - throw new Error('Missing required 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; - this.isAsset = fields.isAsset || false; - - if (this.isAsset_DEPRECATED && this.isAsset) { - throw new Error('Cannot be an asset and a deprecated asset'); - } - - this.resolution = fields.resolution; - - if (this.isAsset && isNaN(this.resolution)) { - throw new Error('Expected resolution to be a number for asset modules'); - } - - this.altId = fields.altId; - - this.isJSON = fields.isJSON; - - this._fields = fields; -} - -ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; -}; - -module.exports = ModuleDescriptor; diff --git a/react-packager/src/DependencyResolver/Package.js b/react-packager/src/DependencyResolver/Package.js new file mode 100644 index 00000000..f52ff42b --- /dev/null +++ b/react-packager/src/DependencyResolver/Package.js @@ -0,0 +1,84 @@ +'use strict'; + +const isAbsolutePath = require('absolute-path'); +const path = require('path'); + +class Package { + + constructor(file, fastfs) { + this.path = path.resolve(file); + this.root = path.dirname(this.path); + this._fastfs = fastfs; + this.type = 'Package'; + } + + getMain() { + return this._read().then(json => { + if (typeof json.browser === 'string') { + return path.join(this.root, json.browser); + } + + let main = json.main || 'index'; + + if (json.browser && typeof json.browser === 'object') { + main = json.browser[main] || + json.browser[main + '.js'] || + json.browser[main + '.json'] || + json.browser[main.replace(/(\.js|\.json)$/, '')] || + main; + } + + return path.join(this.root, main); + }); + } + + isHaste() { + return this._read().then(json => !!json.name); + } + + getName() { + return this._read().then(json => json.name); + } + + redirectRequire(name) { + return this._read().then(json => { + const {browser} = json; + + if (!browser || typeof browser !== 'object') { + return name; + } + + if (name[0] !== '/') { + return browser[name] || name; + } + + if (!isAbsolutePath(name)) { + throw new Error(`Expected ${name} to be absolute path`); + } + + const relPath = './' + path.relative(this.root, name); + const redirect = browser[relPath] || + browser[relPath + '.js'] || + browser[relPath + '.json']; + if (redirect) { + return path.join( + this.root, + redirect + ); + } + + return name; + }); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path) + .then(jsonStr => JSON.parse(jsonStr)); + } + + return this._reading; + } +} + +module.exports = Package; diff --git a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js similarity index 65% rename from react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js rename to react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 9bc8b8b9..da159b5e 100644 --- a/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -9,17 +9,21 @@ 'use strict'; jest.dontMock('../') - .dontMock('q') - .dontMock('../replacePatterns') - .setMock('../../ModuleDescriptor', function(data) {return data;}); + .dontMock('q') + .dontMock('../replacePatterns'); jest.mock('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; + function createModule(o) { + o.getPlainObject = () => Promise.resolve(o); + return o; + } + beforeEach(function() { // For the polyfillDeps require('path').join.mockImpl(function(a, b) { @@ -30,7 +34,10 @@ describe('HasteDependencyResolver', function() { describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -40,7 +47,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(); @@ -113,7 +120,12 @@ describe('HasteDependencyResolver', function() { }); pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + }); + var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -123,7 +135,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(); @@ -196,7 +208,11 @@ describe('HasteDependencyResolver', function() { }); pit('should pass in more polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -207,7 +223,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(); @@ -294,7 +310,7 @@ describe('HasteDependencyResolver', function() { }); describe('wrapModule', function() { - it('should resolve modules', function() { + pit('should resolve modules', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', }); @@ -446,162 +462,165 @@ describe('HasteDependencyResolver', function() { depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { - return { + return Promise.resolve(createModule({ id: 'changed' - }; + })); } else if (toModuleName === 'y') { - return { id: 'Y' }; + return Promise.resolve(createModule({ id: 'Y' })); } - return null; + + return Promise.resolve(null); }); - var processedCode = depResolver.wrapModule({ + return depResolver.wrapModule({ id: 'test module', path: '/root/test.js', dependencies: dependencies - }, code); + }, code).then(processedCode => { - expect(processedCode).toEqual([ - '__d(\'test module\',["changed","Y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) { ' + - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - 'require("changed")', - 'require("Y")', - 'require( \'z\' )', - 'require( "a")', - 'require("b" )});', - ].join('\n')); + expect(processedCode).toEqual([ + '__d(\'test module\',["changed","Y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) { ' + + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )', + '});', + ].join('\n')); + }); }); }); }); diff --git a/react-packager/src/DependencyResolver/fastfs.js b/react-packager/src/DependencyResolver/fastfs.js new file mode 100644 index 00000000..0053b14e --- /dev/null +++ b/react-packager/src/DependencyResolver/fastfs.js @@ -0,0 +1,310 @@ +'use strict'; + +const Promise = require('promise'); +const {EventEmitter} = require('events'); + +const _ = require('underscore'); +const debug = require('debug')('DependencyGraph'); +const fs = require('fs'); +const path = require('path'); + +const readDir = Promise.denodeify(fs.readdir); +const readFile = Promise.denodeify(fs.readFile); +const stat = Promise.denodeify(fs.stat); +const hasOwn = Object.prototype.hasOwnProperty; + +class Fastfs extends EventEmitter { + constructor(roots, fileWatcher, {ignore, pattern}) { + super(); + this._fileWatcher = fileWatcher; + this._ignore = ignore; + this._pattern = pattern; + this._roots = roots.map(root => new File(root, { isDir: true })); + this._fastPaths = Object.create(null); + } + + build() { + const queue = this._roots.slice(); + return this._search(queue).then(() => { + this._fileWatcher.on('all', this._processFileChange.bind(this)); + }); + } + + stat(filePath) { + return Promise.resolve().then(() => { + const file = this._getFile(filePath); + return file.stat(); + }); + } + + getAllFiles() { + return _.chain(this._roots) + .map(root => root.getFiles()) + .flatten() + .value(); + } + + findFilesByExt(ext, { ignore }) { + return this.getAllFiles() + .filter( + file => file.ext() === ext && (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + findFilesByExts(exts) { + return this.getAllFiles() + .filter(file => exts.indexOf(file.ext()) !== -1) + .map(file => file.path); + } + + findFilesByName(name, { ignore }) { + return this.getAllFiles() + .filter( + file => path.basename(file.path) === name && + (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + readFile(filePath) { + return this._getFile(filePath).read(); + } + + closest(filePath, name) { + for (let file = this._getFile(filePath).parent; + file; + file = file.parent) { + if (file.children[name]) { + return file.children[name].path; + } + } + return null; + } + + fileExists(filePath) { + const file = this._getFile(filePath); + return file && !file.isDir; + } + + dirExists(filePath) { + const file = this._getFile(filePath); + return file && file.isDir; + } + + matches(dir, pattern) { + let dirFile = this._getFile(dir); + if (!dirFile.isDir) { + throw new Error(`Expected file ${dirFile.path} to be a directory`); + } + + return Object.keys(dirFile.children) + .filter(name => name.match(pattern)) + .map(name => path.join(dirFile.path, name)); + } + + _getRoot(filePath) { + for (let i = 0; i < this._roots.length; i++) { + let possibleRoot = this._roots[i]; + if (isDescendant(possibleRoot.path, filePath)) { + return possibleRoot; + } + } + return null; + } + + _getAndAssertRoot(filePath) { + const root = this._getRoot(filePath); + if (!root) { + throw new Error(`File ${filePath} not found in any of the roots`); + } + return root; + } + + _getFile(filePath) { + filePath = path.normalize(filePath); + if (!hasOwn.call(this._fastPaths, filePath)) { + this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath); + } + + return this._fastPaths[filePath]; + } + + _add(file) { + this._getAndAssertRoot(file.path).addChild(file); + } + + _search(queue) { + const dir = queue.shift(); + if (!dir) { + return Promise.resolve(); + } + + return readAndStatDir(dir.path).then(([filePaths, stats]) => { + filePaths.forEach((filePath, i) => { + if (this._ignore(filePath)) { + return; + } + + if (stats[i].isDirectory()) { + queue.push( + new File(filePath, { isDir: true, fstat: stats[i] }) + ); + return; + } + + if (filePath.match(this._pattern)) { + this._add(new File(filePath, { fstat: stats[i] })); + } + }); + return this._search(queue); + }); + } + + _processFileChange(type, filePath, root, fstat) { + const absPath = path.join(root, filePath); + if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { + return; + } + + // Make sure this event belongs to one of our roots. + if (!this._getRoot(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + const file = this._getFile(absPath); + if (file) { + file.remove(); + } + } + + delete this._fastPaths[path.normalize(absPath)]; + + if (type !== 'delete') { + this._add(new File(absPath, { + isDir: false, + fstat + })); + } + + this.emit('change', type, filePath, root, fstat); + } +} + +class File { + constructor(filePath, {isDir, fstat}) { + this.path = filePath; + this.isDir = Boolean(isDir); + if (this.isDir) { + this.children = Object.create(null); + } + + if (fstat) { + this._stat = Promise.resolve(fstat); + } + } + + read() { + if (!this._read) { + this._read = readFile(this.path, 'utf8'); + } + return this._read; + } + + stat() { + if (!this._stat) { + this._stat = stat(this.path); + } + + return this._stat; + } + + addChild(file) { + const parts = path.relative(this.path, file.path).split(path.sep); + + if (parts.length === 0) { + return; + } + + if (parts.length === 1) { + this.children[parts[0]] = file; + file.parent = this; + } else if (this.children[parts[0]]) { + this.children[parts[0]].addChild(file); + } else { + const dir = new File(path.join(this.path, parts[0]), { isDir: true }); + dir.parent = this; + this.children[parts[0]] = dir; + dir.addChild(file); + } + } + + getFileFromPath(filePath) { + const parts = path.relative(this.path, filePath) + .split(path.sep); + + /*eslint consistent-this:0*/ + let file = this; + for (let i = 0; i < parts.length; i++) { + let fileName = parts[i]; + if (!fileName) { + continue; + } + + if (!file || !file.isDir) { + // File not found. + return null; + } + + file = file.children[fileName]; + } + + return file; + } + + getFiles() { + return _.flatten(_.values(this.children).map(file => { + if (file.isDir) { + return file.getFiles(); + } else { + return file; + } + })); + } + + ext() { + return path.extname(this.path).replace(/^\./, ''); + } + + remove() { + if (!this.parent) { + throw new Error(`No parent to delete ${this.path} from`); + } + + delete this.parent.children[path.basename(this.path)]; + } +} + +function isDescendant(root, child) { + return path.relative(root, child).indexOf('..') !== 0; +} + +function readAndStatDir(dir) { + return readDir(dir) + .then(files => Promise.all(files.map(f => path.join(dir, f)))) + .then(files => Promise.all( + files.map(f => stat(f).catch(handleBrokenLink)) + ).then(stats => [ + // Remove broken links. + files.filter((file, i ) => !!stats[i]), + stats.filter(Boolean), + ])); +} + +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return Promise.resolve(); +} + +module.exports = Fastfs; diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js deleted file mode 100644 index c247e59d..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ /dev/null @@ -1,1612 +0,0 @@ -/** - * 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('../index') - .dontMock('absolute-path') - .dontMock('../docblock') - .dontMock('../../replacePatterns') - .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); - -jest.mock('fs'); - -describe('DependencyGraph', function() { - var DependencyGraph; - var fileWatcher; - var fs; - - beforeEach(function() { - fs = require('fs'); - DependencyGraph = require('../index'); - - fileWatcher = { - on: function() { - return this; - } - }; - }); - - describe('getOrderedDependencies', function() { - pit('should get dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get dependencies with the correct extensions', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - 'a.js.orig': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get json dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'package' - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./a.json")' - ].join('\n'), - 'a.json': JSON.stringify({}), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'package/index', - path: '/root/index.js', - dependencies: ['./a.json'] - }, - { - id: 'package/a.json', - isJSON: true, - path: '/root/a.json', - dependencies: [] - }, - ]); - }); - }); - - pit('should get dependencies with deprecated assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!a")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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, - }, - ]); - }); - }); - - pit('should get dependencies with relative assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: ['./imgs/a.png'] - }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get dependencies with assets and resolution', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - 'imgs': { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: [ - './imgs/a.png', - './imgs/b.png', - './imgs/c.png', - ] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.png', - resolution: 1, - dependencies: [], - isAsset: true - }, - ]); - }); - }); - - pit('Deprecated and relative assets can live together', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - 'require("image!a")', - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'rootPackage/index', - path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get recursive dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("index")', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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']}, - ]); - }); - }); - - pit('should work with packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default main package to index.js', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': 'lol', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should have altId for a package with providesModule', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': [ - '/**', - ' * @providesModule EpicModule', - ' */', - ].join('\n'), - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', - altId: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default use index.js if main is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'lib', - }), - lib: { - 'index.js': 'lol', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', - path: '/root/aPackage/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should resolve require to index if it is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'index.js': 'lol', - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', - path: '/root/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should ignore malformed packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': 'lol', - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - ]); - }); - }); - - pit('can have multiple modules with the same name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("b")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - 'c.js': [ - '/**', - ' * @providesModule c', - ' */', - ].join('\n'), - 'somedir': { - 'somefile.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("c")', - ].join('\n') - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) - .toEqual([ - { id: 'index', - altId: '/root/somedir/somefile.js', - path: '/root/somedir/somefile.js', - dependencies: ['c'] - }, - { id: 'c', - altId: '/root/c.js', - path: '/root/c.js', - dependencies: [] - }, - ]); - }); - }); - - pit('providesModule wins when conflict with package', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule aPackage', - ' */', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage', - altId: '/root/b.js', - path: '/root/b.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should be forgiving with missing requires', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("lolomg")', - ].join('\n') - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['lolomg'] - } - ]); - }); - }); - - pit('should work with packages with subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with packages with symlinked subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'symlinkedPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - }, - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { SYMLINK: '/symlinkedPackage' }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with relative modules in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'require("./subdir/lolynot")', - 'subdir': { - 'lolynot.js': 'require("../other")' - }, - 'other.js': 'some code' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] - }, - { id: 'aPackage/other', - path: '/root/aPackage/other.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support simple browser field in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should supportbrowser field in packages w/o .js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support mapping main in browser field json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work do correct browser mapping w/o js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping of files', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - }, - }), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'some browser code', - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] - }, - { id: 'aPackage/not-node', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] - }, - { id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [] - }, - { id: 'aPackage/dir/client', - path: '/root/aPackage/dir/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping for packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - } - }), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: ['node-package'] - }, - { id: 'browser-package/index', - path: '/root/aPackage/browser-package/index.js', - dependencies: [] - }, - ]); - }); - }); - }); - - describe('file watch updating', function() { - var triggerFileChange; - - beforeEach(function() { - fileWatcher = { - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - } - }; - }); - - pit('updates module dependencies', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo': [ - '/**', - ' * @providesModule foo', - ' */', - '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.load().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')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file change', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - '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.load().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')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file delete', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - '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.load().then(function() { - delete filesystem.root.foo; - triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - '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.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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'] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on deprecated asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!foo")' - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetRoots_DEPRECATED: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on relative asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./foo.png")' - ].join('\n'), - 'package.json': JSON.stringify({ - name: 'aPackage' - }), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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, - }, - ]); - }); - }); - }); - - pit('runs changes through ignore filter', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - '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'], - ignoreFilePath: function(filePath) { - if (filePath === '/root/bar.js') { - return true; - } - return false; - } - }); - return dgraph.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - - pit('should ignore directory updates', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - '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.load().then(function() { - triggerFileChange('change', 'aPackage', '/root', { - isDirectory: function(){ return true; } - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - }); -}); diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js deleted file mode 100644 index 0881e5dc..00000000 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ /dev/null @@ -1,798 +0,0 @@ -/** - * 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'; - -var ModuleDescriptor = require('../../ModuleDescriptor'); -var Promise = require('bluebird'); -var fs = require('fs'); -var docblock = require('./docblock'); -var replacePatterns = require('../replacePatterns'); -var path = require('path'); -var isAbsolutePath = require('absolute-path'); -var debug = require('debug')('DependecyGraph'); -var util = require('util'); -var declareOpts = require('../../../lib/declareOpts'); -var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); - -var readFile = Promise.promisify(fs.readFile); -var readDir = Promise.promisify(fs.readdir); -var lstat = Promise.promisify(fs.lstat); -var realpath = Promise.promisify(fs.realpath); - -var validateOpts = declareOpts({ - roots: { - type: 'array', - required: true, - }, - ignoreFilePath: { - type: 'function', - default: function(){} - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetRoots_DEPRECATED: { - type: 'array', - default: [], - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function DependecyGraph(options) { - var opts = validateOpts(options); - - this._roots = opts.roots; - this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED; - this._assetExts = opts.assetExts; - this._ignoreFilePath = opts.ignoreFilePath; - this._fileWatcher = options.fileWatcher; - - this._loaded = false; - this._queue = this._roots.slice(); - this._graph = Object.create(null); - this._packageByRoot = Object.create(null); - this._packagesById = Object.create(null); - this._moduleById = Object.create(null); - this._debugUpdateEvents = []; - - this._moduleExtPattern = new RegExp( - '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' - ); - - // Kick off the search process to precompute the dependency graph. - this._init(); -} - -DependecyGraph.prototype.load = function() { - if (this._loading != null) { - return this._loading; - } - - this._loading = Promise.all([ - this._search(), - this._buildAssetMap_DEPRECATED(), - ]); - - return this._loading; -}; - -/** - * 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 - ); - } - - 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); - - // 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); - - 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 (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } - }); - } - - visited[module.id] = true; - collect(module); - - return deps; -}; - -/** - * Given a module descriptor `fromModule` return the module descriptor for - * the required module `depModuleId`. It could be top-level or relative, - * or both. - */ -DependecyGraph.prototype.resolveDependency = function( - fromModule, - depModuleId -) { - if (this._assetMap_DEPRECATED != null) { - var assetMatch = depModuleId.match(/^image!(.+)/); - // Process DEPRECATED global asset requires. - if (assetMatch && assetMatch[1]) { - if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); - return null; - } - return this._assetMap_DEPRECATED[assetMatch[1]]; - } - } - - var packageJson, modulePath, dep; - - // Package relative modules starts with '.' or '..'. - if (depModuleId[0] !== '.') { - - // Check if we need to map the dependency to something else via the - // `browser` field in package.json - var fromPackageJson = this._lookupPackage(fromModule.path); - if (fromPackageJson && fromPackageJson.browser && - fromPackageJson.browser[depModuleId]) { - depModuleId = fromPackageJson.browser[depModuleId]; - } - - // `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)]) { - 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) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - depModuleId, - fromModule.id - ); - 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')]; - } - - if (dep == null) { - throw new Error( - 'Cannot find package main file for package: ' + packageJson._root - ); - } - return dep; - } else { - - // `depModuleId` is a module defined in a package relative to `fromModule`. - packageJson = this._lookupPackage(fromModule.path); - - if (packageJson == null) { - throw new Error( - 'Expected relative module lookup from ' + fromModule.id + ' to ' + - depModuleId + ' to be within a package but no package.json found.' - ); - } - - // Example: depModuleId: ../a/b - // fromModule.path: /x/y/z - // 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); - } - } - - // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { - modulePath = withExtJs(modulePath); - } - - dep = this._graph[modulePath]; - - // Maybe the dependency is a directory and there is an index.js inside it. - if (dep == null) { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; - } - - // Maybe it's an asset with @n.nx resolution and the path doesn't map - // to the id - if (dep == null && this._isFileAsset(modulePath)) { - dep = this._moduleById[this._lookupName(modulePath)]; - } - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.' + - ' Inferred required module path is %s', - depModuleId, - fromModule.id, - modulePath - ); - return null; - } - - return dep; - } -}; - -/** - * Intiates the filewatcher and kicks off the search process. - */ -DependecyGraph.prototype._init = function() { - var processChange = this._processFileChange.bind(this); - var watcher = this._fileWatcher; - - this._loading = this.load().then(function() { - watcher.on('all', processChange); - }); -}; - -/** - * Implements a DFS over the file system looking for modules and packages. - */ -DependecyGraph.prototype._search = function() { - var self = this; - var dir = this._queue.shift(); - - if (dir == null) { - return Promise.resolve(this._graph); - } - - // Steps: - // 1. Read a dir and stat all the entries. - // 2. Filter the files and queue up the directories. - // 3. Process any package.json in the files - // 4. recur. - return readAndStatDir(dir) - .spread(function(files, stats) { - var modulePaths = files.filter(function(filePath, i) { - if (self._ignoreFilePath(filePath)) { - return false; - } - - if (stats[i].isDirectory()) { - self._queue.push(filePath); - return false; - } - - if (stats[i].isSymbolicLink()) { - return false; - } - - return filePath.match(self._moduleExtPattern); - }); - - var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); - - return Promise.all([ - processing, - self._search() - ]); - }) - .then(function() { - return self; - }); -}; - -/** - * Given a list of files find a `package.json` file, and if found parse it - * and update indices. - */ -DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - - var packagePath; - for (var i = 0; i < files.length ; i++) { - var file = files[i]; - if (path.basename(file) === 'package.json') { - packagePath = file; - break; - } - } - - if (packagePath != null) { - return this._processPackage(packagePath); - } else { - return Promise.resolve(); - } -}; - -DependecyGraph.prototype._processPackage = function(packagePath) { - var packageRoot = path.dirname(packagePath); - var self = this; - return readFile(packagePath, 'utf8') - .then(function(content) { - var packageJson; - try { - packageJson = JSON.parse(content); - } catch (e) { - debug('WARNING: malformed package.json: ', packagePath); - return Promise.resolve(); - } - - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return Promise.resolve(); - } - - packageJson._root = packageRoot; - self._addPackageToIndices(packageJson); - - return packageJson; - }); -}; - -DependecyGraph.prototype._addPackageToIndices = function(packageJson) { - this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; -}; - -DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { - delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; -}; - -/** - * Parse a module and update indices. - */ -DependecyGraph.prototype._processModule = function(modulePath) { - var moduleData = { path: path.resolve(modulePath) }; - var module; - - if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = getAssetDataFromName(this._lookupName(modulePath)); - moduleData.id = assetData.assetName; - moduleData.resolution = assetData.resolution; - moduleData.isAsset = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - if (extname(modulePath) === 'json') { - moduleData.id = this._lookupName(modulePath); - moduleData.isJSON = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - var self = this; - return readFile(modulePath, 'utf8') - .then(function(content) { - var moduleDocBlock = docblock.parseAsObject(content); - if (moduleDocBlock.providesModule || moduleDocBlock.provides) { - moduleData.id = /^(\S*)/.exec( - moduleDocBlock.providesModule || moduleDocBlock.provides - )[1]; - - // Incase someone wants to require this module via - // packageName/path/to/module - moduleData.altId = self._lookupName(modulePath); - } else { - moduleData.id = self._lookupName(modulePath); - } - moduleData.dependencies = extractRequires(content); - - module = new ModuleDescriptor(moduleData); - self._updateGraphWithModule(module); - return module; - }); -}; - -/** - * Compute the name of module relative to a package it may belong to. - */ -DependecyGraph.prototype._lookupName = function(modulePath) { - var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { - return path.resolve(modulePath); - } else { - var relativePath = - sansExtJs(path.relative(packageJson._root, modulePath)); - return path.join(packageJson.name, relativePath); - } -}; - -DependecyGraph.prototype._deleteModule = function(module) { - delete this._graph[module.path]; - - // Others may keep a reference so we mark it as deleted. - module.deleted = true; - - // Haste allows different module to have the same id. - if (this._moduleById[module.id] === module) { - delete this._moduleById[module.id]; - } - - if (module.altId && this._moduleById[module.altId] === module) { - delete this._moduleById[module.altId]; - } -}; - -/** - * Update the graph and indices with the module. - */ -DependecyGraph.prototype._updateGraphWithModule = function(module) { - if (this._graph[module.path]) { - this._deleteModule(this._graph[module.path]); - } - - this._graph[module.path] = module; - - if (this._moduleById[module.id]) { - debug( - 'WARNING: Top-level module name conflict `%s`.\n' + - 'module with path `%s` will replace `%s`', - module.id, - module.path, - this._moduleById[module.id].path - ); - } - - this._moduleById[module.id] = module; - - // Some module maybe refrenced by both @providesModule and - // require(package/moduleName). - if (module.altId != null && this._moduleById[module.altId] == null) { - this._moduleById[module.altId] = module; - } -}; - -/** - * Find the nearest package to a module. - */ -DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = 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)); -}; - -/** - * Process a filewatcher change event. - */ -DependecyGraph.prototype._processFileChange = function( - eventType, - filePath, - root, - stat -) { - var absPath = path.join(root, filePath); - if (this._ignoreFilePath(absPath)) { - return; - } - - this._debugUpdateEvents.push({event: eventType, path: filePath}); - - if (this._assetExts.indexOf(extname(filePath)) > -1) { - this._processAssetChange_DEPRECATED(eventType, absPath); - // Fall through because new-style assets are actually modules. - } - - var isPackage = path.basename(filePath) === 'package.json'; - if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } - } else { - var module = this._graph[absPath]; - if (module == null) { - return; - } - - this._deleteModule(module); - } - } else if (!(stat && stat.isDirectory())) { - var self = this; - this._loading = this._loading.then(function() { - if (isPackage) { - return self._processPackage(absPath); - } - return self._processModule(absPath); - }); - } -}; - -DependecyGraph.prototype.getDebugInfo = function() { - return '

FileWatcher Update Events

' + - '
' + util.inspect(this._debugUpdateEvents) + '
' + - '

Graph dump

' + - '
' + util.inspect(this._graph) + '
'; -}; - -/** - * Searches all roots for the file and returns the first one that has file of - * the same path. - */ -DependecyGraph.prototype._getAbsolutePath = function(filePath) { - if (isAbsolutePath(filePath)) { - return filePath; - } - - for (var i = 0; i < this._roots.length; i++) { - var root = this._roots[i]; - var absPath = path.join(root, filePath); - if (this._graph[absPath]) { - return absPath; - } - } - - return null; -}; - -DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() { - if (this._assetRoots_DEPRECATED == null || - this._assetRoots_DEPRECATED.length === 0) { - return Promise.resolve(); - } - - this._assetMap_DEPRECATED = Object.create(null); - return buildAssetMap_DEPRECATED( - this._assetRoots_DEPRECATED, - this._processAsset_DEPRECATED.bind(this) - ); -}; - -DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { - var ext = extname(file); - if (this._assetExts.indexOf(ext) !== -1) { - var name = assetName(file, ext); - if (this._assetMap_DEPRECATED[name] != null) { - debug('Conflcting assets', name); - } - - this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ - id: 'image!' + name, - path: path.resolve(file), - isAsset_DEPRECATED: true, - dependencies: [], - resolution: getAssetDataFromName(file).resolution, - }); - } -}; - -DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) { - if (this._assetMap_DEPRECATED == null) { - return; - } - - var name = assetName(file, extname(file)); - if (eventType === 'change' || eventType === 'delete') { - delete this._assetMap_DEPRECATED[name]; - } - - if (eventType === 'change' || eventType === 'add') { - this._processAsset_DEPRECATED(file); - } -}; - -DependecyGraph.prototype._isFileAsset = function(file) { - return this._assetExts.indexOf(extname(file)) !== -1; -}; - -/** - * Extract all required modules from a `code` string. - */ -var blockCommentRe = /\/\*(.|\n)*?\*\//g; -var lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code) { - var deps = []; - - code - .replace(blockCommentRe, '') - .replace(lineCommentRe, '') - .replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - return match; - }) - .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - }); - - return deps; -} - -/** - * `file` without the .js extension. - */ -function sansExtJs(file) { - if (file.match(/\.js$/)) { - return file.slice(0, -3); - } else { - return file; - } -} - -/** - * `file` with the .js extension. - */ -function withExtJs(file) { - if (file.match(/\.js$/)) { - return file; - } else { - return file + '.js'; - } -} - -function handleBrokenLink(e) { - debug('WARNING: error stating, possibly broken symlink', e.message); - return Promise.resolve(); -} - -function readAndStatDir(dir) { - return readDir(dir) - .then(function(files){ - return Promise.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)).catch(handleBrokenLink); - })); - }).then(function(files) { - files = files.filter(function(f) { - return !!f; - }); - - var stats = files.map(function(filePath) { - return lstat(filePath).catch(handleBrokenLink); - }); - - return [ - files, - Promise.all(stats), - ]; - }); -} - -/** - * Given a list of roots and list of extensions find all the files in - * the directory with that extension and build a map of those assets. - */ -function buildAssetMap_DEPRECATED(roots, processAsset) { - var queue = roots.slice(0); - - function search() { - var root = queue.shift(); - - if (root == null) { - return Promise.resolve(); - } - - return readAndStatDir(root).spread(function(files, stats) { - files.forEach(function(file, i) { - if (stats[i].isDirectory()) { - queue.push(file); - } else { - processAsset(file); - } - }); - - return search(); - }); - } - - return search(); -} - -function assetName(file, ext) { - return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); -} - -function extname(name) { - return path.extname(name).replace(/^\./, ''); -} - -function NotFoundError() { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - var msg = util.format.apply(util, arguments); - this.message = msg; - this.type = this.name = 'NotFoundError'; - this.status = 404; -} - -util.inherits(NotFoundError, Error); - -module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js deleted file mode 100644 index da68785e..00000000 --- a/react-packager/src/DependencyResolver/haste/index.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * 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'; - -var path = require('path'); -var DependencyGraph = require('./DependencyGraph'); -var replacePatterns = require('./replacePatterns'); -var ModuleDescriptor = require('../ModuleDescriptor'); -var declareOpts = require('../../lib/declareOpts'); - -var DEFINE_MODULE_CODE = [ - '__d(', - '\'_moduleName_\',', - '_deps_,', - 'function(global, require, requireDynamic, requireLazy, module, exports) {', - ' _code_', - '}', - ');', -].join(''); - -var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; - -var validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, - nonPersistent: { - type: 'boolean', - default: false, - }, - moduleFormat: { - type: 'string', - default: 'haste', - }, - assetRoots: { - type: 'array', - default: [], - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function HasteDependencyResolver(options) { - var opts = validateOpts(options); - - this._depGraph = new DependencyGraph({ - roots: opts.projectRoots, - assetRoots_DEPRECATED: opts.assetRoots, - assetExts: opts.assetExts, - ignoreFilePath: function(filepath) { - return filepath.indexOf('__tests__') !== -1 || - (opts.blacklistRE && opts.blacklistRE.test(filepath)); - }, - fileWatcher: opts.fileWatcher, - }); - - - this._polyfillModuleNames = opts.polyfillModuleNames || []; -} - -var getDependenciesValidateOpts = declareOpts({ - dev: { - type: 'boolean', - default: true, - }, -}); - -HasteDependencyResolver.prototype.getDependencies = function(main, options) { - var opts = getDependenciesValidateOpts(options); - - var depGraph = this._depGraph; - var self = this; - - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); - var mainModuleId = dependencies[0].id; - - self._prependPolyfillDependencies(dependencies, opts.dev); - - return { - mainModuleId: mainModuleId, - dependencies: dependencies - }; - }); -}; - -HasteDependencyResolver.prototype._prependPolyfillDependencies = function( - dependencies, - isDev -) { - var polyfillModuleNames = [ - isDev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - path.join(__dirname, 'polyfills/String.prototype.es6.js'), - path.join(__dirname, 'polyfills/Array.prototype.es6.js'), - ].concat(this._polyfillModuleNames); - - var polyfillModules = polyfillModuleNames.map( - function(polyfillModuleName, idx) { - return new ModuleDescriptor({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true - }); - } - ); - dependencies.unshift.apply(dependencies, polyfillModules); -}; - -HasteDependencyResolver.prototype.wrapModule = function(module, code) { - if (module.isPolyfill) { - return code; - } - - var resolvedDeps = Object.create(null); - var resolvedDepsArr = []; - - for (var i = 0; i < module.dependencies.length; i++) { - var depName = module.dependencies[i]; - var dep = this._depGraph.resolveDependency(module, depName); - if (dep) { - resolvedDeps[depName] = dep.id; - resolvedDepsArr.push(dep.id); - } - } - - var relativizeCode = function(codeMatch, pre, quot, depName, post) { - var depId = resolvedDeps[depName]; - if (depId) { - return pre + quot + depId + post; - } else { - return codeMatch; - } - }; - - return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { - return { - '_moduleName_': module.id, - '_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - '_deps_': JSON.stringify(resolvedDepsArr), - }[key]; - }); -}; - -HasteDependencyResolver.prototype.getDebugInfo = function() { - return this._depGraph.getDebugInfo(); -}; - -module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/index.js b/react-packager/src/DependencyResolver/index.js index ca80ab0b..0ddf5c3c 100644 --- a/react-packager/src/DependencyResolver/index.js +++ b/react-packager/src/DependencyResolver/index.js @@ -8,15 +8,174 @@ */ 'use strict'; -var HasteDependencyResolver = require('./haste'); -var NodeDependencyResolver = require('./node'); +var path = require('path'); +var DependencyGraph = require('./DependencyGraph'); +var replacePatterns = require('./replacePatterns'); +var declareOpts = require('../lib/declareOpts'); +var Promise = require('promise'); -module.exports = function createDependencyResolver(options) { - if (options.moduleFormat === 'haste') { - return new HasteDependencyResolver(options); - } else if (options.moduleFormat === 'node') { - return new NodeDependencyResolver(options); - } else { - throw new Error('unsupported'); +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, + assetRoots: { + type: 'array', + default: [], + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetExts: { + type: 'array', + required: true, } +}); + +function HasteDependencyResolver(options) { + var opts = validateOpts(options); + + this._depGraph = new DependencyGraph({ + roots: opts.projectRoots, + assetRoots_DEPRECATED: opts.assetRoots, + assetExts: opts.assetExts, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (opts.blacklistRE && opts.blacklistRE.test(filepath)); + }, + fileWatcher: opts.fileWatcher, + }); + + + this._polyfillModuleNames = opts.polyfillModuleNames || []; +} + +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, +}); + +HasteDependencyResolver.prototype.getDependencies = function(main, options) { + var opts = getDependenciesValidateOpts(options); + + var depGraph = this._depGraph; + var self = this; + return depGraph.load().then( + () => depGraph.getOrderedDependencies(main).then( + dependencies => { + const mainModuleId = dependencies[0].id; + self._prependPolyfillDependencies( + dependencies, + opts.dev + ); + + return { + mainModuleId: mainModuleId, + dependencies: dependencies + }; + } + ) + ); }; + +HasteDependencyResolver.prototype._prependPolyfillDependencies = function( + dependencies, + isDev +) { + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/String.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.prototype.es6.js'), + ].concat(this._polyfillModuleNames); + + var polyfillModules = polyfillModuleNames.map( + (polyfillModuleName, idx) => ({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true, + }) + ); + + dependencies.unshift.apply(dependencies, polyfillModules); +}; + +HasteDependencyResolver.prototype.wrapModule = function(module, code) { + if (module.isPolyfill) { + return Promise.resolve(code); + } + + const resolvedDeps = Object.create(null); + const resolvedDepsArr = []; + + return Promise.all( + module.dependencies.map(depName => { + return this._depGraph.resolveDependency(module, depName) + .then((dep) => dep && dep.getPlainObject().then(mod => { + if (mod) { + resolvedDeps[depName] = mod.id; + resolvedDepsArr.push(mod.id); + } + })); + }) + ).then(() => { + const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; + + return defineModuleCode({ + code: code + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), + deps: JSON.stringify(resolvedDepsArr), + moduleName: module.id, + }); + }); +}; + +HasteDependencyResolver.prototype.getDebugInfo = function() { + return this._depGraph.getDebugInfo(); +}; + +function defineModuleCode({moduleName, code, deps}) { + return [ + `__d(`, + `'${moduleName}',`, + `${deps},`, + 'function(global, require, ', + 'requireDynamic, requireLazy, module, exports) {', + ` ${code}`, + '\n});', + ].join(''); +} + +module.exports = HasteDependencyResolver; diff --git a/react-packager/src/DependencyResolver/node/index.js b/react-packager/src/DependencyResolver/node/index.js deleted file mode 100644 index 57388564..00000000 --- a/react-packager/src/DependencyResolver/node/index.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 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'; - -var Promise = require('bluebird'); -var ModuleDescriptor = require('../ModuleDescriptor'); - -var mdeps = require('module-deps'); -var path = require('path'); - -exports.getRuntimeCode = function() {}; - -exports.wrapModule = function(id, source) { - return Promise.resolve( - 'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n' - + source + '\n});' - ); -}; - -exports.getDependencies = function(root, fileEntryPath) { - return new Promise(function(resolve) { - fileEntryPath = path.join(process.cwd(), root, fileEntryPath); - - var md = mdeps(); - - md.end({file: fileEntryPath}); - - var deps = []; - - md.on('data', function(data) { - deps.push( - new ModuleDescriptor({ - id: data.id, - deps: data.deps, - path: data.file, - entry: data.entry - }) - ); - }); - - md.on('end', function() { - resolve(deps); - }); - }); -}; diff --git a/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js rename to react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js b/react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js rename to react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/console.js b/react-packager/src/DependencyResolver/polyfills/console.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/console.js rename to react-packager/src/DependencyResolver/polyfills/console.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/react-packager/src/DependencyResolver/polyfills/error-guard.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/error-guard.js rename to react-packager/src/DependencyResolver/polyfills/error-guard.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/react-packager/src/DependencyResolver/polyfills/polyfills.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/polyfills.js rename to react-packager/src/DependencyResolver/polyfills/polyfills.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/react-packager/src/DependencyResolver/polyfills/prelude.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/prelude.js rename to react-packager/src/DependencyResolver/polyfills/prelude.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/react-packager/src/DependencyResolver/polyfills/prelude_dev.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js rename to react-packager/src/DependencyResolver/polyfills/prelude_dev.js diff --git a/react-packager/src/DependencyResolver/haste/polyfills/require.js b/react-packager/src/DependencyResolver/polyfills/require.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/polyfills/require.js rename to react-packager/src/DependencyResolver/polyfills/require.js diff --git a/react-packager/src/DependencyResolver/haste/replacePatterns.js b/react-packager/src/DependencyResolver/replacePatterns.js similarity index 100% rename from react-packager/src/DependencyResolver/haste/replacePatterns.js rename to react-packager/src/DependencyResolver/replacePatterns.js diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index cd1a28e5..aac211ad 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -10,7 +10,7 @@ var EventEmitter = require('events').EventEmitter; var sane = require('sane'); -var Promise = require('bluebird'); +var Promise = require('promise'); var util = require('util'); var exec = require('child_process').exec; @@ -57,7 +57,7 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - return Promise.promisify(watcher.close, watcher)(); + return Promise.denodeify(watcher.close).call(watcher); }); }); }; diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js index 584077b6..aee8d4f2 100644 --- a/react-packager/src/JSTransformer/Cache.js +++ b/react-packager/src/JSTransformer/Cache.js @@ -14,7 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var isAbsolutePath = require('absolute-path'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var tmpdir = require('os').tmpDir(); var version = require('../../../../package.json').version; @@ -74,11 +74,13 @@ Cache.prototype.get = function(filepath, loaderCb) { Cache.prototype._set = function(filepath, loaderPromise) { this._data[filepath] = loaderPromise.then(function(data) { - return [ + return Promise.all([ data, - Promise.promisify(fs.stat)(filepath) - ]; - }).spread(function(data, stat) { + Promise.denodeify(fs.stat)(filepath) + ]); + }).then(function(ref) { + var data = ref[0]; + var stat = ref[1]; this._persistEventually(); return { data: data, @@ -113,7 +115,7 @@ Cache.prototype._persistCache = function() { Object.keys(data).forEach(function(key, i) { json[key] = values[i]; }); - return Promise.promisify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) .then(function() { this._persisting = null; diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js index f91490ba..3877b3dd 100644 --- a/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -17,7 +17,7 @@ jest .mock('os') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('JSTransformer Cache', function() { var Cache; diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 513d4394..7c20e10e 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -9,14 +9,14 @@ 'use strict'; var fs = require('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); var ModuleTransport = require('../lib/ModuleTransport'); -var readFile = Promise.promisify(fs.readFile); +var readFile = Promise.denodeify(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; @@ -69,7 +69,7 @@ function Transformer(options) { options.transformModulePath ); - this._transform = Promise.promisify(this._workers); + this._transform = Promise.denodeify(this._workers); } } diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index 901c467b..216e9009 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -17,12 +17,15 @@ jest jest.mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('Packager', function() { var getDependencies; var wrapModule; var Packager; + var packager; + var assetServer; + var modules; beforeEach(function() { getDependencies = jest.genMockFn(); @@ -35,30 +38,27 @@ describe('Packager', function() { }); Packager = require('../'); - }); - pit('create a package', function() { require('fs').statSync.mockImpl(function() { return { - isDirectory: function() {return true;} + isDirectory: () => true }; }); - require('fs').readFile.mockImpl(function(file, callback) { callback(null, '{"json":true}'); }); - var assetServer = { + assetServer = { getAssetData: jest.genMockFn(), }; - var packager = new Packager({ + packager = new Packager({ projectRoots: ['/root'], assetServer: assetServer, }); - var modules = [ + modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, { @@ -101,7 +101,7 @@ describe('Packager', function() { }); wrapModule.mockImpl(function(module, code) { - return 'lol ' + code + ' lol'; + return Promise.resolve('lol ' + code + ' lol'); }); require('image-size').mockImpl(function(path, cb) { @@ -116,7 +116,9 @@ describe('Packager', function() { type: 'png', }; }); + }); + pit('create a package', function() { return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ @@ -200,4 +202,42 @@ describe('Packager', function() { ]); }); }); + + pit('gets the list of dependencies', function() { + return packager.getDependencies('/root/foo.js', true) + .then(({dependencies}) => { + expect(dependencies).toEqual([ + { + dependencies: [], + id: 'foo', + path: '/root/foo.js', + }, + { + dependencies: [], + id: 'bar', + path: '/root/bar.js', + }, + { + dependencies: [], + id: 'image!img', + isAsset_DEPRECATED: true, + path: '/root/img/img.png', + resolution: 2, + }, + { + dependencies: [], + id: 'new_image.png', + isAsset: true, + path: '/root/img/new_image.png', + resolution: 2, + }, + { + dependencies: [], + id: 'package/file.json', + isJSON: true, + path: '/root/file.json', + }, + ]); + }); + }); }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index a85281d2..3c6d1a2f 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -11,7 +11,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var Package = require('./Package'); @@ -20,8 +20,8 @@ var ModuleTransport = require('../lib/ModuleTransport'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); -var sizeOf = Promise.promisify(imageSize); -var readFile = Promise.promisify(fs.readFile); +var sizeOf = Promise.denodeify(imageSize); +var readFile = Promise.denodeify(fs.readFile); var validateOpts = declareOpts({ projectRoots: { @@ -159,16 +159,17 @@ Packager.prototype._transformModule = function(ppackage, module) { } var resolver = this._resolver; - return transform.then(function(transformed) { - var code = resolver.wrapModule(module, transformed.code); - return new ModuleTransport({ - code: code, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - }); + return transform.then( + transformed => resolver.wrapModule(module, transformed.code).then( + code => new ModuleTransport({ + code: code, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }) + ) + ); }; Packager.prototype.getGraphDebugInfo = function() { @@ -206,7 +207,9 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { return Promise.all([ sizeOf(module.path), this._assetServer.getAssetData(relPath), - ]).spread(function(dimensions, assetData) { + ]).then(function(res) { + var dimensions = res[0]; + var assetData = res[1]; var img = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index e4e7b508..32c9060a 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -20,7 +20,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) .setMock('uglify-js') .dontMock('../'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('processRequest', function() { var server; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 0ce5c584..1d2140ef 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -15,7 +15,7 @@ var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); var AssetServer = require('../AssetServer'); -var Promise = require('bluebird'); +var Promise = require('promise'); var _ = require('underscore'); var exec = require('child_process').exec; var fs = require('fs'); @@ -131,13 +131,14 @@ Server.prototype._onFileChange = function(type, filepath, root) { Server.prototype._rebuildPackages = function() { var buildPackage = this.buildPackage.bind(this); var packages = this._packages; - Object.keys(packages).forEach(function(key) { - var options = getOptionsFromUrl(key); + + Object.keys(packages).forEach(function(optionsJson) { + var options = JSON.parse(optionsJson); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || Promise.resolve()).finally(function() { + packages[optionsJson] = (packages[optionsJson] || Promise.resolve()).finally(function() { // With finally promise callback we can't change the state of the promise // so we need to reassign the promise. - packages[key] = buildPackage(options).then(function(p) { + packages[optionsJson] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.inlineSourceMap, @@ -146,7 +147,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); - return packages[key]; + return packages[optionsJson]; }); }; @@ -228,15 +229,15 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { res.end(ret); } else if (parts[1] === 'packages') { ret += '

Cached Packages

'; - Promise.all(Object.keys(this._packages).map(function(url) { - return this._packages[url].then(function(p) { - ret += '

' + url + '

'; + Promise.all(Object.keys(this._packages).map(function(optionsJson) { + return this._packages[optionsJson].then(function(p) { + ret += '

' + optionsJson + '

'; ret += p.getDebugInfo(); }); }, this)).then( function() { res.end(ret); }, function(e) { - res.wrteHead(500); + res.writeHead(500); res.end('Internal Error'); console.log(e.stack); } @@ -350,10 +351,11 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromUrl(req.url); - var building = this._packages[req.url] || this.buildPackage(options); + var optionsJson = JSON.stringify(options); + var building = this._packages[optionsJson] || this.buildPackage(options); - this._packages[req.url] = building; - building.then( + this._packages[optionsJson] = building; + building.then( function(p) { if (requestType === 'bundle') { res.end(p.getSource({ diff --git a/react-packager/src/__mocks__/fs.js b/react-packager/src/__mocks__/fs.js index d0e08a2f..b5251a44 100644 --- a/react-packager/src/__mocks__/fs.js +++ b/react-packager/src/__mocks__/fs.js @@ -51,7 +51,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { var node = getToNode(filepath); // dir check if (node && typeof node === 'object' && node.SYMLINK == null) { - callback(new Error('Trying to read a dir, ESIDR, or whatever')); + callback(new Error('Error readFile a dir: ' + filepath)); } return callback(null, node); } catch (e) { @@ -59,12 +59,13 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { } }); -fs.lstat.mockImpl(function(filepath, callback) { +fs.stat.mockImpl(function(filepath, callback) { var node; try { node = getToNode(filepath); } catch (e) { - return callback(e); + callback(e); + return; } var mtime = { @@ -73,7 +74,12 @@ fs.lstat.mockImpl(function(filepath, callback) { } }; - if (node && typeof node === 'object' && node.SYMLINK == null) { + if (node.SYMLINK) { + fs.stat(node.SYMLINK, callback); + return; + } + + if (node && typeof node === 'object') { callback(null, { isDirectory: function() { return true; @@ -89,9 +95,6 @@ fs.lstat.mockImpl(function(filepath, callback) { return false; }, isSymbolicLink: function() { - if (typeof node === 'object' && node.SYMLINK) { - return true; - } return false; }, mtime: mtime, @@ -113,6 +116,9 @@ function getToNode(filepath) { } var node = filesystem; parts.slice(1).forEach(function(part) { + if (node && node.SYMLINK) { + node = getToNode(node.SYMLINK); + } node = node[part]; });