238 lines
6.1 KiB
JavaScript
Raw Normal View History

/**
* 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 Activity = require('../Activity');
const DependencyGraph = require('../DependencyResolver/DependencyGraph');
const replacePatterns = require('../DependencyResolver/lib/replacePatterns');
const Polyfill = require('../DependencyResolver/Polyfill');
const declareOpts = require('../lib/declareOpts');
const Promise = require('promise');
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
polyfillModuleNames: {
type: 'array',
default: [],
},
moduleFormat: {
type: 'string',
default: 'haste',
},
assetRoots: {
type: 'array',
default: [],
},
fileWatcher: {
type: 'object',
required: true,
},
assetExts: {
type: 'array',
required: true,
},
cache: {
type: 'object',
required: true,
},
});
const getDependenciesValidateOpts = declareOpts({
dev: {
type: 'boolean',
default: true,
},
platform: {
type: 'string',
required: false,
},
isUnbundle: {
type: 'boolean',
default: false
},
});
class Resolver {
constructor(options) {
const opts = validateOpts(options);
this._depGraph = new DependencyGraph({
activity: Activity,
roots: opts.projectRoots,
assetRoots_DEPRECATED: opts.assetRoots,
assetExts: opts.assetExts,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
},
providesModuleNodeModules: [
'fbjs',
'react',
'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',
Hot Loading E2E basic flow Summary: public Implement all the necessary glue code for several diffs submitted before to get Hot Loading work end to end: - Simplify `HMRClient`: we don't need to make it stateful allowing to enable and disable it because both when we enable and disable the interface we need to reload the bundle. - On the native side we introduced a singleton to process the bundle URL. This new class might alter the url to include the `hot` attribute. I'm not 100% sure this is the best way to implement this but we cannot use `CTLSettings` for this as it's are not available on oss and I didn't want to contaminate `RCTBridge` with something specific to hot loading. Also, we could potentially use this processor for other things in the future. Please let me know if you don't like this approach or you have a better idea :). - Use this processor to alter the default bundle URL and request a `hot` bundle when hot loading is enabled. Also make sure to enable the HMR interface when the client activates it on the dev menu. - Add packager `hot` option. - Include gaeron's `react-transform` on Facebook's JS transformer. The current implementation couples a bit React Native to this feature because `react-transform-hmr` is required on `InitializeJavaScriptAppEngine`. Ideally, the packager should accept an additional list of requires and include them on the bundle among all their dependencies. Note this is not the same as the option `runBeforeMainModule` as that one only adds a require to the provided module but doesn't include all the dependencies that module amy have that the entry point doesn't. I'll address this in a follow up task to enable asap hot loading (9536142) I had to remove 2 `.babelrc` files from `react-proxy` and `react-deep-force-update`. There's an internal task for fixing the underlaying issue to avoid doing this horrible hack (t9515889). Reviewed By: vjeux Differential Revision: D2790806 fb-gh-sync-id: d4b78a2acfa071d6b3accc2e6716ef5611ad4fda
2015-12-28 16:43:21 -08:00
'react-transform-hmr',
],
platforms: ['ios', 'android'],
preferNativePlatform: true,
fileWatcher: opts.fileWatcher,
cache: opts.cache,
});
this._polyfillModuleNames = opts.polyfillModuleNames || [];
}
getShallowDependencies(entryFile) {
return this._depGraph.getShallowDependencies(entryFile);
}
getModuleForPath(entryFile) {
return this._depGraph.getModuleForPath(entryFile);
}
getDependencies(main, options) {
const opts = getDependenciesValidateOpts(options);
return this._depGraph.getDependencies(main, opts.platform).then(
resolutionResponse => {
this._getPolyfillDependencies().reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
return resolutionResponse.finalize();
}
);
}
getModuleSystemDependencies(options) {
const opts = getDependenciesValidateOpts(options);
const prelude = opts.dev
? path.join(__dirname, 'polyfills/prelude_dev.js')
: path.join(__dirname, 'polyfills/prelude.js');
const moduleSystem = opts.isUnbundle
? path.join(__dirname, 'polyfills/require-unbundle.js')
: path.join(__dirname, 'polyfills/require.js');
return [
prelude,
moduleSystem
].map(moduleName => new Polyfill({
path: moduleName,
id: moduleName,
dependencies: [],
isPolyfill: true,
}));
}
_getPolyfillDependencies() {
const polyfillModuleNames = [
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'),
path.join(__dirname, 'polyfills/Array.es6.js'),
path.join(__dirname, 'polyfills/babelHelpers.js'),
].concat(this._polyfillModuleNames);
return polyfillModuleNames.map(
(polyfillModuleName, idx) => new Polyfill({
path: polyfillModuleName,
id: polyfillModuleName,
dependencies: polyfillModuleNames.slice(0, idx),
isPolyfill: true,
})
);
}
resolveRequires(resolutionResponse, module, code) {
return Promise.resolve().then(() => {
if (module.isPolyfill()) {
return Promise.resolve({code});
}
const resolvedDeps = Object.create(null);
const resolvedDepsArr = [];
return Promise.all(
resolutionResponse.getResolvedDependencyPairs(module).map(
([depName, depModule]) => {
if (depModule) {
return depModule.getName().then(name => {
resolvedDeps[depName] = name;
resolvedDepsArr.push(name);
});
}
}
)
).then(() => {
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
const depId = resolvedDeps[depName];
if (depId) {
return pre + quot + depId + post;
} else {
return codeMatch;
}
};
code = code
.replace(replacePatterns.IMPORT_RE, relativizeCode)
.replace(replacePatterns.EXPORT_RE, relativizeCode)
.replace(replacePatterns.REQUIRE_RE, relativizeCode);
return module.getName().then(name => {
return {name, code};
});
});
});
}
wrapModule(resolutionResponse, module, code) {
if (module.isPolyfill()) {
return Promise.resolve({code});
}
return this.resolveRequires(resolutionResponse, module, code).then(
({name, code}) => {
return {name, code: defineModuleCode(name, code)};
});
}
getDebugInfo() {
return this._depGraph.getDebugInfo();
}
}
function defineModuleCode(moduleName, code) {
return [
`__d(`,
`'${moduleName}',`,
'function(global, require, module, exports) {',
` ${code}`,
'\n});',
].join('');
}
module.exports = Resolver;