(CC BY 2.0)

TypeScript, ES Modules & Micheal Jackson

Ant Stanley
Feb 5 · 3 min read

ECMAScript Modules were introduced with the ES2015 specification for ECMAScript and is the modern module system for JavaScript.

Node’s module system, CommonJS, predates ES Modules, and was created out of the need for module system in Node. In September 2017, Node.js added support for ES modules, alongside CommonJS modules, with the release of Node.js 8.5, using the .mjs (Micheal Jackson Script) extension to differentiate ES modules from CommonJS modules.

TypeScript has had some ES module support for a while, and at a high level appears it would be simple to update your tsconfig.json to target ES2015 as the output syntax if you were using ES module syntax in your TypeScript.

The reality is slightly different as TypeScript does not transpile to .mjs files, and due to the way it handles ES modules there are some limitations on the syntax you use. Essentially TypeScript treats ES modules the same as CommonJS modules, even though they are different

Below is a brief list of what to watch out for if you want to export .mjs files that will run in Node.js …

1.) TypeScript cannot transpile to .mjs files.

Workaround: Transpile to ES2015 target, and use a post compile script to rename the files to .mjs from .js

The command below works in any POSIX based OS using Bash that supports the find and mv commands.

find ./dist/ -name "*.js" -exec bash -c 'mv "$1" "${1%.js}".mjs' - '{}' \;

Github Issue on .mjs support:

2.) TypeScript cannot import .mjs files.

Workaround: There is none. Unfortunately you can’t do it today. You can import .ts files written with ES2015 syntax though.

3.) TypeScript allows the use of CommonJS require() and ES module import syntax in the same module. Node.js does not.

Workaround: Ensure any TypeScript you write only uses ES module syntax. You should rewrite any instances of require() syntax to use import() syntax.

4.) ES modules in Node.js doesn’t support __dirname or __filename statements. The import.meta.url syntax is intended to provide similar functionality to __filename when using ES modules in Node.js as the alternative. TypeScript supports import.meta syntax, but allows __filename and __dirname to be used in files that are exported to ES modules

Workaround: Ensure you use import.meta.url syntax instead of __filename and __dirname

Github PR to introduce support for import.meta :

5.) ES modules are read-only. TypeScript allows you to mutate ES modules, which will create an error

Workaround: Ensure you don’t attempt to change an ES module after it’s imported.

This post focuses mainly on where TypeScript allows you to ship code that won’t run in Node.js. I recommend reading my other blog post on tips for using ES modules,, if you want to have a greater understanding of how to write ES modules if you’re used to CommonJS.

The biggest issue with transpiling TypeScript to ES modules is that TypeScript treats ES module syntax as another way of doing the same thing as CommonJS, when fundamentally under the hood CommonJS and ES modules are different.

TypeScript allows mixing and matching of CommonJS and ES module syntax which will always fail when running in Node.js because they are distinct module systems, and Node uses the file extension to determine which module system to use before parsing the file, so will only ever load one or the other module system.

This is a great deep dive to read on ES modules, including how they differ to CommonJS under the hood.

Hope this is useful!

Ant Stanley

Ant Stanley

Written by

Figuring it out…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade