ES6 Modules: Getting Started Gotchas

Matt LaGrandeur
4 min readJul 2, 2018

--

Hey y’all —just a quick note: this was written in 2018. Hopefully there are still some useful bits in here, but a lot of this functionality may have changed in the years since I wrote this. I haven’t checked! Happy coding 👍

As of Firefox’s release in May of 2018, all major browsers now support ES6 Modules natively! Never having used Modules extensively, I began some simple tests to get started… and immediately ran into issues. It seems like a lot of random literature out there around Modules is actually about how products like Webpack or Babel use Modules, and there are subtle and crucial differences compared to how it’s been implemented in native JavaScript.

Here are the issues I ran into, and how I solved them. Hopefully this article will save somebody a bunch of time :-)

Blocked by CORS

Firefox and Edge seem to be fine with loading Modules across local files, but Chrome throws this error:

Access to Script at ‘file:///C:/path/to/main.js’ from origin ‘null’ has been blocked by CORS policy: Invalid response. Origin ‘null’ is therefore not allowed access.

I get many large projects already have a hard requirement for servers to be running while developing (like React) but for many small projects I just use local files at first. Chrome seems to treat Modules with a higher level of security than Firefox or Edge, and throws this Cross Origin Resource Sharing error, saying that local files are not from the same origin.

The Fix for this is to run your project files off a server. I use the most basic one from NPM called http-server.

type = “module”

The great thing about Modules is that you can import and export functionality between files. At some point, your main HTML file will have to have at least one script tag that loads your main file. Your script tag in this case has to have a new type attribute equal to module, like this:

<script src="main.js" type="module"></script>

If you don’t include type="module", you will get an lot of different errors that are incredibly confusing:

Firefox

SyntaxError: import declarations may only appear at top level of a module

Chrome

Uncaught SyntaxError: Unexpected identifier

Edge

SCRIPT1086: SCRIPT1086: Module import or export statement unexpected here

The Firefox one is especially bad, because it describes another error that could be true, but isn’t really the issue here (it’s referring to the fact that, basically, you can’t put an export in an if/else clause, or in any loop).

Anyway, add type="module" to your script tag.

You must export a default

This is almost always left out of “getting started” tutorials that are comprised of little snippits of examples. All Modules must define a default export, and if they don’t, then these errors get thrown:

Firefox

SyntaxError: import not found: default

Chrome

Uncaught SyntaxError: The requested module ‘./modulename.js’ does not provide an export named ‘default’

Edge — actually doesn’t throw an error, but also fails to load the module :-\

It is good practice to think about what your default export will be, but in this case, it’s actually required to do so. You have to be careful how you export, though, as we’ll see in the next section…

Export the “right way”

For readability, I always do import and export statements at the very top of the file, it’s a nice overview and context setting for what comes below. export and import syntax allows for a few variations, but only some work in certain situations… mostly having to do with variable scope.

This, for example, gives you errors:

export default count;
export {ability};
let count = 10;function ability() { return 'hello!'; }

Firefox

ReferenceError: can’t access lexical declaration `count’ before initialization

Chrome

modulename.js:1 Uncaught ReferenceError: count is not defined
at modulename.js:1

Edge

SCRIPT5113: Use before declaration

There are two things to know here: Firstly, if your default export is a function, you can do this:

export default ability;
export {count};
let count = 10;function ability() { return 'hello!'; }

No problem — the issue only comes with let or const variables. Also, if you really want to export a variable by default at the top of your file, you can do this:

export {count as default};
export {ability};
let count = 10;function ability() { return 'hello!'; }

Import the “right way”

Imports must end with .js

Module type functionality has been available to early adopters for a while now through the use of transpilers. But the syntax for things like require('modulename') and ES6 Modules are slightly different. One up-front gotcha is that ES6 Modules must be full file names, otherwise they will throw errors:

import {count} from './modulename';

Firefox

Loading failed for the module with source “http://127.0.0.1:8080/modulename”.

Chrome

GET http://127.0.0.1:8080/start 404 (Not Found)

Edge — actually doesn’t throw an error, but also fails to load the module :-\

Simply including the .js suffix fixes this:

import {count} from './modulename.js';

Named imports get brackets, defaults don’t get brackets

This can be very tricky… for the most part when I export something, I import it to another module with the same name. This isn’t required, though. Let’s go back to our super basic example:

export {count as default};
export {ability};
let count = 10;function ability() { return 'hello!'; }

Now, in another file, I import stuff:

import ability from './modulename.js';

The import ability here does not have brackets around it, which means it will be assigned the default export. In this case the default export is actually the variable count equal to the number 10, as opposed to the function.

If we want to import a specific named thing, use curly braces:

import {ability} from './modulename.js';

This now looks through modulename.js's exports and searches for ability by name, and creates a local imported value with the same name.

There are many, many other variations on how to export and import things, I encourage you to read through MDN’s export and import pages. This little difference between brackets and default exports was confusing at first, though, so it’s a basic thing to keep in mind.

That’s it!

Like I said, hopefully that saves somebody some time. Hit me up with questions or comments: matt@mattlag.com

--

--