Bug 1250002 - part1: add loader to globals exposed by require from browser-loader;r=jryans
authorJulian Descottes <jdescottes@mozilla.com>
Tue, 23 Feb 2016 23:31:32 +0100
changeset 321930 5c0b8b63d2a8ed080b6f785b641dbd059ecd2bf4
parent 321929 b34dbc48da45cb1c06e9088835513837bf0ff6ca
child 321931 3ac7627f1cf7a049091bc639ea9c2a3e3a9acd72
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1250002
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1250002 - part1: add loader to globals exposed by require from browser-loader;r=jryans MozReview-Commit-ID: Dzv5AdksdnP
devtools/client/shared/browser-loader.js
--- a/devtools/client/shared/browser-loader.js
+++ b/devtools/client/shared/browser-loader.js
@@ -13,50 +13,16 @@ Cu.import("resource://gre/modules/AppCon
 
 const BROWSER_BASED_DIRS = [
   "resource://devtools/client/jsonview",
   "resource://devtools/client/shared/vendor",
   "resource://devtools/client/shared/components",
   "resource://devtools/client/shared/redux"
 ];
 
-function clearCache() {
-  Services.obs.notifyObservers(null, "startupcache-invalidate", null);
-}
-
-function hotReloadFile(window, require, loader, componentProxies, fileURI) {
-  dump("Hot reloading: " + fileURI + "\n");
-
-  if (fileURI.match(/\.js$/)) {
-    // Test for React proxy components
-    const proxy = componentProxies.get(fileURI);
-    if (proxy) {
-      // Remove the old module and re-require the new one; the require
-      // hook in the loader will take care of the rest
-      delete loader.modules[fileURI];
-      clearCache();
-      require(fileURI);
-    }
-  } else if (fileURI.match(/\.css$/)) {
-    const links = [...window.document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "link")];
-    links.forEach(link => {
-      if (link.href.indexOf(fileURI) === 0) {
-        const parentNode = link.parentNode;
-        const newLink = window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
-        newLink.rel = "stylesheet";
-        newLink.type = "text/css";
-        newLink.href = fileURI + "?s=" + Math.random();
-
-        parentNode.insertBefore(newLink, link);
-        parentNode.removeChild(link);
-      }
-    });
-  }
-}
-
 /*
  * Create a loader to be used in a browser environment. This evaluates
  * modules in their own environment, but sets window (the normal
  * global object) as the sandbox prototype, so when a variable is not
  * defined it checks `window` before throwing an error. This makes all
  * browser APIs available to modules by default, like a normal browser
  * environment, but modules are still evaluated in their own scope.
  *
@@ -73,16 +39,33 @@ function hotReloadFile(window, require, 
  * @param Object window
  *        The window instance to evaluate modules within
  * @return Object
  *         An object with two properties:
  *         - loader: the Loader instance
  *         - require: a function to require modules with
  */
 function BrowserLoader(baseURI, window) {
+  const browserLoaderBuilder = new BrowserLoaderBuilder(baseURI, window);
+  return {
+    loader: browserLoaderBuilder.loader,
+    require: browserLoaderBuilder.require
+  };
+}
+
+/**
+ * Private class used to build the Loader instance and require method returned
+ * by BrowserLoader(baseURI, window).
+ *
+ * @param string baseURI
+ *        Base path to load modules from.
+ * @param Object window
+ *        The window instance to evaluate modules within
+ */
+function BrowserLoaderBuilder(baseURI, window) {
   const loaderOptions = devtools.require("@loader/options");
   const dynamicPaths = {};
   const componentProxies = new Map();
   const hotReloadEnabled = Services.prefs.getBoolPref("devtools.loader.hotreload");
 
   if(AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES) {
     dynamicPaths["devtools/client/shared/vendor/react"] =
       "resource://devtools/client/shared/vendor/react-dev";
@@ -121,16 +104,23 @@ function BrowserLoader(baseURI, window) 
       //     ... code ...
       //   });
       //
       // Bug 1248830 will work out a better plan here for our content module
       // loading needs, especially as we head towards devtools.html.
       define(factory) {
         factory(this.require, this.exports, this.module);
       },
+      // Allow modules to use the DevToolsLoader lazy loading helpers.
+      loader: {
+        lazyGetter: devtools.lazyGetter,
+        lazyImporter: devtools.lazyImporter,
+        lazyServiceGetter: devtools.lazyServiceGetter,
+        lazyRequireGetter: this.lazyRequireGetter.bind(this),
+      },
     }
   };
 
   if(hotReloadEnabled) {
     opts.loadModuleHook = (module, require) => {
       const { uri, exports } = module;
 
       if (exports.prototype &&
@@ -150,34 +140,86 @@ function BrowserLoader(baseURI, window) 
           instances.forEach(getForceUpdate(React));
           module.exports = proxy.get();
         }
       }
       return exports;
     }
   }
 
-
   const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
-  const mainLoader = loaders.Loader(opts);
-  const require = loaders.Require(mainLoader, mainModule);
+  this.loader = loaders.Loader(opts);
+  this.require = loaders.Require(this.loader, mainModule);
 
   if (hotReloadEnabled) {
     const watcher = devtools.require("devtools/client/shared/file-watcher");
-    function onFileChanged(_, fileURI) {
-      hotReloadFile(window, require, mainLoader, componentProxies, fileURI);
-    }
+    const onFileChanged = (_, fileURI) => {
+      this.hotReloadFile(window, componentProxies, fileURI);
+    };
     watcher.on("file-changed", onFileChanged);
 
     window.addEventListener("unload", () => {
       watcher.off("file-changed", onFileChanged);
     });
   }
+}
 
-  return {
-    loader: mainLoader,
-    require: require
-  };
-}
+BrowserLoaderBuilder.prototype = {
+  /**
+   * Define a getter property on the given object that requires the given
+   * module. This enables delaying importing modules until the module is
+   * actually used.
+   *
+   * @param Object obj
+   *    The object to define the property on.
+   * @param String property
+   *    The property name.
+   * @param String module
+   *    The module path.
+   * @param Boolean destructure
+   *    Pass true if the property name is a member of the module's exports.
+   */
+  lazyRequireGetter: function(obj, property, module, destructure) {
+    devtools.lazyGetter(obj, property, () => {
+      return destructure
+          ? this.require(module)[property]
+          : this.require(module || property);
+    });
+  },
+
+  clearCache: function() {
+    Services.obs.notifyObservers(null, "startupcache-invalidate", null);
+  },
+
+  hotReloadFile: function(window, componentProxies, fileURI) {
+    dump("Hot reloading: " + fileURI + "\n");
+
+    if (fileURI.match(/\.js$/)) {
+      // Test for React proxy components
+      const proxy = componentProxies.get(fileURI);
+      if (proxy) {
+        // Remove the old module and re-require the new one; the require
+        // hook in the loader will take care of the rest
+        delete this.loader.modules[fileURI];
+        this.clearCache();
+        this.require(fileURI);
+      }
+    } else if (fileURI.match(/\.css$/)) {
+      const links = [...window.document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "link")];
+      links.forEach(link => {
+        if (link.href.indexOf(fileURI) === 0) {
+          const parentNode = link.parentNode;
+          const newLink = window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
+          newLink.rel = "stylesheet";
+          newLink.type = "text/css";
+          newLink.href = fileURI + "?s=" + Math.random();
+
+          parentNode.insertBefore(newLink, link);
+          parentNode.removeChild(link);
+        }
+      });
+    }
+  }
+};
 
 this.BrowserLoader = BrowserLoader;
 
 this.EXPORTED_SYMBOLS = ["BrowserLoader"];