Bundling functions using ESBuild compiles to commonJS?

I have an Astro project where I would like to include a serverless function to send a few emails. I have done something similar in an old Gatsby project and expected this to be rather straight-forward.

However, when testing things out locally with the latest version of netlify CLI my function is compiled to commonJS - and I simply cannot figure out how to avoid this?
I use a few packages that do not support commonJS - which makes perfectly sense in 2024…

I see that an issue was created on github with the same problem: Netlify functions not working with ESM…

In my netlify.toml file I have:

[functions]
  node_bundler = "esbuild"
  directory = "functions"
  included_files = ["functions/*.handlebars"]
  external_node_modules = [
    "nodemailer",
    "nodemailer-express-handlebars",
    "zod"
  ]

and in my package.json I have “type”: “module”.

I have also tried moving my function into an *.mjs file instead of *.js - but this file is also compiled to commonJS???

Is it something trivial I am missing here?

Do you have a reproduction to share?

1 Like

Thanks for the quick reply.

I will create this!

@hrishikesh Please inspect this project for a reproduction of the issue:

Just start up the project, fill out the contact form on the index page and submit it - and you will get the error directly in the browser.

You seem to be using Netlify Functions v1 which, I believe are transpiled into CJS. You should try to migrate to Functions v2 where ESM is preserved as far as I recall.

@hrishikesh - thank for you comment - I appreciate the help :+1:

Just to make sure we are on the same page: Functions v2 - as in edge functions?

So, I need to use the API?:

import type { Config, Context } from "@netlify/edge-functions";

export default async (request: Request, context: Context) => {

// some code ending with a response

Not exactly. Functions v2 means still using Functions, but the syntax is similar to that of Edge Functions. They’re (almost) drop-in replacements in terms of syntax. So you would be using the same thing (but replace the import with @netlify/functions for the minor things that might be different).

I just tried re-writing the function (seems very straight-forward). However, when netlify CLI compiles it locally it places the function in .netlify/functions-serve/send-email

in this folder in includes a package.json with the following content:
{“type”:“commonjs”}

And as a result my function is compiled to commonJS and my packages fail when trying to import using require.

I have updated the minimal reproduction at:
https://github.com/LarsEjaas/netlify-function-commonjs-issue-minimal-reproduction/tree/main to reflect the changes.

You’re still exporting handler, so that makes you use Functions v1. You should export a default function to use v2.

Thanks a lot for trying to help @hrishikesh :+1:

I really just needed to verify that I did everything correctly. I got it working now.

  • use *.mts file format (probably not really important - used *.ts before).
  • use export default when exporting the function (as you mentioned).
    Last part (took me a while to debug):
    Do not import types from a namespace, this will make everything explode when code is compiled.

Somehow this line made everything break apart:
import { type Options as MailOptions } from 'nodemailer/lib/smtp-transport';

This is an interface on a namespace. Works fine normally in an ES environment - but makes everything explode when compiled.
I changed to this syntax:

//@ts-ignore
import type { Options as MailOptions } from '@types/nodemailer/lib/smtp-transport';

And now my function actually works!

Thanks for taking the time to help - I really appreciate it!