Tuesday, November 4, 2014

Lazy Load RequireJS Modules from a Remote Server over HTTP OR HTTPS

The Need

General Problem


  • We need to "lazy load" a RequireJS module, stored in a file in a remote server. 
  • This server has a different domain than the site hosting our code, so using an absolute URL as a path to the module is needed.  
  • We need to support loading the file over HTTP or HTTPS, while still using an absolute full path URL.

Specific Example

We need to load the "target" function from the following file, which is stored in a different domain (URL) than our current site:

module.js, stored at: http://www.server.com/module.js
/*===Inner Module Dependencies===*/
define("text!module.html!strip", [],
  function () {
      return '<div></div>';
  });
define("text!module.css", [],
  function () {
      return 'div{color:blue}';
  });
/*===Module===*/
define("http://www.server.com/module.js",
  ["require", "text!module.html!strip", "text!module.css"],
  function (require) {
      /*Module Dependencies*/
      var html = require("text!module.html!strip");
      var css = require("text!module.css");
      /*Define function*/
      function target() {
          /*...*/
      }
      /*Return Module*/
      return target;
  }
);

The Problem

When attempting to load a specific module over HTTPS, the module's name doesn't match the URL given to the require function, and an empty module is returned:

Example


lazyLoader.js
require(["https://www.server.com/module.js"], // ERROR :(
  function (module) {
      /*Not Called*/
  },
  function (err) {
      /*Called since no module matching the
       required name was found in the loaded file*/
  }
);

The Solution

Create an alias module which is named like the HTTPS URL of the original module and only returns the original module.

Example


"Alias" Module inside module.js
/*This will be returned if module was requested via HTTPS*/
define("https://www.server.com/module.js", ["require", "http://www.server.com/module.js"], function (require) {
      return require("http://www.server.com/module.js");
  }
);

What Just Happened?


Inline Naming RequireJS Modules

RequireJS allows for optional inline naming of modules, done by passing in an optional 1st parameter to the define function when defining a module:

Example

define("OPTIONAL_NAME",
  [/*DEPENDENCIES*/],
  function (require) {
      /*CODE*/
  }
);

Why is Inline Naming Needed?

Inline naming allows for placing multiple modules inside the same file. In the given example, this would cause requiring "OPTIONAL_NAME" to return the module named as such instead of the module stored in the filepath named as such, which is RequireJS's default behavior.

This naming is usually done automatically by the RequireJS optimizer when combining multiple modules into a single file.

How has Inline Naming Helped Solve Our Specific Problem?

In our "lazy load" scenario, the file was stored in a remote URL and only the module named "http://www.server.com/module.js" was actually needed. To support HTTPS as well, an alias module, named like the HTTPS URL, was created. (It goes without saying that the file server must be configured itself to support HTTPS, but that is beyond the scope of this post).

A Final Word about Automation

Creating an alias module can be done manually, but we prefer using an automation tool such as Grunt for tasks like this.

No comments:

Post a Comment