aws-cdk icon indicating copy to clipboard operation
aws-cdk copied to clipboard

lambda: Cannot create ES Module function w/Code.fromInline

Open krk opened this issue 1 year ago • 7 comments

Describe the bug

Lambda Node Runtimes support loading ESM code since the nodejs14.x runtime.

If there is a single file in the zip, it needs to have the .mjs extension to be loaded as an ES module. Other option is providing a package.json file with "type": "module" in it.

Currently, there is no way to tell CDK to set the file extension when lambda.Code.fromInline is used:

Expected Behavior

Not getting Runtime.UserCodeSyntaxError from the lambda invocation.

Current Behavior

Error on lambda invoke:

{
  "errorType": "Runtime.UserCodeSyntaxError",
  "errorMessage": "SyntaxError: Cannot use import statement outside a module",
  "trace": [
    "Runtime.UserCodeSyntaxError: SyntaxError: Cannot use import statement outside a module",
    "    at _loadUserApp (file:///var/runtime/index.mjs:1084:17)",
    "    at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1119:21)",
    "    at async start (file:///var/runtime/index.mjs:1282:23)",
    "    at async file:///var/runtime/index.mjs:1288:1"
  ]
}

only because the file name in the deployment package is index.js.

Reproduction Steps

new lambda.Function(this, "Verso-Cacher", {
      functionName: "esm",
      code: lambda.Code.fromInline("import { DynamoDBClient } from '@aws-sdk/client-dynamodb'",
      handler: "index.handler",
      memorySize: 2048,
      timeout: Duration.seconds(60),
      runtime: new lambda.Runtime("nodejs18.x", lambda.RuntimeFamily.NODEJS, {
        supportsInlineCode: true,
      }),
    });

Deploy and invoke the function.

This function handler doesn't do anything useful, other than importing from the SDK as an ES Module. It will deploy successfully and invocations will fail.

Possible Solution

Add a new argument in lambda.Code.fromInline that specifies the filename, index.mjs would fix it for the example.

Additional Information/Context

No response

CDK CLI Version

any

Framework Version

No response

Node.js Version

14,16,18

OS

any

Language

TypeScript

Language Version

No response

Other information

No response

krk avatar Nov 14 '23 11:11 krk

Yes this could be a solution. We'll look into this.

pahud avatar Nov 14 '23 16:11 pahud

Another, IMO easier solution would be, to add Code to NodeJsFunction. Because then you could make use of BundlingOptions. But if code is set the entry should not be allowed.

new NodejsFunction(stack, 'MyFunc', {
  runtime: Runtime.NODEJS_LATEST,
  code: Code.fromInline("import { DynamoDBClient } from '@aws-sdk/client-dynamodb'"),
  handler: 'index.handler',
  bundling: {
    format: OutputFormat.ESM
  }
})

jolo-dev avatar Nov 18 '23 09:11 jolo-dev

Is it even possible to set filename or extension when using ZipFile? I'm afraid not, since there is no such property on the CFn definition. Maybe needs-cfn.

tmokmss avatar Nov 18 '23 15:11 tmokmss

Is it even possible to set a filename or extension when using ZipFile? I'm afraid not, since there is no such property on the CFn definition. Maybe needs-cfn.

Hmm... I may not follow. Could you elaborate? When we zip, we zip the whole folder. There, we don't care about the extension. But here, in this case, it's about Code.fromInline where we don't need to zip, right? Theoretically, we could use the esbuildArgs to add the file extension.

new NodejsFunction(stack, 'MyFunc', {
  runtime: Runtime.NODEJS_LATEST,
  code: Code.fromInline("import { DynamoDBClient } from '@aws-sdk/client-dynamodb'"),
  handler: 'index.handler',
  bundling: {
    format: OutputFormat.ESM,
    esbuildArgs: {
      "--outfile": "index.mjs"
    }
  }
})

jolo-dev avatar Nov 18 '23 18:11 jolo-dev

Hi @jolo-dev Currently Code.fromInline only works to render code as a CloudFormation ZipFile prop:

https://github.com/aws/aws-cdk/blob/c66e197f6f8840da6475383dbf2421c3b06ea417/packages/aws-cdk-lib/aws-lambda/lib/function.ts#L902-L911

so the constraints of CloudFormation ZipFile are directly imposed on Code.fromInline. The actual zip process (e.g. creating a file from the code string and zipping it) is done on CloudFormation side. Afaik currently you cannot set its filename but CFn decides it.

Is there any reason not to use code from a file? With code from a file, you can use NodejsFunction to get ESM code. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.BundlingOptions.html#format

tmokmss avatar Nov 19 '23 00:11 tmokmss

@tmokmss You're right. But since we're using Typescript, we could come up with something ;)

I once had a situation where I needed a simple Lambda to pass the event to trigger something. However, it is not Software Engineering's best practice. Because you cannot really test it :D

jolo-dev avatar Nov 19 '23 10:11 jolo-dev

Ran into this same limitation. CDK created index.js, not index.mjs when I used the fromInline function.

sigpop avatar Feb 21 '24 02:02 sigpop

This is a consequence of the underlying CloudFormation support for inline Lambda functions. Please see https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/1420 for updates.

jtuliani avatar Jun 12 '24 16:06 jtuliani