[react-packager] Add support for platform in the resolver

Summary:
Teach the resolver about platform-based resolution. The platform extension is inferred from the entry point.
It works for haste modules, as well as node-based resolution.
This commit is contained in:
Amjad Masad 2015-08-03 18:16:18 -07:00
parent ab23c251c3
commit 63a96af6c6
2 changed files with 293 additions and 16 deletions

View File

@ -1809,6 +1809,77 @@ describe('DependencyGraph', function() {
});
});
pit('platform should work with node_modules', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': [
'/**',
' * @providesModule index',
' */',
'require("foo");',
'require("bar");',
].join('\n'),
'node_modules': {
'foo': {
'package.json': JSON.stringify({
name: 'foo',
}),
'index.ios.js': '',
},
'bar': {
'package.json': JSON.stringify({
name: 'bar',
main: 'main'
}),
'main.ios.js': '',
},
},
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['foo', 'bar'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'foo/index.ios.js',
path: '/root/node_modules/foo/index.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'bar/main.ios.js',
path: '/root/node_modules/bar/main.ios.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({
@ -2333,6 +2404,169 @@ describe('DependencyGraph', function() {
]);
});
});
pit('should work with multiple platforms (haste)', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('a');
`,
'a.ios.js': `
/**
* @providesModule a
*/
`,
'a.android.js': `
/**
* @providesModule a
*/
`,
'a.js': `
/**
* @providesModule a
*/
`,
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['a'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'a',
path: '/root/a.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
pit('should pick the generic file', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('a');
`,
'a.android.js': `
/**
* @providesModule a
*/
`,
'a.js': `
/**
* @providesModule a
*/
`,
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.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 work with multiple platforms (node)', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.ios.js': `
/**
* @providesModule index
*/
require('./a');
`,
'a.ios.js': '',
'a.android.js': '',
'a.js': '',
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.ios.js',
dependencies: ['./a'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: '/root/a.ios.js',
path: '/root/a.ios.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
]);
});
});
});
describe('file watch updating', function() {

View File

@ -56,6 +56,10 @@ const validateOpts = declareOpts({
'parse',
],
},
platforms: {
type: 'array',
default: ['ios', 'android'],
}
});
class DependencyGraph {
@ -169,6 +173,13 @@ class DependencyGraph {
);
}
const platformExt = getPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt;
} else {
this._platformExt = null;
}
const entry = this._moduleCache.getModule(absolutePath);
const deps = [];
const visited = Object.create(null);
@ -237,16 +248,14 @@ class DependencyGraph {
}
return p.then((realModuleName) => {
let dep = this._hasteMap[realModuleName];
let dep = this._getHasteModule(realModuleName);
if (dep && dep.type === 'Module') {
return dep;
}
let packageName = realModuleName;
while (packageName && packageName !== '.') {
dep = this._hasteMap[packageName];
dep = this._getHasteModule(packageName);
if (dep && dep.type === 'Package') {
break;
}
@ -349,6 +358,9 @@ class DependencyGraph {
let file;
if (this._fastfs.fileExists(potentialModulePath)) {
file = potentialModulePath;
} else if (this._platformExt != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platformExt + '.js')) {
file = potentialModulePath + '.' + this._platformExt + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
@ -419,15 +431,32 @@ class DependencyGraph {
}
_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;
}
if (this._hasteMap[name] == null) {
this._hasteMap[name] = [];
}
this._hasteMap[name] = mod;
if (mod.type === 'Module') {
// Modules takes precendence over packages.
this._hasteMap[name].unshift(mod);
} else {
this._hasteMap[name].push(mod);
}
}
_getHasteModule(name) {
if (this._hasteMap[name]) {
const modules = this._hasteMap[name];
if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) {
if (getPlatformExt(modules[i].path) === this._platformExt) {
return modules[i];
}
}
}
return modules[0];
}
return null;
}
_isNodeModulesDir(file) {
@ -511,12 +540,17 @@ class DependencyGraph {
return;
}
/*eslint no-labels: 0 */
if (type === 'delete' || type === 'change') {
_.each(this._hasteMap, (mod, name) => {
if (mod.path === absPath) {
delete this._hasteMap[name];
loop: for (let name in this._hasteMap) {
let modules = this._hasteMap[name];
for (var i = 0; i < modules.length; i++) {
if (modules[i].path === absPath) {
modules.splice(i, 1);
break loop;
}
}
});
}
if (type === 'delete') {
return;
@ -566,6 +600,15 @@ function normalizePath(modulePath) {
return modulePath.replace(/\/$/, '');
}
// Extract platform extension: index.ios.js -> ios
function getPlatformExt(file) {
const parts = path.basename(file).split('.');
if (parts.length < 3) {
return null;
}
return parts[parts.length - 2];
}
util.inherits(NotFoundError, Error);
module.exports = DependencyGraph;