3.4 ES modules
3.4.1 Imports
Import statements must not be line wrapped and are therefore an exception to the80-column limit.
3.4.1.1 Import paths
ES module files must use the import
statement to import other ES modulefiles. Do not goog.require
another ES module.
import './sideeffects.js';
import * as goog from '../closure/goog/goog.js';
import * as parent from '../parent.js';
import {name} from './sibling.js';
3.4.1.1.1 File extensions in import paths
The .js
file extension is not optional in import paths and must always beincluded.
import '../directory/file';
import '../directory/file.js';
3.4.1.2 Importing the same file multiple times
Do not import the same file multiple times. This can make it hard to determinethe aggregate imports of a file.
// Imports have the same path, but since it doesn't align it can be hard to see.
import {short} from './long/path/to/a/file.js';
import {aLongNameThatBreaksAlignment} from './long/path/to/a/file.js';
3.4.1.3 Naming imports
3.4.1.3.1 Naming module imports
Module import names (import * as name
) are lowerCamelCase
names that arederived from the imported file name.
import * as fileOne from '../file-one.js';
import * as fileTwo from '../file_two.js';
import * as fileThree from '../filethree.js';
import * as libString from './lib/string.js';
import * as math from './math/math.js';
import * as vectorMath from './vector/math.js';
3.4.1.3.2 Naming default imports
Default import names are derived from the imported file name and follow therules in ??.
import MyClass from '../my-class.js';
import myFunction from '../my_function.js';
import SOME_CONSTANT from '../someconstant.js';
Note: In general this should not happen as default exports are banned by thisstyle guide, see ??. Default imports are only usedto import modules that do not conform to this style guide.
3.4.1.3.3 Naming named imports
In general symbols imported via the named import (import {name}
) should keepthe same name. Avoid aliasing imports (import {SomeThing as SomeOtherThing}
).Prefer fixing name collisions by using a module import (import *
) or renamingthe exports themselves.
import * as bigAnimals from './biganimals.js';
import * as domesticatedAnimals from './domesticatedanimals.js';
new bigAnimals.Cat();
new domesticatedAnimals.Cat();
If renaming a named import is needed then use components of the importedmodule's file name or path in the resulting alias.
import {Cat as BigCat} from './biganimals.js';
import {Cat as DomesticatedCat} from './domesticatedanimals.js';
new BigCat();
new DomesticatedCat();
3.4.2 Exports
Symbols are only exported if they are meant to be used outside the module.Non-exported module-local symbols are not declared @private
nor do their namesend with an underscore. There is no prescribed ordering for exported andmodule-local symbols.
3.4.2.1 Named vs default exports
Use named exports in all code. You can apply the export
keyword to adeclaration, or use the export {name};
syntax.
Do not use default exports. Importing modules must give a name to these values,which can lead to inconsistencies in naming across modules.
// Do not use default exports:
export default class Foo { ... } // BAD!
// Use named exports:
export class Foo { ... }
// Alternate style named exports:
class Foo { ... }
export {Foo};
3.4.2.2 Exporting static container classes and objects
Do not export container classes or objects with static methods or properties forthe sake of namespacing.
// container.js
// Bad: Container is an exported class that has only static methods and fields.
export class Container {
/** @return {number} */
static bar() {
return 1;
}
}
/** @const {number} */
Container.FOO = 1;
Instead, export individual constants and functions:
/** @return {number} */
export function bar() {
return 1;
}
export const /** number */ FOO = 1;
3.4.2.3 Mutability of exports
Exported variables must not be mutated outside of module initialization.
There are alternatives if mutation is needed, including exporting a constantreference to an object that has mutable fields or exporting accessor functions formutable data.
// Bad: both foo and mutateFoo are exported and mutated.
export let /** number */ foo = 0;
/**
* Mutates foo.
*/
export function mutateFoo() {
++foo;
}
/**
* @param {function(number): number} newMutateFoo
*/
export function setMutateFoo(newMutateFoo) {
// Exported classes and functions can be mutated!
mutateFoo = () => {
foo = newMutateFoo(foo);
};
}
// Good: Rather than export the mutable variables foo and mutateFoo directly,
// instead make them module scoped and export a getter for foo and a wrapper for
// mutateFooFunc.
let /** number */ foo = 0;
let /** function(number): number */ mutateFooFunc = foo => foo + 1;
/** @return {number} */
export function getFoo() {
return foo;
}
export function mutateFoo() {
foo = mutateFooFunc(foo);
}
/** @param {function(number): number} mutateFoo */
export function setMutateFoo(mutateFoo) {
mutateFooFunc = mutateFoo;
}
3.4.2.4 export from
export from
statements must not be line wrapped and are therefore anexception to the 80-column limit. This applies to both export from
flavors.
export {specificName} from './other.js';
export * from './another.js';
3.4.3 Circular Dependencies in ES modules
Do not create cycles between ES modules, even though the ECMAScriptspecification allows this. Note that it is possible to create cycles with boththe import
and export
statements.
// a.js
import './b.js';
// b.js
import './a.js';
// `export from` can cause circular dependencies too!
export {x} from './c.js';
// c.js
import './b.js';
export let x;
3.4.4 Interoperating with Closure
3.4.4.1 Referencing goog
To reference the Closure goog
namespace, import Closure's goog.js
.
import * as goog from '../closure/goog/goog.js';
const name = goog.require('a.name');
export const CONSTANT = name.compute();
goog.js
exports only a subset of properties from the global goog
that can beused in ES modules.
3.4.4.2 goog.require in ES modules
goog.require
in ES modules works as it does in goog.module
files. You canrequire any Closure namespace symbol (i.e., symbols created by goog.provide
orgoog.module
) and goog.require
will return the value.
import * as goog from '../closure/goog/goog.js';
import * as anEsModule from './anEsModule.js';
const GoogPromise = goog.require('goog.Promise');
const myNamespace = goog.require('my.namespace');
3.4.4.3 Declaring Closure Module IDs in ES modules
goog.declareModuleId
can be used within ES modules to declare agoog.module
-like module ID. This means that this module ID can begoog.require
d, goog.module.get
d, goog.forwardDeclare
'd, etc. as if it werea goog.module
that did not call goog.module.declareLegacyNamespace
. It doesnot create the module ID as a globally available JavaScript symbol.
A goog.require
(or goog.module.get
) for a module ID fromgoog.declareModuleId
will always return the module object (as if it wasimport *
'd). As a result, the argument to goog.declareModuleId
should alwaysend with a lowerCamelCaseName
.
Note: It is an error to call goog.module.declareLegacyNamespace
in an ESmodule, it can only be called from goog.module
files. There is no direct wayto associate a legacy
namespace with an ES module.
goog.declareModuleId
should only be used to upgrade Closure files to ESmodules in place, where named exports are used.
import * as goog from '../closure/goog.js';
goog.declareModuleId('my.esm');
export class Class {};