Skip to content

Commit

Permalink
Allow parents to accept children modules
Browse files Browse the repository at this point in the history
Summary:In order to be able to Hot Load Redux stores and modules that export functions, we need to build infrastructure to bubble up the HMR updates similar to how webpack does: https://webpack.github.io/docs/hot-module-replacement-with-webpack.html.

In here we introduce the minimum of this infrastructure we need to make this work. The Packager server needs to send the inverse dependencies to the HMR runtime that runs on the client so that it can bubble up the patches if they cannot be self accepted by the module that was changed.

This diff relies on https://github.com/facebook/node-haste/pull/40/files which adds support for getting the inverse dependencies.

Reviewed By: davidaurelio

Differential Revision: D2950662

fb-gh-sync-id: 26dcd4aa15da76a727026a9d7ee06e7ae4d22eaa
shipit-source-id: 26dcd4aa15da76a727026a9d7ee06e7ae4d22eaa
  • Loading branch information
martinbigio authored and Facebook Github Bot 4 committed Feb 26, 2016
1 parent d7cee3a commit 436db67
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 29 deletions.
21 changes: 17 additions & 4 deletions Libraries/Utilities/HMRClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ Error: ${e.message}`
break;
}
case 'update': {
const modules = data.body.modules;
const sourceMappingURLs = data.body.sourceMappingURLs;
const sourceURLs = data.body.sourceURLs;
const {
modules,
sourceMappingURLs,
sourceURLs,
inverseDependencies,
} = data.body;

if (Platform.OS === 'ios') {
const RCTRedBox = require('NativeModules').RedBox;
Expand All @@ -85,7 +88,7 @@ Error: ${e.message}`
RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
}

modules.forEach((code, i) => {
modules.forEach(({name, code}, i) => {
code = code + '\n\n' + sourceMappingURLs[i];

require('SourceMapsCache').fetch({
Expand All @@ -101,6 +104,16 @@ Error: ${e.message}`
? global.nativeInjectHMRUpdate
: eval;

// TODO: (martinb) yellow box if cannot accept module
code = `
__accept(
${name},
function(global, require, module, exports) {
${code}
},
${JSON.stringify(inverseDependencies)}
);`;

injectFunction(code, sourceURLs[i]);
});

Expand Down
30 changes: 20 additions & 10 deletions local-cli/server/util/attachHMRServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
'use strict';

const getInverseDependencies = require('node-haste').getInverseDependencies;
const querystring = require('querystring');
const url = require('url');

Expand All @@ -23,9 +24,10 @@ function attachHMRServer({httpServer, path, packagerServer}) {
packagerServer.setHMRFileChangeListener(null);
}

// Returns a promise with the full list of dependencies and the shallow
// dependencies each file on the dependency list has for the give platform
// and entry file.
// For the give platform and entry file, returns a promise with:
// - The full list of dependencies.
// - The shallow dependencies each file on the dependency list has
// - Inverse shallow dependencies map
function getDependencies(platform, bundleEntry) {
return packagerServer.getDependencies({
platform: platform,
Expand Down Expand Up @@ -70,12 +72,16 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesModulesCache[depName] = dep;
});
})).then(() => {
return {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
resolutionResponse: response,
};
return getInverseDependencies(response)
.then(inverseDependenciesCache => {
return {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
inverseDependenciesCache,
resolutionResponse: response,
};
});
});
});
});
Expand All @@ -97,6 +103,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
inverseDependenciesCache,
}) => {
client = {
ws,
Expand All @@ -105,6 +112,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
inverseDependenciesCache,
};

packagerServer.setHMRFileChangeListener((filename, stat) => {
Expand Down Expand Up @@ -151,6 +159,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
inverseDependenciesCache,
resolutionResponse,
}) => {
if (!client) {
Expand Down Expand Up @@ -211,7 +220,8 @@ function attachHMRServer({httpServer, path, packagerServer}) {
return JSON.stringify({
type: 'update',
body: {
modules: bundle.getModulesCode(),
modules: bundle.getModulesNamesAndCode(),
inverseDependencies: inverseDependenciesCache,
sourceURLs: bundle.getSourceURLs(),
sourceMappingURLs: bundle.getSourceMappingURLs(),
},
Expand Down
12 changes: 7 additions & 5 deletions packager/react-packager/src/Bundler/HMRBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ class HMRBundle extends BundleBase {
module,
transformed.code,
).then(({name, code}) => {
// need to be in single line so that lines match on sourcemaps
code = `__accept(${JSON.stringify(name)}, function(global, require, module, exports) { ${code} });`;

const moduleTransport = new ModuleTransport({
code,
name,
Expand All @@ -44,8 +41,13 @@ class HMRBundle extends BundleBase {
});
}

getModulesCode() {
return this._modules.map(module => module.code);
getModulesNamesAndCode() {
return this._modules.map(module => {
return {
name: JSON.stringify(module.name),
code: module.code,
};
});
}

getSourceURLs() {
Expand Down
49 changes: 39 additions & 10 deletions packager/react-packager/src/Resolver/polyfills/require.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,34 +94,63 @@ global.__d = define;
global.require = require;

if (__DEV__) { // HMR
function accept(id, factory) {
function accept(id, factory, inverseDependencies) {
var mod = modules[id];

if (!mod) {
define(id, factory);
return; // new modules don't need to be accepted
return true; // new modules don't need to be accepted
}

if (!mod.module.hot) {
console.warn(
'Cannot accept module because Hot Module Replacement ' +
'API was not installed.'
);
return;
return false;
}

if (mod.module.hot.acceptCallback) {
// replace and initialize factory
if (factory) {
mod.factory = factory;
mod.isInitialized = false;
require(id);
}
mod.isInitialized = false;
require(id);

if (mod.module.hot.acceptCallback) {
mod.module.hot.acceptCallback();
return true;
} else {
console.warn(
'[HMR] Module `' + id + '` can\'t be hot reloaded because it\'s not a ' +
'React component. To get the changes reload the JS bundle.'
);
// need to have inverseDependencies to bubble up accept
if (!inverseDependencies) {
throw new Error('Undefined `inverseDependencies`');
}

// accept parent modules recursively up until all siblings are accepted
return acceptAll(inverseDependencies[id], inverseDependencies);
}
}

function acceptAll(modules, inverseDependencies) {
if (modules.length === 0) {
return true;
}

var notAccepted = modules.filter(function(module) {
return !accept(module, /*factory*/ undefined, inverseDependencies);
});

var parents = [];
for (var i = 0; i < notAccepted.length; i++) {
// if this the module has no parents then the change cannot be hot loaded
if (inverseDependencies[notAccepted[i]].length === 0) {
return false;
}

parents.pushAll(inverseDependencies[notAccepted[i]]);
}

return acceptAll(parents, inverseDependencies);
}

global.__accept = accept;
Expand Down

0 comments on commit 436db67

Please sign in to comment.