What is ESM, Why Is it Important?
I would like to share my experience when I upgraded my TypeScript based Firebase backend code where chai
or mocha
stopped working any more. This was because the Node.js and its ecosystem have been moving toward using EMS (ECMAScript Modules) system, and I had to come up to speed and migrate my project using ESM.
Before ESM, we relied on the CommonJS module system and its require()
function. However, many TypeScript users, myself included, are accustomed to using import
statements for more structured module management. ESM brings this standardized syntax and the ability to import specific members from modules to JavaScript, enhancing code clarity and maintainability.
But, Why ESM Now?
But why is ESM becoming so important now? While there are countless articles detailing its advantages, this blog post cuts to the chase and provides a clear guide to migrating your projects. Simply put, ESM solves many shortcomings, including more efficient module loading, better code organization, and improved maintainability through features like tree-shaking. So, let’s dive in and explore how you can seamlessly transition your TypeScript based Node.js projects to leverage the power of ESM.
Summary of Steps
- Update
package.json
:- Add
"type": "module"
to indicate that your project uses ESM. - Set
"module": "node20"
(or newer) to emit ESM.
- Add
- Update
tsconfig.json
:- Set
module
toESNext
andtarget
toES2020
or later.
- Set
- Change export syntax for firebase function endpoints:
- Use
export const functionName = ...
for exporting functions.
- Use
- Add “.js” to local imports:
- Always include the file extension when importing local modules.
- Upgrade dependencies:
- Upgrade mocha and chai to versions that support ESM.
- Address potential
require
issues:- Handle dependencies that still use
require
appropriately.
- Handle dependencies that still use
I will be adding more details of each step above in upcoming weeks. Please come back.
Updating package.json
I have added "type": "module" in my package.json
. Also it will be a good idea to specify node.js engine type. I am using version 20. By the time you read this, this may have moved up.
{
"name": "my-backend",
"version": "1.1.1",
"description": "My Firebase Functions Example",
"type": "module",
"main": "index.js",
"engines": {
"node": "20"
},
Updating tsconfig.json
The important one here is the module
of ESnext
and target
of ES2020
.
{
"compilerOptions": {
"noImplicitReturns": true,
"noUnusedLocals": true,
"strict": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"module": "ESNext",
"moduleResolution": "node",
"sourceMap": true,
"target": "ES2020",
"noEmit": false
},
"compileOnSave": true,
"include": [
"./src/**/*.ts"
],
"exclude": ["node_modules"]
}
Change Export Syntax for Firebase Function Endpoints
Your function endpoints should now be specified with export const
instead of exports.my_function_name
export const ping_me = onRequest(
{
cors,
timeoutSeconds: instanceTimeoutSecConfig,
},
async (request: express.Request, resp: express.Response): Promise<void> => {
const doing = doingMain.method("ping3")
doing.info(`${request}`)
resp.status(200).json({ "message": "pong" });
}
)
Add “.js” to Local Imports
This is where AI assistants and even other “how to” article missed, but this is important. Also there are ways to import .ts
by manipulating tsconfig.json
` but I advise you not to do that because there are additional issues while deploying your functions to the cloud.
Very important to note that I did not have to add the .js
extension on modules that are in node_modules
, meaning that anything you added with npm
or yarn
. You only need (and have to) add .js
to your own sources that are not part of npm modules.
import * as nodemailer from "nodemailer";
import {ConfigService} from "./ConfigService.js";
import {Doing} from "./Doing.js";
import Mail from "nodemailer/lib/mailer";
import SMTPPool from "nodemailer/lib/smtp-pool";
Upgrade Dependencies
Now I can upgrade mocha
and chai
to the latest. You may need to research other dependencies.
I highly recommend you move up to yarn
. It downloads assets via multiple threads, and rest of the usage is very similar to npm
so there is not much learning curve there. Also yarn
is a bit smarter on run
. In most cases you can yarn something
instead of npm run something
.
yarn add mocha@latest
yarn add chai@latest
Address Potential Require Issues:
You may still have require()
inline with your code. Be sure to global search for require(
in your code base and change to import
. There are plenty of examples you can search if you get into an issue or use the LLM to fix them for you.
About Me
Bridging the gap between clinical systems and healthcare stakeholders. I develop cutting-edge interfaces, leveraging Graph databases to unlock insights from complex patient data and using Firebase/Flutter to rapidly deploy desktop and mobile clinical apps. Connecting clinicians, patients, and AI to improve healthcare delivery. Let’s connect! Contact Us