Skip to content

Instantly share code, notes, and snippets.

@jamiebuilds
Last active October 9, 2017 06:11
Show Gist options
  • Save jamiebuilds/128ca73cb948d69c558607f2ca502703 to your computer and use it in GitHub Desktop.
Save jamiebuilds/128ca73cb948d69c558607f2ca502703 to your computer and use it in GitHub Desktop.

Synchronous module inspection

This document describes the challenges presented by import() and how they could be addressed.

Problem

If you're starting up an app with multiple modules already loaded, using import() prevents you from gaining access to them synchronously.

let loadFoo = () => import('./foo');
let loadBar = () => import('./bar');

From this code, you have no way of knowing if the ./foo or ./bar modules are loaded already. The only option you have is to call loadFoo() and loadBar() and wait for them to resolve.

Additionally, if either module has already been loaded, you have no way of gaining access to them synchronously.

This limits your ability to make decisions based on the state of the modules that are loaded.

In the browser, this is problematic because modules could take a much longer time to load based on the network performance. At the same time you need to make decisions on what to show the user.

In order to get around this, users are reaching into their build systems and depending on the implementation of how their modules are getting loaded.

For example, with Webpack:

let tryLoad = getWeakId => {
  // Make sure we're inside of Webpack before we start doing things.
  if (
    typeof __webpack_require__ !== 'undefined' &&
    typeof __webpack_modules__ !== 'undefined' &&
    typeof require.resolveWeak !== 'undefined'
  ) {
    let weakId = getWeakId();
    // If you don't check this, and call __webpack_require__ it will cache an
    // empty module
    if (__webpack_modules__[weakId]) {
      // __webpack_require__ could throw in some scenarios
      try {
        let obj = __webpack_require__(weakId);
        // Make sure to interop if the Babel was possibly compiled with Babel.
        return obj && obj.__esModule ? obj.default : obj
      } catch (err) {}
    }
  }
  return null;
};

// We need to put resolveWeak inside a function so we don't call it outside of
// a Webpack context. Also we need the exact expression
// `require.resolveWeak("...")` in order to work.
let foo = tryLoad(() => require.resolveWeak('./foo'));

async function main() {
  // We need to repeat `./foo`
  if (!foo) foo = await import('./foo');
  foo();
}

Proposed Solution

  • Add API (i.e. System.moduleSpecifier) to describe the module separately from import()
  • Add API (i.e. System.isLoaded(moduleSpecifier)) to query if the module is loaded or not.
  • Add API (i.e. System.getModule()) to get access to loaded modules that we know are loaded.
const FOO_MODULE = System.moduleSpecifier('./foo');
const BAR_MODULE = System.moduleSpecifier('./foo');

async function main() {
  let foo;

  if (System.isLoaded(FOO_MODULE)) {
    foo = System.getModule(FOO_MODULE);
  } else {
    document.body.innerHTML = 'Loading...';
    foo = await import(FOO_MODULE);
  }

  foo();
}

Alternative API

Instead of querying if a module is loaded and then going to load it, instead you could just try-catch the call to System.getModule(FOO_MODULE) which would throw if the module is not already loaded.

const FOO_MODULE = System.moduleSpecifier('./foo');
const BAR_MODULE = System.moduleSpecifier('./foo');

async function main() {
  let foo;

  try {
    foo = System.getModule(FOO_MODULE);
  } catch (err) {
    document.body.innerHTML = 'Loading...';
    foo = await import(FOO_MODULE);
  }

  foo();
}

Comparison

Before

let tryLoad = getWeakId => {
  // Make sure we're inside of Webpack before we start doing things.
  if (
    typeof __webpack_require__ !== 'undefined' &&
    typeof __webpack_modules__ !== 'undefined' &&
    typeof require.resolveWeak !== 'undefined'
  ) {
    let weakId = getWeakId();
    // If you don't check this, and call __webpack_require__ it will cache an
    // empty module
    if (__webpack_modules__[weakId]) {
      // __webpack_require__ could throw in some scenarios
      try {
        let obj = __webpack_require__(weakId);
        // Make sure to interop if the Babel was possibly compiled with Babel.
        return obj && obj.__esModule ? obj.default : obj
      } catch (err) {}
    }
  }
  return null;
};

// We need to put resolveWeak inside a function so we don't call it outside of
// a Webpack context. Also we need the exact expression
// `require.resolveWeak("...")` in order to work.
let foo = tryLoad(() => require.resolveWeak('./foo'));

async function main() {
  // We need to repeat `./foo`
  if (!foo) foo = await import('./foo');
  foo();
}

After

// Create a module specifier in a static way that can be used by bundlers.
// We don't need to worry about the environment because we know this function
// will always exist (or at least be polyfilled).
let moduleSpecifier = System.moduleSpecifier('./foo');
let foo;

// Check if the module is already loaded.
if (System.isLoaded(moduleSpecifier)) {
  // Get it synchronously.
  foo = System.getLoadedModule(moduleSpecifier);
} else {
  // Or wait for it to load
  foo = await import(moduleSpecifier);
}

// Note we didn't need to repeat `./foo` multiple times.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment