Resolving modules in JavaScript ESM

In JavaScript ES modules we can’t rely on CommonJS-style module resolution to add extensions to our import statements. Even Node.js has recently dropped automatic extension resolution in an effort to be more compatible with the ESM specification. Bare imports for Node modules is still possible, but as a side-effect dual-modules are no longer possible, except via deep-imports (import foo from 'bar/module.mjs';).

Obviously nobody wants to write these out manually, but this presents some new challenges for transpilers, which unfortunately no transpilers have yet tried to solve.

So what can we do about this?

Enter babel-plugin-esm-resolver, a Babel plugin I wrote to simplify this process without the need to manually rewrite your imports for CommonJS and ESM modules. It features 3 options for transforming your import statements.

  • source Source modules (import foo from './bar').
  • module Module main entry points (import foo from 'bar').
  • submodule Module submodules (import foo from 'bar/foo').

You can read the documentation on these options on the GitHub repo, but here’s an example Babel configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"presets": [
["@babel/preset-env", {
"modules": false,
"targets": {
"node": "current"
}
}]
],
"plugins": [
["esm-resolver", {
"source": {
"extensions": [
[[".ts", ".tsx", ".mjs", ".mjsx", ".js", ".jsx"], ".js"],
".json"
]
},
"module": {
"entry": [
{
"type": "package.json",
"field": "module",
"extensions": [
".mjs",
".js"
]
},
{
"type": "file",
"path": "./module",
"extensions": [
".mjs",
".js"
]
}
]
},
"submodule": {
"extensions": [
".mjs",
".js"
]
}
}]
]
}

This example works with:

  • TypeScript
  • JSX
  • MJS
  • JS
  • Resolves package.json module field for modules with .mjs or .js extension.
  • Resolves ./module deep-import entry file for modules with .mjs or .js extension.
  • Resolves module deep-imports to .mjs or .js files.

There we go, everything we need to be able to write and publish ESM modules TODAY!

The need to resolve Node module entry points in dual-packages to a deep-import to use them as ESM modules is kinda ugly. Hopefully we’ll see some cleaner options for that added to Node before the ESM loader is unflagged.

Comments