Just recently I moved minimal-analytics, a Node.js and Preact project to ES modules.
And it was quite pleasant and straightforward.
It didn’t require
much work to migrate the project to ES modules 🤭.
Why?
- wanted to learn more about ES modules
- it’s the (foreseeable) future/standard of module resolution in Node.js and the browser
- more and more people and dependencies are using ES modules
- perfect excuse to revisit some parts of the project
- why not?
How did it go?
I honestly had some troubles with two things:
Namely:
path.resolve
occurrences with__dirname
caused me some headaches- I had to dig deeper to simulate the
require.main === module
check
Ah, and before I forget, use node 16.16.0 (lts) for the best experience.
And also, set "type": "module"
in your project’s package.json
to make it work.
Check out the commit for the migration if you want to see the code.
path.resolve
with __dirname
I had some trouble with this, since in a few places I needed to resolve for relative filepaths.
The CommonJS variables __dirname
and __filename
are not available in ES modules.
But the usage can be replicated with import.meta.url
, the native URL
module and pathname
property.
E.g.
new URL(import.meta.url).pathname
is essentially the same as __filename
in the CommonJS environment.
And similarly, new URL('.', import.meta.url).pathname
can be compared to __dirname
in CommonJS.
Keep in mind, this is not the only way to simulate __dirname and __filename
Under an experimental flag, you can resolve modules relatively with import.meta.resolve
(as far as I understood)
See this for more information.
require.main === module
This is oftentimes used to determine if the main script was run directly from the shell, or required as a module from another script.
I am using this technique to start the server when the script was invoked from the shell.
e.g.
#!/usr/bin/env node
...
if (require.main === module) {
startServer()
}
...
To replicate this behaviour, I found multiple ways to do it:
# option 1
if (import.meta.url === `file://${process.argv[1]}`) { ... }
# option 2
if (new URL(import.meta.url).pathname === process.argv[1]) { ... }
# option 3
import url from 'url'
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { ... }
# option 4
// TC39 proposal for import.meta.main
# option 5
# See https://github.com/tschaub/es-main
import esMain from 'es-main';
if (esMain(import.meta)) { ... }
Well, as always, in JavaScript, there are multiple ways to achieve the same thing. Some cleaner, some hackier.
Conclusion
Apart from the usual ramblings about JavaScript, Node.js, standards and headaches, it was a fun experience.
Learned a ton about ES modules, and how to use them in Node.js.
More reading about ESM
Here some of the best resources I found on this topic, I highly suggest to read them