Upgrade Your Firebase Project with ESM: An Easy TypeScript Tutorial

Posted by:

|

On:

|

, ,

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.
  • Update tsconfig.json:
    • Set module to ESNext and target to ES2020 or later.
  • Change export syntax for firebase function endpoints:
    • Use export const functionName = ... for exporting functions.
  • 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.

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