From 1eedf05651362b9633b2ebf5ce5da8f34ffbfcc2 Mon Sep 17 00:00:00 2001 From: Alexandre Kirszenberg Date: Mon, 29 Oct 2018 08:54:17 -0700 Subject: [PATCH] Update the Delta/HMR format Summary: Makes the delta bundle data structures more consistent. The changes are as follows: * There are now two types of JSON bundles that can be downloaded from the delta endpoint. Base bundles (`Bundle` type), and Delta bundles (`DeltaBundle` type). * The `reset` boolean is renamed to `base`. * `pre` and `post` properties are now strings. * Only `Bundle` can define `pre` and `post` properties. * The `delta` property is renamed to `modules`. * Deleted modules are now listed inside of the `deleted` property, which is only defined by `DeltaBundle`. Reviewed By: mjesun Differential Revision: D10446831 fbshipit-source-id: 40e229a2811d48950f0bad8dd341ece189089e9b --- .../react/devsupport/BundleDeltaClient.java | 82 +++++++++++-------- ReactCommon/cxxreact/JSDeltaBundleClient.cpp | 30 +++---- .../server/util/debugger-ui/DeltaPatcher.js | 41 +++++----- .../util/debugger-ui/deltaUrlToBlobUrl.js | 16 ++-- 4 files changed, 88 insertions(+), 81 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java index 5af95bd5b..bdc6d0844 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java @@ -25,7 +25,7 @@ import okio.BufferedSource; public abstract class BundleDeltaClient { private static final String METRO_DELTA_ID_HEADER = "X-Metro-Delta-ID"; - @Nullable private String mDeltaId; + @Nullable private String mRevisionId; public enum ClientType { NONE, @@ -54,46 +54,45 @@ public abstract class BundleDeltaClient { BufferedSource body, File outputFile) throws IOException; - final public String extendUrlForDelta(String bundleURL) { - return mDeltaId != null ? bundleURL + "&deltaBundleId=" + mDeltaId : bundleURL; + final public synchronized String extendUrlForDelta(String bundleURL) { + return mRevisionId != null ? bundleURL + "&revisionId=" + mRevisionId : bundleURL; } - public void reset() { - mDeltaId = null; + public synchronized void reset() { + mRevisionId = null; } - public Pair processDelta( + public synchronized Pair processDelta( Headers headers, BufferedSource body, File outputFile) throws IOException { - mDeltaId = headers.get(METRO_DELTA_ID_HEADER); + mRevisionId = headers.get(METRO_DELTA_ID_HEADER); return processDelta(body, outputFile); } private static class BundleDeltaJavaClient extends BundleDeltaClient { - final LinkedHashMap mPreModules = new LinkedHashMap(); - final LinkedHashMap mDeltaModules = new LinkedHashMap(); - final LinkedHashMap mPostModules = new LinkedHashMap(); + byte[] mPreCode; + byte[] mPostCode; + final LinkedHashMap mModules = new LinkedHashMap(); @Override public boolean canHandle(ClientType type) { return type == ClientType.DEV_SUPPORT; } - public void reset() { + public synchronized void reset() { super.reset(); - mDeltaModules.clear(); - mPreModules.clear(); - mPostModules.clear(); + mPreCode = null; + mPostCode = null; + mModules.clear(); } @Override public synchronized Pair processDelta( BufferedSource body, File outputFile) throws IOException { - JsonReader jsonReader = new JsonReader(new InputStreamReader(body.inputStream())); jsonReader.beginObject(); int numChangedModules = 0; @@ -101,11 +100,13 @@ public abstract class BundleDeltaClient { while (jsonReader.hasNext()) { String name = jsonReader.nextName(); if (name.equals("pre")) { - numChangedModules += patchDelta(jsonReader, mPreModules); + mPreCode = jsonReader.nextString().getBytes(); } else if (name.equals("post")) { - numChangedModules += patchDelta(jsonReader, mPostModules); - } else if (name.equals("delta")) { - numChangedModules += patchDelta(jsonReader, mDeltaModules); + mPostCode = jsonReader.nextString().getBytes(); + } else if (name.equals("modules")) { + numChangedModules += setModules(jsonReader, mModules); + } else if (name.equals("deleted")) { + numChangedModules += removeModules(jsonReader, mModules); } else { jsonReader.skipValue(); } @@ -123,20 +124,16 @@ public abstract class BundleDeltaClient { FileOutputStream fileOutputStream = new FileOutputStream(outputFile); try { - for (byte[] code : mPreModules.values()) { + fileOutputStream.write(mPreCode); + fileOutputStream.write('\n'); + + for (byte[] code : mModules.values()) { fileOutputStream.write(code); fileOutputStream.write('\n'); } - for (byte[] code : mDeltaModules.values()) { - fileOutputStream.write(code); - fileOutputStream.write('\n'); - } - - for (byte[] code : mPostModules.values()) { - fileOutputStream.write(code); - fileOutputStream.write('\n'); - } + fileOutputStream.write(mPostCode); + fileOutputStream.write('\n'); } finally { fileOutputStream.flush(); fileOutputStream.close(); @@ -145,7 +142,7 @@ public abstract class BundleDeltaClient { return Pair.create(Boolean.TRUE, null); } - private static int patchDelta(JsonReader jsonReader, LinkedHashMap map) + private static int setModules(JsonReader jsonReader, LinkedHashMap map) throws IOException { jsonReader.beginArray(); @@ -155,12 +152,7 @@ public abstract class BundleDeltaClient { int moduleId = jsonReader.nextInt(); - if (jsonReader.peek() == JsonToken.NULL) { - jsonReader.skipValue(); - map.remove(moduleId); - } else { - map.put(moduleId, jsonReader.nextString().getBytes()); - } + map.put(moduleId, jsonReader.nextString().getBytes()); jsonReader.endArray(); numModules++; @@ -170,6 +162,24 @@ public abstract class BundleDeltaClient { return numModules; } + + private static int removeModules(JsonReader jsonReader, LinkedHashMap map) + throws IOException { + jsonReader.beginArray(); + + int numModules = 0; + while (jsonReader.hasNext()) { + int moduleId = jsonReader.nextInt(); + + map.remove(moduleId); + + numModules++; + } + + jsonReader.endArray(); + + return numModules; + } } private static class BundleDeltaNativeClient extends BundleDeltaClient { diff --git a/ReactCommon/cxxreact/JSDeltaBundleClient.cpp b/ReactCommon/cxxreact/JSDeltaBundleClient.cpp index 8c082d04c..d6785226d 100644 --- a/ReactCommon/cxxreact/JSDeltaBundleClient.cpp +++ b/ReactCommon/cxxreact/JSDeltaBundleClient.cpp @@ -13,9 +13,7 @@ namespace { for (auto section : {pre, post}) { if (section != nullptr) { - for (folly::dynamic pair : *section) { - startupCode << pair[1].getString() << '\n'; - } + startupCode << section->getString() << '\n'; } } @@ -24,28 +22,30 @@ namespace { } // namespace void JSDeltaBundleClient::patch(const folly::dynamic& delta) { - auto const reset = delta.get_ptr("reset"); - if (reset != nullptr && reset->asBool()) { + auto const base = delta.get_ptr("base"); + + if (base != nullptr && base->asBool()) { clear(); - } - auto const pre = delta.get_ptr("pre"); - auto const post = delta.get_ptr("post"); + auto const pre = delta.get_ptr("pre"); + auto const post = delta.get_ptr("post"); - if ((pre != nullptr && pre->size() > 0) || (post != nullptr && post->size() > 0)) { startupCode_ = startupCode(pre, post); + } else { + const folly::dynamic *deleted = delta.get_ptr("deleted"); + if (deleted != nullptr) { + for (const folly::dynamic id : *deleted) { + modules_.erase(id.getInt()); + } + } } - const folly::dynamic *modules = delta.get_ptr("delta"); + const folly::dynamic *modules = delta.get_ptr("modules"); if (modules != nullptr) { for (const folly::dynamic pair : *modules) { auto id = pair[0].getInt(); auto module = pair[1]; - if (module.isNull()) { - modules_.erase(id); - } else { - modules_.emplace(id, module.getString()); - } + modules_.emplace(id, module.getString()); } } } diff --git a/local-cli/server/util/debugger-ui/DeltaPatcher.js b/local-cli/server/util/debugger-ui/DeltaPatcher.js index f99dfd2c2..d96030c67 100644 --- a/local-cli/server/util/debugger-ui/DeltaPatcher.js +++ b/local-cli/server/util/debugger-ui/DeltaPatcher.js @@ -27,10 +27,10 @@ class DeltaPatcher { constructor() { this._lastBundle = { - pre: new Map(), - post: new Map(), + revisionId: undefined, + pre: '', + post: '', modules: new Map(), - id: undefined, }; this._initialized = false; this._lastNumModifiedFiles = 0; @@ -51,44 +51,41 @@ /** * 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) { + applyDelta(bundle) { + // Make sure that the first received bundle is a base. + if (!this._initialized && !bundle.base) { throw new Error( - 'DeltaPatcher should receive a fresh Delta when being initialized', + 'DeltaPatcher should receive a base Bundle when being initialized', ); } this._initialized = true; - // Reset the current delta when we receive a fresh delta. - if (deltaBundle.reset) { + // Reset the current bundle when we receive a base bundle. + if (bundle.base) { this._lastBundle = { - pre: new Map(), - post: new Map(), + revisionId: undefined, + pre: bundle.pre, + post: bundle.post, modules: new Map(), - id: undefined, }; } - this._lastNumModifiedFiles = - deltaBundle.pre.size + deltaBundle.post.size + deltaBundle.delta.size; + this._lastNumModifiedFiles = bundle.modules.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); + this._patchMap(this._lastBundle.modules, bundle.modules); - this._lastBundle.id = deltaBundle.id; + this._lastBundle.revisionId = bundle.revisionId; return this; } - getLastBundleId() { - return this._lastBundle.id; + getLastRevisionId() { + return this._lastBundle.revisionId; } /** @@ -107,9 +104,9 @@ getAllModules() { return [].concat( - Array.from(this._lastBundle.pre.values()), + this._lastBundle.pre, Array.from(this._lastBundle.modules.values()), - Array.from(this._lastBundle.post.values()), + this._lastBundle.post, ); } diff --git a/local-cli/server/util/debugger-ui/deltaUrlToBlobUrl.js b/local-cli/server/util/debugger-ui/deltaUrlToBlobUrl.js index 323342448..6032be956 100644 --- a/local-cli/server/util/debugger-ui/deltaUrlToBlobUrl.js +++ b/local-cli/server/util/debugger-ui/deltaUrlToBlobUrl.js @@ -22,19 +22,19 @@ async function deltaUrlToBlobUrl(deltaUrl) { const client = global.DeltaPatcher.get(deltaUrl); - const deltaBundleId = client.getLastBundleId() - ? `&deltaBundleId=${client.getLastBundleId()}` + const revisionId = client.getLastRevisionId() + ? `&revisionId=${client.getLastRevisionId()}` : ''; - const data = await fetch(deltaUrl + deltaBundleId); + const data = await fetch(deltaUrl + revisionId); const bundle = await data.json(); const deltaPatcher = client.applyDelta({ - id: bundle.id, - pre: new Map(bundle.pre), - post: new Map(bundle.post), - delta: new Map(bundle.delta), - reset: bundle.reset, + base: bundle.base, + revisionId: bundle.revisionId, + pre: bundle.pre, + post: bundle.post, + modules: new Map(bundle.modules), }); let cachedBundle = cachedBundleUrls.get(deltaUrl);