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.

  1. import './sideeffects.js';
  2. import * as goog from '../closure/goog/goog.js';
  3. import * as parent from '../parent.js';
  4. 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.

  1. import '../directory/file';
  1. 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.

  1. // Imports have the same path, but since it doesn't align it can be hard to see.
  2. import {short} from './long/path/to/a/file.js';
  3. 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.

  1. import * as fileOne from '../file-one.js';
  2. import * as fileTwo from '../file_two.js';
  3. import * as fileThree from '../filethree.js';
  1. import * as libString from './lib/string.js';
  2. import * as math from './math/math.js';
  3. 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 ??.

  1. import MyClass from '../my-class.js';
  2. import myFunction from '../my_function.js';
  3. 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.

  1. import * as bigAnimals from './biganimals.js';
  2. import * as domesticatedAnimals from './domesticatedanimals.js';
  3. new bigAnimals.Cat();
  4. 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.

  1. import {Cat as BigCat} from './biganimals.js';
  2. import {Cat as DomesticatedCat} from './domesticatedanimals.js';
  3. new BigCat();
  4. 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.

  1. // Do not use default exports:
  2. export default class Foo { ... } // BAD!
  1. // Use named exports:
  2. export class Foo { ... }
  1. // Alternate style named exports:
  2. class Foo { ... }
  3. 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.

  1. // container.js
  2. // Bad: Container is an exported class that has only static methods and fields.
  3. export class Container {
  4. /** @return {number} */
  5. static bar() {
  6. return 1;
  7. }
  8. }
  9. /** @const {number} */
  10. Container.FOO = 1;

Instead, export individual constants and functions:

  1. /** @return {number} */
  2. export function bar() {
  3. return 1;
  4. }
  5. 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.

  1. // Bad: both foo and mutateFoo are exported and mutated.
  2. export let /** number */ foo = 0;
  3. /**
  4. * Mutates foo.
  5. */
  6. export function mutateFoo() {
  7. ++foo;
  8. }
  9. /**
  10. * @param {function(number): number} newMutateFoo
  11. */
  12. export function setMutateFoo(newMutateFoo) {
  13. // Exported classes and functions can be mutated!
  14. mutateFoo = () => {
  15. foo = newMutateFoo(foo);
  16. };
  17. }
  1. // Good: Rather than export the mutable variables foo and mutateFoo directly,
  2. // instead make them module scoped and export a getter for foo and a wrapper for
  3. // mutateFooFunc.
  4. let /** number */ foo = 0;
  5. let /** function(number): number */ mutateFooFunc = foo => foo + 1;
  6. /** @return {number} */
  7. export function getFoo() {
  8. return foo;
  9. }
  10. export function mutateFoo() {
  11. foo = mutateFooFunc(foo);
  12. }
  13. /** @param {function(number): number} mutateFoo */
  14. export function setMutateFoo(mutateFoo) {
  15. mutateFooFunc = mutateFoo;
  16. }
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.

  1. export {specificName} from './other.js';
  2. 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.

  1. // a.js
  2. import './b.js';
  1. // b.js
  2. import './a.js';
  3. // `export from` can cause circular dependencies too!
  4. export {x} from './c.js';
  1. // c.js
  2. import './b.js';
  3. 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.

  1. import * as goog from '../closure/goog/goog.js';
  2. const name = goog.require('a.name');
  3. 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.

  1. import * as goog from '../closure/goog/goog.js';
  2. import * as anEsModule from './anEsModule.js';
  3. const GoogPromise = goog.require('goog.Promise');
  4. 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.required, goog.module.getd, 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.

  1. import * as goog from '../closure/goog.js';
  2. goog.declareModuleId('my.esm');
  3. export class Class {};