Export and Import

Export and import directives are very versatile.

In the previous chapter we saw a simple use, now let’s explore more examples.

Export before declarations

We can label any declaration as exported by placing export before it, be it a variable, function or a class.

For instance, here all exports are valid:

// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// export a class
export class User {
  constructor(name) {
    this.name = name;
  }
}
No semicolons after export class/function

Please note that export before a class or a function does not make it a function expression. It’s still a function declaration, albeit exported.

Most JavaScript style guides recommend semicolons after statements, but not after function and class declarations.

That’s why there should be no semicolons at the end of export class and export function.

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}  // no ; at the end

Export apart from declarations

Also, we can put export separately.

Here we first declare, and then export:

// 📁 say.js
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // a list of exported variables

…Or, technically we could put export above functions as well.

Import *

Usually, we put a list of what to import into import {...}, like this:

// 📁 main.js
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

But if the list is long, we can import everything as an object using import * as <obj>, for instance:

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

say.sayHi('John');
say.sayBye('John');

At first sight, “import everything” seems such a cool thing, short to write, why should we ever explicitly list what we need to import?

Well, there are few reasons.

  1. Modern build tools (webpack and others) bundle modules together and optimize them to speedup loading and remove unused stuff.

    Let’s say, we added a 3rd-party library lib.js to our project with many functions:

    // 📁 lib.js
    export function sayHi() { ... }
    export function sayBye() { ... }
    export function becomeSilent() { ... }

    Now if we only use one of lib.js functions in our project:

    // 📁 main.js
    import {sayHi} from './lib.js';

    …Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called “tree-shaking”.

  2. Explicitly listing what to import gives shorter names: sayHi() instead of lib.sayHi().

  3. Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier.

Import “as”

We can also use as to import under different names.

For instance, let’s import sayHi into the local variable hi for brevity, and same for sayBye:

// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

Export “as”

The similar syntax exists for export.

Let’s export functions as hi and bye:

// 📁 say.js
...
export {sayHi as hi, sayBye as bye};

Now hi and bye are official names for outsiders:

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

say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

export default

So far, we’ve seen how to import/export multiple things, optionally “as” other names.

In practice, modules contain either:

  • A library, pack of functions, like lib.js.
  • Or an entity, like class User is described in user.js, the whole module has only this class.

Mostly, the second approach is preferred, so that every “thing” resides in its own module.

Naturally, that requires a lot of files, as everything wants its own module, but that’s not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders.

Modules provide special export default syntax to make “one thing per module” way look better.

It requires following export and import statements:

  1. Put export default before the “main export” of the module.
  2. Call import without curly braces.

For instance, here user.js exports class User:

// 📁 user.js
export default class User { // just add "default"
  constructor(name) {
    this.name = name;
  }
}

…And main.js imports it:

// 📁 main.js
import User from './user.js'; // not {User}, just User

new User('John');

Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, import needs curly braces for named imports and doesn’t need them for the default one.

Named export Default export
export class User {...} export default class User {...}
import {User} from ... import User from ...

Naturally, there may be only one “default” export per file.

We may have both default and named exports in a single module, but in practice people usually don’t mix them. A module has either named exports or the default one.

Another thing to note is that named exports must (naturally) have a name, while export default may be anonymous.

For instance, these are all perfectly valid default exports:

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

export default function(user) { // no function name
  alert(`Hello, ${user}!`);
}

// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

That’s fine, because export default is only one per file. Contrary to that, omitting a name for named imports would be an error:

export class { // Error! (non-default export needs a name)
  constructor() {}
}

“Default” alias

The “default” keyword is used as an “alias” for the default export, for standalone exports and other scenarios when we need to reference it.

For example, if we already have a function declared, that’s how to export default it (separately from the definition):

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

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

Or, let’s say a module user.js exports one main “default” thing and a few named ones (rarely the case, but happens):

// 📁 user.js
export default class User {
  constructor(name) {
    this.name = name;
  }
}

export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

Here’s how to import the default export along with a named one:

// 📁 main.js
import {default as User, sayHi} from './user.js';

new User('John');

Or, if we consider importing * as an object, then the default property is exactly the default export:

// 📁 main.js
import * as user from './user.js';

let User = user.default;
new User('John');

Should I use default exports?

One should be careful about using default exports, because they are more difficult to maintain.

Named exports are explicit. They exactly name what they import, so we have that information from them, that’s a good thing.

Also, named exports enforce us to use exactly the right name to import:

import {User} from './user.js';
// import {MyUser} won't work, the name must be {User}

For default exports, we always choose the name when importing:

import User from './user.js'; // works
import MyUser from './user.js'; // works too
// could be import Anything..., and it'll be work

So, there’s a little bit more freedom that can be abused, so that team members may use different names for the same thing.

Usually, to avoid that and keep the code consistent, there’s a rule that imported variables should correspond to file names, e.g:

import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...

Another solution would be to use named exports everywhere. Even if only a single thing is exported, it’s still exported under a name, without default.

That also makes re-export (see below) a little bit easier.

Re-export

“Re-export” syntax export ... from ... allows to import things and immediately export them (possibly under another name), like this:

export {sayHi} from './say.js';
export {default as User} from './user.js';

What’s the point, why that’s needed? Let’s see a practical use case.

Imagine, we’re writing a “package”: a folder with a lot of modules, mostly needed internally, with some of the functionality exported outside (tools like NPM allow to publish and distribute packages, but here it doesn’t matter).

A directory structure could be like this:

auth/
  index.js
  user.js
  helpers.js
  tests/
    login.js
  providers/
    github.js
    facebook.js
    ...

We’d like to expose the package functionality via a single entry point, the “main file” auth/index.js, to be used like this:

import {login, logout} from 'auth/index.js'

The idea is that outsiders, developers who use our package, should not meddle with its internal structure. They should not search for files inside our package folder. We export only what’s necessary in auth/index.js and keep the rest hidden from prying eyes.

Now, as the actual exported functionality is scattered among the package, we can gather and “re-export” it in auth/index.js:

// 📁 auth/index.js
import {login, logout} from './helpers.js';
export {login, logout};

import User from './user.js';
export {User};

import Github from './providers/github.js';
export {Github};
...

“Re-exporting” is just a shorter notation for that:

// 📁 auth/index.js
export {login, logout} from './helpers.js';
// or, to re-export all helpers, we could use:
// export * from './helpers.js';

export {default as User} from './user.js';

export {default as Github} from './providers/github.js';
...
Re-exporting default is tricky

Please note: export User from './user.js' won’t work. It’s actually a syntax error. To re-export the default export, we must mention it explicitly {default as ...}, like in the example above.

Also, there’s another oddity: export * from './user.js' re-exports only named exports, excluding the default one. Once again, we need to mention it explicitly.

For instance, to re-export everything, two statements will be necessary:

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

The default should be mentioned explicitly only when re-exporting: import * as obj works fine. It imports the default export as obj.default. So there’s a slight asymmetry between import and export constructs here.

Summary

There are following types of export:

  • Before declaration:
    • export [default] class/function/variable ...
  • Standalone:
    • export {x [as y], ...}.
  • Re-export:
    • export {x [as y], ...} from "mod"
    • export * from "mod" (doesn’t re-export default).
    • export {default [as y]} from "mod" (re-export default).

Import:

  • Named exports from module:
    • import {x [as y], ...} from "mod"
  • Default export:
    • import x from "mod"
    • import {default as x} from "mod"
  • Everything:
    • import * as obj from "mod"
  • Import the module (it runs), but do not assign it to a variable:
    • import "mod"

We can put import/export statements at the top or at the bottom of a script, that doesn’t matter.

So this is technically fine:

sayHi();

// ...

import {sayHi} from './say.js'; // import at the end of the script

In practice imports are usually at the start of the file, but that’s only for better convenience.

Please note that import/export statements don’t work if inside {...}.

A conditional import, like this, won’t work:

if (something) {
  import {sayHi} from "./say.js"; // Error: import must be at top level
}

…But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it’s really needed?

We’ll see dynamic imports in the next chapter.

Eğitim haritası

Yorumlar

yorum yapmadan önce lütfen okuyun...
  • Eğer geliştirme ile alakalı bir öneriniz var ise yorum yerine github konusu gönderiniz.
  • Eğer makalede bir yeri anlamadıysanız lütfen belirtiniz.
  • Koda birkaç satır eklemek için <code> kullanınız, birkaç satır eklemek için ise <pre> kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)