Add Delta Bundler client to JS remote debugger
Reviewed By: jeanlauliac Differential Revision: D5899825 fbshipit-source-id: 9c5815ef234507034016becaabae754129b96c71
This commit is contained in:
parent
3849765ac1
commit
ace7273538
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is a copy of the reference `DeltaPatcher`, located in
|
||||
* metro-bundler. The reason to not reuse that file is that in this context
|
||||
* we cannot have flow annotations or CJS syntax (since this file is directly)
|
||||
* injected into a static HTML page.
|
||||
*
|
||||
* TODO: Find a simple and lightweight way to compile `DeltaPatcher` to avoid
|
||||
* having this duplicated file.
|
||||
*/
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This is a reference client for the Delta Bundler: it maintains cached the
|
||||
* last patched bundle delta and it's capable of applying new Deltas received
|
||||
* from the Bundler.
|
||||
*/
|
||||
class DeltaPatcher {
|
||||
constructor() {
|
||||
this._lastBundle = {
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
modules: new Map(),
|
||||
};
|
||||
this._initialized = false;
|
||||
this._lastNumModifiedFiles = 0;
|
||||
this._lastModifiedDate = new Date();
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
let deltaPatcher = this._deltaPatchers.get(id);
|
||||
|
||||
if (!deltaPatcher) {
|
||||
deltaPatcher = new DeltaPatcher();
|
||||
this._deltaPatchers.set(id, deltaPatcher);
|
||||
}
|
||||
|
||||
return deltaPatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a Delta Bundle to the current bundle.
|
||||
*/
|
||||
applyDelta(deltaBundle) {
|
||||
// Make sure that the first received delta is a fresh one.
|
||||
if (!this._initialized && !deltaBundle.reset) {
|
||||
throw new Error(
|
||||
'DeltaPatcher should receive a fresh Delta when being initialized',
|
||||
);
|
||||
}
|
||||
|
||||
this._initialized = true;
|
||||
|
||||
// Reset the current delta when we receive a fresh delta.
|
||||
if (deltaBundle.reset) {
|
||||
this._lastBundle = {
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
modules: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
this._lastNumModifiedFiles =
|
||||
deltaBundle.pre.size + deltaBundle.post.size + deltaBundle.delta.size;
|
||||
|
||||
if (this._lastNumModifiedFiles > 0) {
|
||||
this._lastModifiedDate = new Date();
|
||||
}
|
||||
|
||||
this._patchMap(this._lastBundle.pre, deltaBundle.pre);
|
||||
this._patchMap(this._lastBundle.post, deltaBundle.post);
|
||||
this._patchMap(this._lastBundle.modules, deltaBundle.delta);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of modified files in the last received Delta. This is
|
||||
* currently used to populate the `X-Metro-Files-Changed-Count` HTTP header
|
||||
* when metro serves the whole JS bundle, and can potentially be removed once
|
||||
* we only send the actual deltas to clients.
|
||||
*/
|
||||
getLastNumModifiedFiles() {
|
||||
return this._lastNumModifiedFiles;
|
||||
}
|
||||
|
||||
getLastModifiedDate() {
|
||||
return this._lastModifiedDate;
|
||||
}
|
||||
|
||||
getAllModules() {
|
||||
return [].concat(
|
||||
Array.from(this._lastBundle.pre.values()),
|
||||
Array.from(this._lastBundle.modules.values()),
|
||||
Array.from(this._lastBundle.post.values()),
|
||||
);
|
||||
}
|
||||
|
||||
_patchMap(original, patch) {
|
||||
for (const [key, value] of patch.entries()) {
|
||||
if (value == null) {
|
||||
original.delete(key);
|
||||
} else {
|
||||
original.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeltaPatcher._deltaPatchers = new Map();
|
||||
|
||||
global.DeltaPatcher = DeltaPatcher;
|
||||
})(window);
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
/* global Blob, URL: true */
|
||||
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
let cachedBundleUrls = new Map();
|
||||
|
||||
/**
|
||||
* Converts the passed delta URL into an URL object containing already the
|
||||
* whole JS bundle Blob.
|
||||
*/
|
||||
async function deltaUrlToBlobUrl(deltaUrl) {
|
||||
let cachedBundle = cachedBundleUrls.get(deltaUrl);
|
||||
|
||||
const deltaBundleId = cachedBundle
|
||||
? `&deltaBundleId=${cachedBundle.id}`
|
||||
: '';
|
||||
|
||||
const data = await fetch(deltaUrl + deltaBundleId);
|
||||
const bundle = await data.json();
|
||||
|
||||
const deltaPatcher = global.DeltaPatcher.get(bundle.id).applyDelta({
|
||||
pre: new Map(bundle.pre),
|
||||
post: new Map(bundle.post),
|
||||
delta: new Map(bundle.delta),
|
||||
reset: bundle.reset,
|
||||
});
|
||||
|
||||
// If nothing changed, avoid recreating a bundle blob by reusing the
|
||||
// previous one.
|
||||
if (deltaPatcher.getLastNumModifiedFiles() === 0 && cachedBundle) {
|
||||
return cachedBundle.url;
|
||||
}
|
||||
|
||||
// Clean up the previous bundle URL to not leak memory.
|
||||
if (cachedBundle) {
|
||||
URL.revokeObjectURL(cachedBundle.url);
|
||||
}
|
||||
|
||||
const blobContent = deltaPatcher.getAllModules();
|
||||
|
||||
// Build the blob with the whole JS bundle.
|
||||
const blob = new Blob(blobContent, {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
|
||||
const bundleUrl = URL.createObjectURL(blob);
|
||||
cachedBundleUrls.set(deltaUrl, {
|
||||
id: bundle.id,
|
||||
url: bundleUrl,
|
||||
});
|
||||
|
||||
return bundleUrl;
|
||||
}
|
||||
|
||||
global.deltaUrlToBlobUrl = deltaUrlToBlobUrl;
|
||||
})(window || {});
|
|
@ -12,6 +12,8 @@
|
|||
<meta charset=utf-8>
|
||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||
<title>React Native Debugger</title>
|
||||
<script src="/debugger-ui/DeltaPatcher.js"></script>
|
||||
<script src="/debugger-ui/deltaUrlToBlobUrl.js"></script>
|
||||
<script>
|
||||
/* eslint-env browser */
|
||||
'use strict';
|
||||
|
@ -150,7 +152,7 @@
|
|||
Page.setState({status: {type: 'connecting'}});
|
||||
};
|
||||
|
||||
ws.onmessage = function(message) {
|
||||
ws.onmessage = async function(message) {
|
||||
if (!message.data) {
|
||||
return;
|
||||
}
|
||||
|
@ -176,6 +178,11 @@
|
|||
} else if (object.method === '$disconnected') {
|
||||
shutdownJSRuntime();
|
||||
Page.setState({status: {type: 'disconnected'}});
|
||||
} else if (object.method === 'executeApplicationScript') {
|
||||
worker.postMessage({
|
||||
...object,
|
||||
url: await getBlobUrl(object.url),
|
||||
});
|
||||
} else {
|
||||
// Otherwise, pass through to the worker.
|
||||
worker.postMessage(object);
|
||||
|
@ -198,6 +205,9 @@
|
|||
|
||||
connectToDebuggerProxy();
|
||||
|
||||
async function getBlobUrl(url) {
|
||||
return await window.deltaUrlToBlobUrl(url.replace('.bundle', '.delta'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style type="text/css">
|
||||
|
|
Loading…
Reference in New Issue