support react-native field with fallback to browser field

Summary: React Native is a lot more powerful an environment than the browser, so we need an alternate mapping, as specified [here](https://github.com/defunctzombie/node-browser-resolve#browser-field)

An example:
```js
{
  "browser": {
     "./lib/server": false
   },
   "react-native": {
     "dgram": "react-native-udp",
     "fs": "react-native-level-fs"
   },
   "chromeapp": {
     "dgram": "chrome-dgram",
     "fs": "level-filesystem"
   }
}
```

on the other hand, if "react-native" is not present in package.json, you should fall back to "browser"

other than the one (nesting) test added, the tests are unchanged, just done for both "react-native" and "browser"

(I've implemented [react-native-udp](https://npmjs.org/package/react-native-udp) and [react-native-level-fs](https://npmjs.org/package/react-native-level-fs), but they obviously don't belong in the traditional "browser" field as they won't run anywhere except in React Native.)
Closes https://github.com/facebook/react-native/pull/2208

Reviewed By: svcscm

Differential Revision: D2691236

Pulled By: vjeux

fb-gh-sync-id: 34041ed50bda4ec07f31d1dc50dcdfa428af2512
This commit is contained in:
Mark Vayngrib 2015-11-24 11:03:57 -08:00 committed by facebook-github-bot-2
parent 2b22d22a83
commit b86f14738e
2 changed files with 530 additions and 343 deletions

View File

@ -1362,170 +1362,491 @@ describe('DependencyGraph', function() {
}); });
}); });
pit('should support simple browser field in packages', function() { testBrowserField('browser')
var root = '/root'; testBrowserField('react-native')
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({ function replaceBrowserField (json, fieldName) {
...defaults, if (fieldName !== 'browser') {
roots: [root], json[fieldName] = json.browser
}); delete json.browser
return getOrderedDependenciesAsJSON(dgraph, '/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() { return json
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({ function testBrowserField (fieldName) {
...defaults, pit('should support simple browser field in packages ("' + fieldName + '")', function() {
roots: [root], var root = '/root';
}); fs.__setMockFilesystem({
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { 'root': {
expect(deps) 'index.js': [
.toEqual([ '/**',
{ ' * @providesModule index',
id: 'index', ' */',
path: '/root/index.js', 'require("aPackage")',
dependencies: ['aPackage'], ].join('\n'),
isAsset: false, 'aPackage': {
isAsset_DEPRECATED: false, 'package.json': JSON.stringify(replaceBrowserField({
isJSON: false, name: 'aPackage',
isPolyfill: false, main: 'main.js',
resolution: undefined, browser: 'client.js',
}, }, fieldName)),
{ 'main.js': 'some other code',
id: 'aPackage/client.js', 'client.js': 'some code',
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 dgraph = new DependencyGraph({
var root = '/root'; ...defaults,
fs.__setMockFilesystem({ roots: [root],
'root': { });
'index.js': [ return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
'/**', expect(deps)
' * @providesModule index', .toEqual([
' */', {
'require("aPackage")', id: 'index',
].join('\n'), path: '/root/index.js',
'aPackage': { dependencies: ['aPackage'],
'package.json': JSON.stringify({ isAsset: false,
name: 'aPackage', isAsset_DEPRECATED: false,
main: './main.js', isJSON: false,
browser: { isPolyfill: false,
'./main.js': './client.js', resolution: undefined,
resolveDependency: undefined,
}, },
}), {
'main.js': 'some other code', id: 'aPackage/client.js',
'client.js': 'some code', 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 ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
name: 'aPackage',
main: 'main.js',
browser: 'client',
}, fieldName)),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/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 ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
name: 'aPackage',
main: './main.js',
browser: {
'./main.js': './client.js',
},
}, fieldName)),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
assetExts: ['png', 'jpg'],
});
return getOrderedDependenciesAsJSON(dgraph, '/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 ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
name: 'aPackage',
main: './main.js',
browser: {
'./main': './client.js',
},
}, fieldName)),
'main.js': 'some other code',
'client.js': 'some code',
}
}
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
assetExts: ['png', 'jpg'],
});
return getOrderedDependenciesAsJSON(dgraph, '/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 ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
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',
},
}, fieldName)),
'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({ var dgraph = new DependencyGraph({
...defaults, ...defaults,
roots: [root], roots: [root],
}); });
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
.toEqual([ .toEqual([
{ { id: 'index',
id: 'index', path: '/root/index.js',
path: '/root/index.js', dependencies: ['aPackage'],
dependencies: ['aPackage'], isAsset: false,
isAsset: false, isAsset_DEPRECATED: false,
isAsset_DEPRECATED: false, isJSON: false,
isJSON: false, isPolyfill: false,
isPolyfill: false, resolution: undefined,
resolution: undefined, },
}, { id: 'aPackage/client.js',
{ id: 'aPackage/client.js', path: '/root/aPackage/client.js',
path: '/root/aPackage/client.js', dependencies: ['./node', './dir/server.js'],
dependencies: [], isAsset: false,
isAsset: false, isAsset_DEPRECATED: false,
isAsset_DEPRECATED: false, isJSON: false,
isJSON: false, isPolyfill: false,
isPolyfill: false, resolution: undefined,
resolution: undefined, },
resolveDependency: 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 work do correct browser mapping w/o js ext', function() { pit('should support browser mapping for packages ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
name: 'aPackage',
browser: {
'node-package': 'browser-package',
}
}, fieldName)),
'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({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/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,
},
]);
});
});
pit('should support browser mapping for packages ("' + fieldName + '")', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify(replaceBrowserField({
name: 'aPackage',
browser: {
'node-package': 'browser-package',
}
}, fieldName)),
'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({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/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,
},
]);
});
});
}
pit('should fall back to browser mapping from react-native mapping', function() {
var root = '/root'; var root = '/root';
fs.__setMockFilesystem({ fs.__setMockFilesystem({
'root': { 'root': {
@ -1538,186 +1859,36 @@ describe('DependencyGraph', function() {
'aPackage': { 'aPackage': {
'package.json': JSON.stringify({ 'package.json': JSON.stringify({
name: 'aPackage', name: 'aPackage',
main: './main.js', 'react-native': {
browser: { 'node-package': 'rn-package',
'./main': './client.js', }
},
}),
'main.js': 'some other code',
'client.js': 'some code',
},
},
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/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({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/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")', 'index.js': 'require("node-package")',
'node-package': { 'node_modules': {
'package.json': JSON.stringify({ 'node-package': {
'name': 'node-package', 'package.json': JSON.stringify({
}), 'name': 'node-package'
'index.js': 'some node code', }),
}, 'index.js': 'some node code',
'browser-package': { },
'package.json': JSON.stringify({ 'rn-package': {
'name': 'browser-package', 'package.json': JSON.stringify({
}), 'name': 'rn-package',
'index.js': 'some browser code', browser: {
}, 'nested-package': 'nested-browser-package'
}, }
}, }),
'index.js': 'require("nested-package")',
},
'nested-browser-package': {
'package.json': JSON.stringify({
'name': 'nested-browser-package',
}),
'index.js': 'some code'
}
}
}
}
}); });
var dgraph = new DependencyGraph({ var dgraph = new DependencyGraph({
@ -1745,8 +1916,17 @@ describe('DependencyGraph', function() {
isPolyfill: false, isPolyfill: false,
resolution: undefined, resolution: undefined,
}, },
{ id: 'browser-package/index.js', { id: 'rn-package/index.js',
path: '/root/aPackage/browser-package/index.js', path: '/root/aPackage/node_modules/rn-package/index.js',
dependencies: ['nested-package'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{ id: 'nested-browser-package/index.js',
path: '/root/aPackage/node_modules/nested-browser-package/index.js',
dependencies: [], dependencies: [],
isAsset: false, isAsset: false,
isAsset_DEPRECATED: false, isAsset_DEPRECATED: false,

View File

@ -15,17 +15,18 @@ class Package {
getMain() { getMain() {
return this._read().then(json => { return this._read().then(json => {
if (typeof json.browser === 'string') { var replacements = getReplacements(json)
return path.join(this.root, json.browser); if (typeof replacements === 'string') {
return path.join(this.root, replacements);
} }
let main = json.main || 'index'; let main = json.main || 'index';
if (json.browser && typeof json.browser === 'object') { if (replacements && typeof replacements === 'object') {
main = json.browser[main] || main = replacements[main] ||
json.browser[main + '.js'] || replacements[main + '.js'] ||
json.browser[main + '.json'] || replacements[main + '.json'] ||
json.browser[main.replace(/(\.js|\.json)$/, '')] || replacements[main.replace(/(\.js|\.json)$/, '')] ||
main; main;
} }
@ -51,14 +52,14 @@ class Package {
redirectRequire(name) { redirectRequire(name) {
return this._read().then(json => { return this._read().then(json => {
const {browser} = json; var replacements = getReplacements(json);
if (!browser || typeof browser !== 'object') { if (!replacements || typeof replacements !== 'object') {
return name; return name;
} }
if (name[0] !== '/') { if (name[0] !== '/') {
return browser[name] || name; return replacements[name] || name;
} }
if (!isAbsolutePath(name)) { if (!isAbsolutePath(name)) {
@ -66,9 +67,9 @@ class Package {
} }
const relPath = './' + path.relative(this.root, name); const relPath = './' + path.relative(this.root, name);
const redirect = browser[relPath] || const redirect = replacements[relPath] ||
browser[relPath + '.js'] || replacements[relPath + '.js'] ||
browser[relPath + '.json']; replacements[relPath + '.json'];
if (redirect) { if (redirect) {
return path.join( return path.join(
this.root, this.root,
@ -90,4 +91,10 @@ class Package {
} }
} }
function getReplacements(pkg) {
return pkg['react-native'] == null
? pkg.browser
: pkg['react-native'];
}
module.exports = Package; module.exports = Package;