-
Notifications
You must be signed in to change notification settings - Fork 357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: bug around v1 functions with nodeModuleFormat: "esm"
#6557
Changes from all commits
9653292
1ec7852
ee4824f
977ba62
c05abc1
8578837
b23e918
c1d1df6
e5e61a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,12 @@ import { Worker } from 'worker_threads' | |
import lambdaLocal from 'lambda-local' | ||
|
||
import { BLOBS_CONTEXT_VARIABLE } from '../../../blobs/blobs.js' | ||
import type NetlifyFunction from '../../netlify-function.js' | ||
|
||
import detectNetlifyLambdaBuilder from './builders/netlify-lambda.js' | ||
import detectZisiBuilder, { parseFunctionForMetadata } from './builders/zisi.js' | ||
import { SECONDS_TO_MILLISECONDS } from './constants.js' | ||
import { $TSFixMe } from '../../../../commands/types.js' | ||
|
||
export const name = 'js' | ||
|
||
|
@@ -99,11 +101,21 @@ export const invokeFunction = async ({ context, environment, event, func, timeou | |
}) | ||
} | ||
|
||
// @ts-expect-error TS(7031) FIXME: Binding element 'context' implicitly has an 'any' ... Remove this comment to see the full error message | ||
export const invokeFunctionDirectly = async ({ context, event, func, timeout }) => { | ||
export const invokeFunctionDirectly = async ({ | ||
context, | ||
event, | ||
func, | ||
timeout, | ||
}: { | ||
context: $TSFixMe | ||
event: $TSFixMe | ||
func: NetlifyFunction | ||
timeout: number | ||
}) => { | ||
// If a function builder has defined a `buildPath` property, we use it. | ||
// Otherwise, we'll invoke the function's main file. | ||
const lambdaPath = func.buildData?.buildPath ?? func.mainFile | ||
const { buildPath } = await func.getBuildData() | ||
const lambdaPath = buildPath ?? func.mainFile | ||
const result = await lambdaLocal.execute({ | ||
clientContext: JSON.stringify(context), | ||
environment: { | ||
|
@@ -118,6 +130,7 @@ export const invokeFunctionDirectly = async ({ context, event, func, timeout }) | |
lambdaPath, | ||
timeoutMs: timeout * SECONDS_TO_MILLISECONDS, | ||
verboseLevel: 3, | ||
esm: lambdaPath.endsWith('.mjs'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're already running ESM functions locally today, so I'm not sure I follow why we're adding this now. What does it do exactly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lambda-local complained about ESM files not being requireable, so I had to add this. Under the hood, it makes lambda-local dynamic-import the file, instead of requiring it. I'd guess that right now, we're compiling those ESM functions to CJS? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We respect the module format specified by the file extension and the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But lambda-local is a CJS module, and if we don't set the I'm not sure why this hasn't been a problem before, but if I remove this line locally I get: |
||
}) | ||
|
||
return result | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { test } from 'vitest' | ||
|
||
import { withDevServer } from '../../utils/dev-server' | ||
import { withSiteBuilder } from '../../utils/site-builder' | ||
|
||
test('nodeModuleFormat: esm v1 functions should work', async (t) => { | ||
await withSiteBuilder(t, async (builder) => { | ||
await builder | ||
.withNetlifyToml({ | ||
config: { | ||
plugins: [{ package: './plugins/setup-functions' }], | ||
}, | ||
}) | ||
.withBuildPlugin({ | ||
name: 'setup-functions', | ||
plugin: { | ||
onBuild: async () => { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires, n/global-require | ||
const { mkdir, writeFile } = require('node:fs/promises') | ||
await mkdir('.netlify/functions-internal', { recursive: true }) | ||
await writeFile( | ||
'.netlify/functions-internal/server.json', | ||
JSON.stringify({ | ||
config: { | ||
nodeModuleFormat: 'esm', | ||
}, | ||
version: 1, | ||
}), | ||
) | ||
|
||
await writeFile( | ||
'.netlify/functions-internal/server.mjs', | ||
` | ||
export async function handler(event, context) { | ||
return { | ||
statusCode: 200, | ||
body: "This is an internal function.", | ||
}; | ||
} | ||
`, | ||
) | ||
}, | ||
}, | ||
}) | ||
.build() | ||
|
||
await withDevServer({ cwd: builder.directory, serve: true }, async (server) => { | ||
const response = await fetch(new URL('/.netlify/functions/server', server.url)) | ||
t.expect(await response.text()).toBe('This is an internal function.') | ||
t.expect(response.status).toBe(200) | ||
}) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem here was that esm files end in
.mjs
, not.js
. By using the basename from the manifest, we'll have the right extension