Consume react, fbjs from npm

Summary:
We don't (yet) treat these the same as any other modules because we still have special resolution rules for them in the packager allowing the use of `providesModule`, but I believe this allows people to use npm react in their RN projects and not have duplicate copies of React. Fixes facebook/react-native#2985.

This relies on fbjs 0.6, which includes `.flow` files alongside the `.js` files to allow them to be typechecked without additional configuration. This also uses react 0.14.5, which shims a couple of files (as `.native.js`) to avoid DOM-specific bits. Once we fix these in React, we will use the same code on web and native. Hopefully we can also remove the packager support I'm adding here for `.native.js`.

This diff is not the desired end state for us – ideally the packager would know nothing of react or fbjs, and we'll get there eventually by not relying on `providesModule` in order to load react and fbjs modules. (fbjs change posted here but not merged yet: https://github.com/facebook/fbjs/pull/84.)

This should also allow relay to work seamlessly with RN, but I haven't verified this.

public

Reviewed By: sebmarkbage

Differential Revision: D2786197

fb-gh-sync-id: ff50f28445e949edc9501f4b599df7970813870d
This commit is contained in:
Ben Alpert 2015-12-30 11:38:44 -08:00 committed by facebook-github-bot-9
parent eed6dde1d4
commit e41008f208
5 changed files with 75 additions and 50 deletions

View File

@ -13,36 +13,41 @@ var path = require('path');
// Don't forget to everything listed here to `testConfig.json` // Don't forget to everything listed here to `testConfig.json`
// modulePathIgnorePatterns. // modulePathIgnorePatterns.
var sharedBlacklist = [ var sharedBlacklist = [
'node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js', /node_modules[/\\]react[/\\]dist[/\\].*/,
'node_modules/react-haste/React.js', 'node_modules/react/lib/React.js',
'node_modules/react-haste/renderers/dom/ReactDOM.js', 'node_modules/react/lib/ReactDOM.js',
// For each of these fbjs files (especially the non-forks/stubs), we should // For each of these fbjs files (especially the non-forks/stubs), we should
// consider deleting the conflicting copy and just using the fbjs version. // consider deleting the conflicting copy and just using the fbjs version.
'node_modules/fbjs-haste/__forks__/Map.js', //
'node_modules/fbjs-haste/__forks__/Promise.js', // fbjs forks:
'node_modules/fbjs-haste/__forks__/fetch.js', 'node_modules/fbjs/lib/Map.js',
'node_modules/fbjs-haste/core/Deferred.js', 'node_modules/fbjs/lib/Promise.js',
'node_modules/fbjs-haste/core/PromiseMap.js', 'node_modules/fbjs/lib/fetch.js',
'node_modules/fbjs-haste/core/areEqual.js', // fbjs stubs:
'node_modules/fbjs-haste/core/flattenArray.js', 'node_modules/fbjs/lib/ErrorUtils.js',
'node_modules/fbjs-haste/core/isEmpty.js', 'node_modules/fbjs/lib/URI.js',
'node_modules/fbjs-haste/core/removeFromArray.js', // fbjs modules:
'node_modules/fbjs-haste/core/resolveImmediate.js', 'node_modules/fbjs/lib/Deferred.js',
'node_modules/fbjs-haste/core/sprintf.js', 'node_modules/fbjs/lib/PromiseMap.js',
'node_modules/fbjs-haste/crypto/crc32.js', 'node_modules/fbjs/lib/UserAgent.js',
'node_modules/fbjs-haste/fetch/fetchWithRetries.js', 'node_modules/fbjs/lib/areEqual.js',
'node_modules/fbjs-haste/functional/everyObject.js', 'node_modules/fbjs/lib/base62.js',
'node_modules/fbjs-haste/functional/filterObject.js', 'node_modules/fbjs/lib/crc32.js',
'node_modules/fbjs-haste/functional/forEachObject.js', 'node_modules/fbjs/lib/everyObject.js',
'node_modules/fbjs-haste/functional/someObject.js', 'node_modules/fbjs/lib/fetchWithRetries.js',
'node_modules/fbjs-haste/request/xhrSimpleDataSerializer.js', 'node_modules/fbjs/lib/filterObject.js',
'node_modules/fbjs-haste/stubs/ErrorUtils.js', 'node_modules/fbjs/lib/flattenArray.js',
'node_modules/fbjs-haste/stubs/URI.js', 'node_modules/fbjs/lib/forEachObject.js',
'node_modules/fbjs-haste/useragent/UserAgent.js', 'node_modules/fbjs/lib/isEmpty.js',
'node_modules/fbjs-haste/utils/nullthrows.js', 'node_modules/fbjs/lib/nullthrows.js',
'node_modules/fbjs/lib/removeFromArray.js',
'node_modules/fbjs/lib/resolveImmediate.js',
'node_modules/fbjs/lib/someObject.js',
'node_modules/fbjs/lib/sprintf.js',
'node_modules/fbjs/lib/xhrSimpleDataSerializer.js',
// Those conflicts with the ones in fbjs-haste/. We need to blacklist the // Those conflicts with the ones in fbjs/. We need to blacklist the
// internal version otherwise they won't work in open source. // internal version otherwise they won't work in open source.
'downstream/core/CSSCore.js', 'downstream/core/CSSCore.js',
'downstream/core/TouchEventUtils.js', 'downstream/core/TouchEventUtils.js',
@ -63,11 +68,8 @@ var sharedBlacklist = [
'downstream/core/invariant.js', 'downstream/core/invariant.js',
'downstream/core/nativeRequestAnimationFrame.js', 'downstream/core/nativeRequestAnimationFrame.js',
'downstream/core/toArray.js', 'downstream/core/toArray.js',
];
// Raw unescaped patterns in case you need to use wildcards /website\/node_modules\/.*/,
var sharedBlacklistWildcards = [
'website\/node_modules\/.*',
]; ];
var platformBlacklists = { var platformBlacklists = {
@ -85,10 +87,16 @@ var platformBlacklists = {
], ],
}; };
function escapeRegExp(str) { function escapeRegExp(pattern) {
var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); if (Object.prototype.toString.call(pattern) === '[object RegExp]') {
// convert the '/' into an escaped local file separator return pattern.source;
return escaped.replace(/\//g,'\\' + path.sep); } else if (typeof pattern === 'string') {
var escaped = pattern.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
// convert the '/' into an escaped local file separator
return escaped.replace(/\//g,'\\' + path.sep);
} else {
throw new Error('Unexpected packager blacklist pattern: ' + pattern);
}
} }
function blacklist(platform, additionalBlacklist) { function blacklist(platform, additionalBlacklist) {
@ -96,7 +104,6 @@ function blacklist(platform, additionalBlacklist) {
(additionalBlacklist || []).concat(sharedBlacklist) (additionalBlacklist || []).concat(sharedBlacklist)
.concat(platformBlacklists[platform] || []) .concat(platformBlacklists[platform] || [])
.map(escapeRegExp) .map(escapeRegExp)
.concat(sharedBlacklistWildcards)
.join('|') + .join('|') +
')$' ')$'
); );

View File

@ -12,12 +12,20 @@ const getPlatformExtension = require('../lib/getPlatformExtension');
const Promise = require('promise'); const Promise = require('promise');
const GENERIC_PLATFORM = 'generic'; const GENERIC_PLATFORM = 'generic';
const NATIVE_PLATFORM = 'native';
class HasteMap { class HasteMap {
constructor({ extensions, fastfs, moduleCache, helpers }) { constructor({
extensions,
fastfs,
moduleCache,
preferNativePlatform,
helpers,
}) {
this._extensions = extensions; this._extensions = extensions;
this._fastfs = fastfs; this._fastfs = fastfs;
this._moduleCache = moduleCache; this._moduleCache = moduleCache;
this._preferNativePlatform = preferNativePlatform;
this._helpers = helpers; this._helpers = helpers;
} }
@ -73,18 +81,19 @@ class HasteMap {
return null; return null;
} }
// If no platform is given we choose the generic platform module list. // If platform is 'ios', we prefer .ios.js to .native.js which we prefer to
// If a platform is given and no modules exist we fallback // a plain .js file.
// to the generic platform module list. let module = undefined;
if (platform == null) { if (module == null && platform != null) {
return modulesMap[GENERIC_PLATFORM]; module = modulesMap[platform];
} else {
let module = modulesMap[platform];
if (module == null) {
module = modulesMap[GENERIC_PLATFORM];
}
return module;
} }
if (module == null && this._preferNativePlatform) {
module = modulesMap[NATIVE_PLATFORM];
}
if (module == null) {
module = modulesMap[GENERIC_PLATFORM];
}
return module;
} }
_processHasteModule(file) { _processHasteModule(file) {

View File

@ -18,6 +18,7 @@ const Promise = require('promise');
class ResolutionRequest { class ResolutionRequest {
constructor({ constructor({
platform, platform,
preferNativePlatform,
entryPath, entryPath,
hasteMap, hasteMap,
deprecatedAssetMap, deprecatedAssetMap,
@ -26,6 +27,7 @@ class ResolutionRequest {
fastfs, fastfs,
}) { }) {
this._platform = platform; this._platform = platform;
this._preferNativePlatform = preferNativePlatform;
this._entryPath = entryPath; this._entryPath = entryPath;
this._hasteMap = hasteMap; this._hasteMap = hasteMap;
this._deprecatedAssetMap = deprecatedAssetMap; this._deprecatedAssetMap = deprecatedAssetMap;
@ -329,6 +331,9 @@ class ResolutionRequest {
} else if (this._platform != null && } else if (this._platform != null &&
this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) { this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) {
file = potentialModulePath + '.' + this._platform + '.js'; file = potentialModulePath + '.' + this._platform + '.js';
} else if (this._preferNativePlatform &&
this._fastfs.fileExists(potentialModulePath + '.native.js')) {
file = potentialModulePath + '.native.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) { } else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
file = potentialModulePath + '.js'; file = potentialModulePath + '.js';
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) { } else if (this._fastfs.fileExists(potentialModulePath + '.json')) {

View File

@ -37,6 +37,7 @@ class DependencyGraph {
assetExts, assetExts,
providesModuleNodeModules, providesModuleNodeModules,
platforms, platforms,
preferNativePlatform,
cache, cache,
extensions, extensions,
mocksPattern, mocksPattern,
@ -51,6 +52,7 @@ class DependencyGraph {
assetExts: assetExts || [], assetExts: assetExts || [],
providesModuleNodeModules, providesModuleNodeModules,
platforms: platforms || [], platforms: platforms || [],
preferNativePlatform: preferNativePlatform || false,
cache, cache,
extensions: extensions || ['js', 'json'], extensions: extensions || ['js', 'json'],
mocksPattern, mocksPattern,
@ -105,7 +107,7 @@ class DependencyGraph {
fastfs: this._fastfs, fastfs: this._fastfs,
extensions: this._opts.extensions, extensions: this._opts.extensions,
moduleCache: this._moduleCache, moduleCache: this._moduleCache,
assetExts: this._opts.exts, preferNativePlatform: this._opts.preferNativePlatform,
helpers: this._helpers, helpers: this._helpers,
}); });
@ -147,6 +149,7 @@ class DependencyGraph {
const absPath = this._getAbsolutePath(entryPath); const absPath = this._getAbsolutePath(entryPath);
const req = new ResolutionRequest({ const req = new ResolutionRequest({
platform, platform,
preferNativePlatform: this._opts.preferNativePlatform,
entryPath: absPath, entryPath: absPath,
deprecatedAssetMap: this._deprecatedAssetMap, deprecatedAssetMap: this._deprecatedAssetMap,
hasteMap: this._hasteMap, hasteMap: this._hasteMap,

View File

@ -81,8 +81,8 @@ class Resolver {
(opts.blacklistRE && opts.blacklistRE.test(filepath)); (opts.blacklistRE && opts.blacklistRE.test(filepath));
}, },
providesModuleNodeModules: [ providesModuleNodeModules: [
'fbjs-haste', 'fbjs',
'react-haste', 'react',
'react-native', 'react-native',
// Parse requires AsyncStorage. They will // Parse requires AsyncStorage. They will
// change that to require('react-native') which // change that to require('react-native') which
@ -92,6 +92,7 @@ class Resolver {
'react-transform-hmr', 'react-transform-hmr',
], ],
platforms: ['ios', 'android'], platforms: ['ios', 'android'],
preferNativePlatform: true,
fileWatcher: opts.fileWatcher, fileWatcher: opts.fileWatcher,
cache: opts.cache, cache: opts.cache,
}); });