Lambda function ES module support?

I am trying to deploy an ESM function like this:

export const handler = async (event: any, context: any) => {
  return {
    statusCode: 200,
    body: 'Hello, World!'
  }
}

which, during build, compiles to javascript ES module:

export const handler = async (event, context) => {
    return {
        statusCode: 200,
        body: 'Hello, World!'
    };
};

Here is how my netlify.toml looks like:

[build]
  publish = "public"
  functions = "functions"
  command = "npm run build"
  environment = { NODE_VERSION = "15.5.0", AWS_LAMBDA_JS_RUNTIME = "15.5.0" }
[build.processing]
  skip_processing = true

When I call the function .netlify/functions/foo, an error occurs saying Unexpected token 'export':

{
  "errorType": "Runtime.UserCodeSyntaxError",
  "errorMessage": "SyntaxError: Unexpected token 'export'",
  "trace": [
    "Runtime.UserCodeSyntaxError: SyntaxError: Unexpected token 'export'",
    "    at _loadUserApp (/var/runtime/UserFunction.js:98:13)",
    "    at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
    "    at Object.<anonymous> (/var/runtime/index.js:43:30)",
    "    at Module._compile (internal/modules/cjs/loader.js:999:30)",
    "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)",
    "    at Module.load (internal/modules/cjs/loader.js:863:32)",
    "    at Function.Module._load (internal/modules/cjs/loader.js:708:14)",
    "    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)",
    "    at internal/main/run_main_module.js:17:47"
  ]
}

I tried renaming foo.js to foo.mjs, but it only results in No Functions were found.

It looks like that node.js does not consider foo.js an ES module, which is the expected behavior. And netlify does not consider foo.mjs a lambda function, which is also expected.

However, this behavior makes it seemingly impossible to deploy ES module labmda functions. How should I work it out ?

1 Like

maybe @jonsully has an idea?

Greetings :wave:t2:

I don’t actually use ES6 Modules for Lambda / Netlify Functions since Node.js more traditionally uses require. You may be able to flag Node to prepare for a module instead using a flag in your package.json. I recommend reading more here:

https://nodejs.org/api/esm.html

Under the “Enabling” header.

Although for what it’s worth, you can just change from


export const handler = async (event, context) => {
    return {
        statusCode: 200,
        body: 'Hello, World!'
    };
};

to

exports.handler = async (event, context) => {
    return {
        statusCode: 200,
        body: 'Hello, World!'
    };
};

and things ought to work fine.

Hope that helps!

–
Jon

Hello jsonsully,

I was trying to find a way to pass --input-type to node.js, but I don’t know how to. Is there sth. useful in netlify.toml ?

For now I tell typescript to compile it into commonjs.

Compiling it to commonjs is probably a good way to go actually. Lambda defines their runtime and, AFAIK, doesn’t let you inject your own preferences or flags into the runtime that ultimately runs your functions. Remember that Netlify Functions is just a (great) logistical wrapper around AWS Lambda :slight_smile:

–
Jon

Node v14 just recently became available in AWS lambda.

Anybody know if writing functions using vanilla ES module syntax (i.e. import x from "x" instead of const x = require("x")) works for netlify functions? I can’t get it to work, at least not locally with netlify dev.

I have a project where everything is running on ESM syntax. My build command is a JS file in ESM syntax and it works just fine because I’ve told netlify that my version of node is 14. To get this to work, I’m using the type: "module" value in my package.json. However, this breaks my netlify functions, which are expected to be in commonJS.

As a workaround, I have a directory structure like this for my project:

netlify.toml // specifies NODE_VERSION, AWS_LAMBDA_JS_RUNTIME as 14
package.json // has "type": "module"
functions/
|—endpoint.js
|—package.json // doesn't have "type": "module"

My root package.json tells node to run in ESM mode. That works for my build commands and other scripts I run (which are all written in ESM). However, my netlify functions won’t work unless I write them using commonJS and add a package.json closer to them that doesn’t have type:module (otherwise, node looks for their closest package.json which would be at my project root and it says type:module so it tries to run them as modules but that doesn’t work).

I’ve tried removing the functions/package.json file and rewriting my JS files in functions to ESM syntax instead of commonJS, but it doesn’t work. When I run them with netlify dev, I get this error when invoking a function:

Request from ::1: GET /.netlify/functions/endpoint
{"level":"error","message":"End - Error:"}
{
  "errorMessage": "Must use import to load ES Module: /Users/jimnielsen/Sites/icongallery/functions/endpoint.js\nrequire() of ES modules is not supported.\nrequire() of /Users/jimnielsen/Sites/icongallery/functions/endpoint.js from /usr/local/lib/node_modules/netlify-cli/node_modules/lambda-local/build/lambdalocal.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.\nInstead rename endpoint.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /Users/jimnielsen/Sites/icongallery/package.json.\n",
  "errorType": "Error",
  "stackTrace": [
    "uire() of ES modules is not supported.",
    "uire() of /Users/jimnielsen/Sites/icongallery/functions/endpoint.js from /usr/local/lib/node_modules/netlify-cli/node_modules/lambda-local/build/lambdalocal.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.",
    "tead rename endpoint.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /Users/jimnielsen/Sites/icongallery/package.json.",
    "",
    "Object.Module._extensions..js (internal/modules/cjs/loader.js:1216:13)",
    "Module.load (internal/modules/cjs/loader.js:1049:32)",
    "Function.Module._load (internal/modules/cjs/loader.js:937:14)",
    "Module.require (internal/modules/cjs/loader.js:1089:19)",
    "require (internal/modules/cjs/helpers.js:73:18)",
    "Object._executeSync (/usr/local/lib/node_modules/netlify-cli/node_modules/lambda-local/build/lambdalocal.js:277:26)",
    "Object.execute (/usr/local/lib/node_modules/netlify-cli/node_modules/lambda-local/build/lambdalocal.js:83:22)",
    "executeSynchronousFunction (/usr/local/lib/node_modules/netlify-cli/src/utils/serve-functions.js:102:15)",
    "handler (/usr/local/lib/node_modules/netlify-cli/src/utils/serve-functions.js:248:12)",
    "Layer.handle [as handle_request] (/usr/local/lib/node_modules/netlify-cli/node_modules/express/lib/router/layer.js:95:5)"
  ],
  "level": "error"
}

Now that AWS supports node 14 and above, shouldn’t this be possible? Not sure where in the stack it needs to be made possible…

2 Likes

Hey @jimniels :wave:

Thanks for weighing in here! This goes a bit beyond my scope of what I am able to support you with, but this seems like a great topic to have in our Open Talk channel. I have moved the thread there so that others can weigh in.

Best,
Hillary

1 Like

Hey @jimniels, curious if you found a solve to this. Same issue over here!

Unfortunately not. Was hoping to hear back from folks inside netlify, as I couldn’t find a way to make it work in ESM (have to continue writing functions in commonJS)

Update:

Given that Netlify just posted on the blog about the new esbuild bundler for functions, I thought “ah I bet that resolves this issue”.

Unfortunately, however, I still can’t seem to get it working.

My current setup:

  • netlify-cli@3.15.0
  • node@15.13.0

I updated my netlify.toml file to have node_bundler = "esbuild". I also removed functions/package.json (so that the closest package.json has type: "module") and changed my javascript function to do export function handler instead of exports.handler = ... but still no dice.

I get this actually rather confusing error when doing a GET to my function on localhost:

Request from ::1: GET /.netlify/functions/api-v1-search?name=facebook
{"level":"error","message":"End - Error:"}
{"errorMessage":"Must use import to load ES Module: /Users/jimnielsen/Sites/icongallery/functions/api-v1-search.js\nrequire() of ES modules is not supported.\nrequire() of /Users/jimnielsen/Sites/icongallery/functions/api-v1-search.js from /Users/jimnielsen/Sites/icongallery/node_modules/lambda-local/build/lambdalocal.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.\nInstead rename api-v1-search.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /Users/jimnielsen/Sites/icongallery/package.json.\n","errorType":"Error","stackTrace":["uire() of ES modules is not supported.","uire() of /Users/jimnielsen/Sites/icongallery/functions/api-v1-search.js from /Users/jimnielsen/Sites/icongallery/node_modules/lambda-local/build/lambdalocal.js is an ES module file as it is a .js file whose nearest parent package.json contains \"type\": \"module\" which defines all .js files in that package scope as ES modules.","tead rename api-v1-search.js to end in .cjs, change the requiring code to use import(), or remove \"type\": \"module\" from /Users/jimnielsen/Sites/icongallery/package.json.","","new NodeError (node:internal/errors:329:5)","Object.Module._extensions..js (node:internal/modules/cjs/loader:1109:13)","Module.load (node:internal/modules/cjs/loader:972:32)","Function.Module._load (node:internal/modules/cjs/loader:813:14)","Module.require (node:internal/modules/cjs/loader:996:19)","require (node:internal/modules/cjs/helpers:92:18)","Object._executeSync (/Users/jimnielsen/Sites/icongallery/node_modules/lambda-local/build/lambdalocal.js:277:26)","Object.execute (/Users/jimnielsen/Sites/icongallery/node_modules/lambda-local/build/lambdalocal.js:83:22)","executeSynchronousFunction (/Users/jimnielsen/Sites/icongallery/node_modules/netlify-cli/src/utils/serve-functions.js:102:15)","handler (/Users/jimnielsen/Sites/icongallery/node_modules/netlify-cli/src/utils/serve-functions.js:248:12)"],"level":"error"}
Response with status 500 in 28 ms.

The error is telling me that I Must use import to load ES Module which I’m doing? Has anyone else got this working yet?

1 Like

I’d be interested to hear if anyone has sorted out this issue; it’s causing me some headaches also.

1 Like

Doing this worked for me…

The blog post mentioned by @jimniels says to do the following:

Add this to your netlify.toml:

[functions]
  node_bundler = "esbuild"

Use the following structure:

import { something } from 'some-module'

export async function handler(event, context) {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: "Hello World" })
  }
}
2 Likes

Ok, was able to play around with this myself and found that I just needed the esbuild bundler.

Here is my netlify.toml

[build]
  publish = "dist/"
  command = "yarn build"

[build.environment]
  NODE_VERSION = "14.16.0"

[functions]
  directory = ".netlify/functions"
  node_bundler = "esbuild"

hello-world.js

const handler = async (event) => {
  try {
    const subject = event.queryStringParameters.name || 'World'
    return {
      statusCode: 200,
      body: JSON.stringify({ message: `Hello ${subject}` }),
    }
  } catch (error) {
    return { statusCode: 500, body: error.toString() }
  }
}

export { handler };

And you can see it here
https://nifty-franklin-58beda.netlify.app/.netlify/functions/hello-world

3 Likes

Thanks for circling back to this thread and sharing, @thescientist13! We appreciate it :netliconfetti:

1 Like

No problem! Is there any official doc / roadmap either from Netlify or AWS for when ESM will be available via functions / Lambda natively? That would be really great to know.

Thanks!

You are a life saver for this one

1 Like

Before your question, I thought that know javascript well. I think it’s easier to just use a different development environment than figure it out.

1 Like