Advanced exports and imports

Renaming imports and exports

Suppose you have two modules, moduleA.js and moduleB.js, and both modules export a variable called someVar. The browser will throw an error if you import both variables with the same name from the two different modules into the same document.


import { someVar, someConst } from "./modules/moduleA.js";
import { someVar, someFunc } from "./modules/moduleB.js"; // SyntaxError: redeclaration of import someVar

To solve this, you can rename the imports so that they are unique. You can do this by using keyword as inside the import or export statement's curly brackets.


import { someVar as someVarA, someConst } from "./modules/moduleA.js";
import { someVar as someVarB, someFunc } from "./modules/moduleB.js";
Or:

// moduleA.js
export { someVar as someVarA, someConst };

// moduleB.js
export { someVar as someVarB, someFunc };

import { someVarA, someConst} from "./modules/moduleA.js";
import { someVarB, someFunc } from "./modules/moduleB.js";

The second option, using as in the export statement, is in most cases rather senseless or useless. In many cases modules are imported from third party libraries that you don't have any control over. And if you do have access to the module's source code, it may make more sense to simply change the name in the code itself.

Another option for avoiding name conflicts is to use classes as modules. Export the class, import the class and then use its instance fields and methods.

Module objects

If you want to import all exports of a module, you can import everything as an object using an asterisk (*) and keyword as. This also provides an alternative solution to the problem of same name imports.


<script type="module">
  import * as moduleA from "./modules/moduleA.js";
  import * as moduleB from "./modules/moduleB.js";

  console.log(moduleA.someVar); // logs: "someValueA"
  console.log(moduleB.someVar); // logs: "someValueB"
</script>

Note that moduleA and moduleB are objects with the module's exports as their properties.

Default exports

So far we used named exports: the name of the exported function, const, etc. is the same name that is used to import this feature. We can set one feature as the default export. In this case the feature's name is not used. In a module only one feature can be the default export.


// getPI.js
export default function() { return Math.PI };

<script type="module">
  import getPI from "./modules/getPI.js";
  console.log(getPI()); // logs: 3.141592653589793
</script>

Note that no curly brackets ({}) are used in both the export and import statement.

The above is equivalent to:


<script type="module">
  import { default as getPI } from "./modules/getPI.js";
  console.log(getPI()); // logs: 3.141592653589793  
</script>

Or:


// getPI.js
function getPI() { return Math.PI };
export { getPI as default };

<script type="module">
  import getPI from "./modules/getPI.js";
  console.log(getPI()); // logs: 3.141592653589793
</script>

Importing a default export and named exports can also be combined in one import statement:


// getPI.js
export const pi = Math.PI;
export default function() { return Math.PI };

<script type="module">
  import getPI, { pi } from "./modules/getPI.js";
  console.log(pi); // logs: 3.141592653589793
  console.log(getPI()); // logs: 3.141592653589793
</script>

When using a module object to import, the module object gets a property default that equals the default export.


// getPI.js
export default function() { return Math.PI };

<script type="module">
  import * as getPI from "./modules/getPI.js";
  console.log(getPI.default()); // logs: 3.141592653589793
</script>

Aggregating modules

The export … from … syntax allows you to aggregate modules together (often called a "barrel module") while leaving the individual modules unchanged. This is often called "re-exporting".


// barrel.js  
export { someVar as someVarA, someConst } from "./moduleA.js";
export { someVar as someVarB, someFunc  } from "./moduleB.js";

<script type="module">
  import { someVarB, someConst } from "./modules/barrel.js";
  console.log(someVarB); // logs: "someValueB"
</script>

The export … from … syntax is similar to using a combination of import and export, except that, unlike with import/export, imported features are not available inside the barrel module.


// barrel.js
  
import { someVar as someVarA, someConst } from "./moduleA.js";
export { someVarA, someConst };

import { someVar as someVarB, someFunc } from "./moduleB.js";
export { someVarB, someFunc };

export const someConstDoubled = 2 * someConst;

// barrel.js  
export { someVar as someVarA, someConst } from "./moduleA.js";
export { someVar as someVarB, someFunc  } from "./moduleB.js";

export const someConstDoubled = 2 * someConst; // ReferenceError: someConst is not defined

The export * from "module" syntax re-exports all named exports. The default export is not re-exported.

Re-exporting the default export does require curly brackets!


export {default} from './getPI.js';

Or:


export {default as getPI} from './getPI.js';

Next will not work:


export getPI from './getPI.js'; // SyntaxError

Frameworks often include a module bundler, such as Webpack, that automatically bundles modules together, optimize them, and provide additional features such as importing HTML, CSS, and images.

Side effect import

import can also run an entire module's code for side effects only, without importing any feature into the JavaScript file. In this case, the import statement has no variable name and no from keyword.


import "./modules/myModule.js";