ES6 Modules: Getting Started Gotchas
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