Using Closure Library modules in true browser-native ES6 modules

493 views
Skip to first unread message

Richard Connamacher

unread,
Aug 25, 2017, 12:02:38 AM8/25/17
to Closure Library Discuss
Earlier this week I started experimenting with using goog.require() in true-native, raw source ES6 modules (loaded by <script type="module">) for browsers that natively support it. This is on a personal project that has dozens of source files, some using goog.provide and some using goog.module, and uses a couple Closure Library utility packages as well. It's now able to mix and match true-native ES6 modules with Closure packages in both raw source and the Closure Compiler-bundled version.

(You can't run Closure Compiler's import "goog:my.package" syntax in web browsers, of course.)

The main thing I had to change in Closure Library is how goog.require() scripts are loaded. Native ES6 modules are loaded asynchronously as if they had the 'defer' attribute, but the standard good.require() implementation uses document.write so must run in a synchronous script to ensure everything's loaded in the right order. So I made an ES6 module that replaces Closure's default script importer with one that loads them asynchronously. It still ensures they're loaded in the correct order, with the one caveat that they will NOT run before whatever native ES6 module loaded them.

This means you can't immediately reference a goog.require()'d package in the root scope of an ES6 module, so this won't work:

goog.require("my.View");

export default class PageView extends my.View {...}
// ReferenceError: Can't find variable: my


But I made a callback function to help:

import whenScriptsLoaded from "./devLoader.js";
goog
.require("my.app");

// You only need to do this once, since this callback will run after all packages
// required by this module and all other modules it imports have finished loading.
whenScriptsLoaded
(() => {
   
my.app.init();
});

The actual execution order when run as raw source in-browser:
  1. All ES6 modules (in imported order)
  2. All Closure Library packages/modules (in goog.require'd order)
  3. The whenScriptsLoaded() callback.
You also can't load an ES6 module into a goog.require()'d package at all (at least not in a format that works with both web browsers and Closure Compiler), since only files loaded as true ES6 modules can use import.

It seems to build OK in Closure Compiler too, including with advanced compilation turned on. I did have to do some refactoring to get type annotations working right. One difference is Closure Compiler has to actually import an ES6 module in order to use it in a type annotation, but it can't handle circular dependencies. So if Class A imports Class B, you can't then have Class B annotate something with /** @type {./A} */ -- because now Class B will have an implied circular import of Class A. Instead, you need to define an interface class in a third file, have Class A @implement InterfaceA, and then Class B use @type {InterfaceA} in its annotations.

Here's the experimental loader module I wrote. Import this into your ES6 module before you start goog.require()ing stuff.

Richard Connamacher

unread,
Aug 26, 2017, 1:28:36 AM8/26/17
to Closure Library Discuss
 (By "dozens" of source files I really mean almost 200.)

John Plaisted

unread,
Aug 29, 2017, 5:27:01 PM8/29/17
to Closure Library Discuss
Hey,

Thanks for posting! This has been on our radar and we've been working on it internally. And it is a priority with module support coming with Chrome 61. But yes the Closure Library today does not support run-time loading of ES6 modules.

You've got the general gist of it. At a high level we're going to support goog.require(<relative path>) to require ES6 and Closure modules. And for a bootstrap mechanism we need something that is asynchronous like your whenScriptsLoaded method.

It's possible to goog.require by path for ES6/Closure modules so long as ES6/Closure files are executed in the correct order and ES6 exports are registered with Closure. This example polyfill of the proposed import() function shows how to use a helper ES6 module to load and store a reference to another module's exports.

Stay tuned for official support,

John

Richard Connamacher

unread,
Aug 30, 2017, 9:25:55 PM8/30/17
to Closure Library Discuss
Cool, I'd love to test it out once it's ready.
Reply all
Reply to author
Forward
0 new messages