Export and Import

Having a complex application makes developers scroll through hundreds and thousands of lines of code, which makes it harder for them to debug or even understand the app.

Luckily, JavaScript allows overcoming such difficulties. Having import and export helps one out of the problems above.

There are several syntax options for export and import directives. In the chapter Modules, we have already considered their simple use. Now, we are going to explore more complex examples.

Export Before Declarations

It is possible to label any declaration just as exported by putting export before it: no matter it’s a function, variable,or a class.

All the exports in the example below are valid:

// export an array
export let week = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2020;
// export a class
export class Site {
  constructor(siteName) {
    this.siteName = siteName;
  }
}

Please, take into consideration that export before a class or a function doesn’t transform it into a function expression. It remains a function declaration, although exported.

It is not recommended to use semicolons after function and class declarations. For that reason, you don’t need a semicolon at the end of export class and export function .

Here is a case in point:

export function sayWelcome(site) {
  console.log(`Welcome to ${site}!`);
} // no ; at the end

Export Apart from Declarations

It is possible to place export independently.

The first step should be declaring and then exporting.

Let’s demonstrate it in the following example:

//  welcome.js
function sayWelcome(site) {
  console.log(`Welcome to ${site}!`);
}

function saySoon() {
  console.log(`See you later on our ${site}!`);
}

export {
  sayWelcome,
  saySoon
}; // a list of exported variables

Import*

For importing, it is usually necessary to put a list of what to import in curly braces, like here:

// main.js
import {
  sayWelcome,<
  saySoon
} from '.welcome.js';

sayWelcome('W3Docs'); // Welcome to W3Docs!
saySoon('W3Docs'); // See you later on our W3Docs!

If you intend to import a lot of things, you can do that as an object using import * as <obj>.

Import “as”

You can use as for importing under various names.

Let’s see an example where we import sayWelcome into the local variable welcome, and import seeSoon as soon, for brevity.

Here is how it looks like:

//  main.js
import {
  sayWelcome as welcome,
  saySoon as soon
} from './welcome.js';
welcome('W3Docs'); // Welcome to W3Docs!
soon('W3Docs'); // See you later on our W3Docs!

Export “as”

For export, as the syntax, similar to the one used above, is generally used.

In another case, let’s export the welcome and soon functions:

//  welcome.js
...
export {
  sayWelcome as welcome, seeSoon as soon
};

//  main.js
import * as say from './welcome.js';

say.welcome('W3Docs'); // Welcome to W3Docs!
say.soon('W3Docs'); // See you later on our W3Docs!

So, now they become official names for outsiders to be applied for imports.

Export Default

In practice, there exist two types of modules.

  1. Modules, containing a library, pack of functions ( for example, welcome.js).
  2. Modules, declaring a single entity ( for example, site.js exports only class Site).

The second approach is preferred most of the time.

As a rule, that requires many files. The reason is that everything needs its own module. Actually, it becomes easier to navigate the code when files are structured into folders and are well-named.

The modules have unique export default syntax for making the “one thing per module” appear better.

Let’s consider an example where export default is put before the entity:

// site.js
export default class Site { // just add "default"
  constructor(siteName) {
    this.siteName = siteName;
  }
}

There can be only a single export default per file.

The next step is importing it without the curly braces, like this:

// main.js
import Site from './site.js'; // not {Site}, just Site
new Site('W3Docs');

Actually, it looks much nicer without using curly braces.

But, forgetting about the curly braces at all is a common mistake. So, it would be best if you remembered that import requires curly braces for named exports and doesn’t need them for the default ones.

Take a look at this case:

Named export Default export
export class Site { … } export default class Site { … }
import { Site } from... import Site from...

Technically, it is possible to have both named and default exports in a single module, but usually, developers avoid mixing them.

The examples of perfectly valid default exports look like this:

export default class { // no class name
  constructor() { ...
  }
}

export default function (site) { // no function name
  console.log(`Welcome to ${site}!`);
}

// export a single value, without making a variable
export default ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

It is fine not to give a name, as export default is one for each file, so import without curly braces knows exactly what to import. An export like that will lead to an error without default, like here:

export class { // Error! ,non-default export
  constructor() {}
}

The “default” Name

There can be situations when the default keyword is used for referencing the default export.

Here is an example of exporting a function distinctly from its definition:

function welcome(site) {
  console.log(`Welcome to ${site}!`);
}

// same as if we added "export default" before the function
export {  welcome as default };

Let’s consider another example, in which a module site.js exports a single main “default” thing along with several named ones:

// site.js
export default class Site {
  constructor(name) {
    this.name = name;
  }
}

export function welcome(site) {
  console.log(`Welcome to ${site}!`);
}

In the example below, the default export with a named export is imported:

//  main.js
import {
  default as Site,
  welcome
} from './site.js';

new Site('W3Docs');

Finally, if you try to import everything * as an object, the default property will be exactly the default export, like here:

//  main.js
import * as site from './site.js';

let Site = site.default; // the default export
new Site('W3Docs');

Disadvantages of Default Exports

Default exports are straightforward: they name whatever they import.

Moreover, they enforce a developer to use the right name to import, like this:

import {
  Site
} from './site.js';
// import {MySite} won't work, the name must be {Site}

On the contrary, for a default export, the name can be chosen when importing, as follows:

import Site from './site.js'; // works
import MySite from './site.js'; // works too
// could be import anything, and it'll still work

It allows team members to use different names for importing the same things. That’s not a good practice.

For avoiding that and keeping the code consistent, you need to follow a rule, according to which imported variables should match with the file names, like here:

import Site from './site.js';
import func from '/path/to/func.js';
...

Still, in some circles, it’s considered a serious disadvantage of default exports. That’s why using named exports is preferable. Even in case only one thing is exported, it’s still done under an exact name without default.

There is another way of doing it easier: it’s re-export.

Re-export

Re-export allows you to import things and instantly export them ( also, under a different name).

The syntax of re-export looks like this:

export ... from ...

To make it more obvious, let’s see an example of the re-export:

export {
  welcome
}
from './welcome.js'; // re-export welcome

export {
  default as Site
}
from './site.js'; // re-export default

Re-exporting the Default Export

The default export requires independent handling while re-exporting. Imagine, you have site.js and wish to re-export class Site from it, like this:

//  site.js
export default class Site {
  // ...
}

Consider two important facts:

  1. The export Site from './site.js' is a syntax error.
  2. export * from './site.js'can only re-export named exports, ignoring the default one at the same time.

For re-exporting both the default and named export, two statements are required. The case of re-exporting the default and named export is visualized in the example below:

export * from './site.js'; // to re-export named exports
export {
  default
}
from './site.js'; // to re-export the default export

To sum up, we can state that export and import are handy means that can make any programmer’s job much more manageable. They allow organizing and decoupling the code and lead to applications that are much simpler to maintain, understand, or debug.




Do you find this helpful?

Related articles