From bbb2cc000eecd7dbe43128152bcff769dfccf120 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Tue, 2 May 2017 15:59:08 -0500
Subject: [PATCH] Adds babel caching for system.js.

---
 external/systemjs/plugin-babel-cached.js | 108 +++++++++++++++++++++++
 systemjs.config.js                       |   8 +-
 2 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 external/systemjs/plugin-babel-cached.js

diff --git a/external/systemjs/plugin-babel-cached.js b/external/systemjs/plugin-babel-cached.js
new file mode 100644
index 000000000..3fca8af3d
--- /dev/null
+++ b/external/systemjs/plugin-babel-cached.js
@@ -0,0 +1,108 @@
+/* Copyright 2017 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var babel = require('plugin-babel');
+
+var cacheExpiration = 60 /* min */ * 60 * 1000;
+var dbVersion = 1;
+var dbName = 'babelcache';
+var dbCacheTable = 'translated';
+var dbPromise;
+
+function getDb() {
+  if (!dbPromise) {
+    dbPromise = new Promise(function (resolve, reject) {
+      var request = indexedDB.open(dbName, dbVersion);
+      request.onupgradeneeded = function() {
+        var db = request.result;
+        db.createObjectStore(dbCacheTable, {keyPath: 'address'});
+      };
+      request.onsuccess = function() {
+        var db = request.result;
+        resolve(db);
+      };
+      request.onerror = function () {
+        reject(request.error);
+      };
+    });
+  }
+  return dbPromise;
+}
+
+function storeCache(address, hashCode, translated, format) {
+  return getDb().then(function (db) {
+    var tx = db.transaction(dbCacheTable, 'readwrite');
+    var store = tx.objectStore(dbCacheTable);
+    store.put({
+      address: address,
+      hashCode: hashCode,
+      translated: translated,
+      expires: Date.now() + cacheExpiration,
+      format: format,
+    });
+    return new Promise(function (resolve, reject) {
+      tx.oncomplete = function () {
+        resolve();
+      };
+    });
+  });
+}
+
+function loadCache(address, hashCode) {
+  return getDb().then(function (db) {
+    var tx = db.transaction(dbCacheTable, 'readonly');
+    var store = tx.objectStore(dbCacheTable);
+    var getAddress = store.get(address);
+    return new Promise(function (resolve, reject) {
+      tx.oncomplete = function () {
+        var found = getAddress.result;
+        var isValid = found && found.hashCode === hashCode &&
+                      Date.now() < found.expires;
+        resolve(isValid ? {
+          translated: found.translated,
+          format: found.format,
+        } : null);
+      };
+    });
+  });
+}
+
+var encoder = new TextEncoder('utf-8');
+function sha256(str) {
+  var buffer = encoder.encode(str);
+  return crypto.subtle.digest('SHA-256', buffer).then(function (hash) {
+    var data = new Int32Array(hash);
+    return data[0].toString(36) + '-' + data[1].toString(36) + '-' +
+           data[2].toString(36) + '-' + data[3].toString(36);
+  });
+}
+
+exports.translate = function (load, opt) {
+  var savedHashCode;
+  return sha256(load.source).then(function (hashCode) {
+    savedHashCode = hashCode;
+    return loadCache(load.address, hashCode);
+  }).then(function (cache) {
+    if (cache) {
+      load.metadata.format = cache.format;
+      return cache.translated;
+    }
+    return babel.translate.call(this, load, opt).then(function(translated) {
+      return storeCache(load.address, savedHashCode,
+                        translated, load.metadata.format).then(function () {
+        return translated;
+      });
+    });
+  }.bind(this));
+};
diff --git a/systemjs.config.js b/systemjs.config.js
index 4cc27df37..4d88c1eb4 100644
--- a/systemjs.config.js
+++ b/systemjs.config.js
@@ -32,6 +32,11 @@
   var PluginBabelPath = 'node_modules/systemjs-plugin-babel/plugin-babel.js';
   var SystemJSPluginBabelPath =
     'node_modules/systemjs-plugin-babel/systemjs-babel-browser.js';
+  var PluginBabelCachePath = 'external/systemjs/plugin-babel-cached.js';
+
+  var isCachingPossible = typeof indexedDB !== 'undefined' &&
+                          typeof TextEncoder !== 'undefined' &&
+                          typeof crypto !== 'undefined';
 
   SystemJS.config({
     packages: {
@@ -57,7 +62,8 @@
       'plugin-babel': new URL(PluginBabelPath, baseLocation).href,
       'systemjs-babel-build':
         new URL(SystemJSPluginBabelPath, baseLocation).href,
+      'plugin-babel-cached': new URL(PluginBabelCachePath, baseLocation).href,
     },
-    transpiler: 'plugin-babel'
+    transpiler: isCachingPossible ? 'plugin-babel-cached' : 'plugin-babel'
   });
 })();