Migrating from goog.modules to ES modules¶
The primary difference between a goog.module
and an ES module is how imports/exports are defined. With goog.module
, we solely use goog.require
to import dependencies and use the exports
keyword to assign either a single default export or an object listing named exports. ES modules use the import
and export
keywords, which are part of the JavaScript specification. MDN’s JavaScript guide has an excellent reference on JavaScript modules with details on how these keywords work.
Transform Script¶
To convert goog.module
files to ES modules, we’ll use the moduletoes6.js
transform in opensphere-jscodeshift. This transform is based on the Closure Compiler’s Migrating from goog.modules to ES modules guide.
To run the transform, clone opensphere-jscodeshift
to your workspace and yarn upgrade
. Then run the transform on a goog.module
file:
$ cd /path/to/opensphere-jscodeshift
$ yarn run shift -t src/transforms/es6/moduletoes6.js <target file or directory>
The transform can be run against a single file or a directory. In the case of a directory, it will recursively run against any .js
files it finds under the base path. The transform will make the following changes:
- Replace the
goog.module
expression withgoog.declareModuleId
. - Replace
exports
with inlineexport
andexport default
declarations. - If
export default
is used, creates a<filename>.shim.js
file to maintain backwards compatibility withconst TheDefaultExport = goog.require(<module name>)
. - Remove the
goog.module.declareLegacyNamespace
statement, if present.
goog.declareModuleId¶
To make ES modules accessible to goog.module
or goog.provide
files, the Closure Library provides the goog.declareModuleId
function to declare a Closure module ID. This is analagous to goog.module
, and makes the ES module’s exports available via goog.require
or goog.module.get
.
Note
While goog.declareModuleId
is not necessary for source files, it does allow tests to load module exports via goog.module.get
. Unless tests are migrated to fully support loading modules, this statement should be included in every file.
Referencing ES Modules¶
The code used to reference an ES module varies based on the file type referencing the module. For these examples, assume we have the following ES modules in OpenSphere.
1 2 3 4 5 6 7 | goog.declareModuleId('os');
/**
* A named export.
* @type {number}
*/
export const MY_CONSTANT = 42;
|
1 2 3 4 5 6 | goog.declareModuleId('os.MyClass');
/**
* A default export.
*/
export default class MyClass {}
|
From an ES Module¶
To import these files from another ES module, use import
statements with a path to the file. Within OpenSphere, use a relative path. From external projects, use a Node path. The file’s .js
extension is not required in the path, and for index.js
files the path can end at the containing directory.
// From OpenSphere
import {MY_CONSTANT} from '../path/to/os';
import MyClass from '../path/to/os/myclass';
// From another project
import {MY_CONSTANT} from 'opensphere/src/os';
import MyClass from 'opensphere/src/os/myclass';
From a Goog Module¶
To import these files from a goog.module
, use goog.require
assignments.
const {MY_CONSTANT} = goog.require('os');
const {default: MyClass} = goog.require('os.MyClass');
Note
The default export from an ES module will be assigned to the default
property on the object returned by goog.require
. This is why the transform script creates a shim file, so existing references to the module do not need to be updated. You do not need to create this shim for new files, simply use the above syntax to reference the default export properly.
From Legacy Files/Tests¶
To import the modules from a legacy file using goog.provide
or from tests, use a combination of goog.require
to add the dependency and goog.module.get
to reference the module.
// Add a dependency on the module
goog.require('os');
goog.require('os.MyClass');
// Load the module
const {MY_CONSTANT} = goog.module.get('os');
const {default: MyClass} = goog.module.get('os.MyClass');
Legacy Namespaces¶
With goog.module
files, the goog.module.declareLegacyNamespace
function is called to export the module’s namespace to the global window
object. This function cannot be used in an ES module because it violates a core principle of modules, that they do not pollute the global scope. The transform script will remove this function from converted files, so prior to running the transform please ensure the module is no longer referenced using the global namespace.