diff --git a/cypress/integration/middleware/standard.spec.ts b/cypress/integration/middleware/standard.spec.ts index 6bc197bfa8..2c6f79d7c0 100644 --- a/cypress/integration/middleware/standard.spec.ts +++ b/cypress/integration/middleware/standard.spec.ts @@ -43,3 +43,11 @@ describe('Middleware matchers', () => { }) }) }) + +describe('Middleware with edge API', () => { + it('serves API routes from the edge runtime', () => { + cy.request('/api/edge').then((response) => { + expect(response.body).to.include('Hello world') + }) + }) +}) diff --git a/demos/middleware/pages/api/edge.ts b/demos/middleware/pages/api/edge.ts new file mode 100644 index 0000000000..fd2d60dc30 --- /dev/null +++ b/demos/middleware/pages/api/edge.ts @@ -0,0 +1,5 @@ +export const config = { + runtime: 'experimental-edge', +} + +export default (req) => new Response('Hello world!') diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts index 465b17efba..949c493ec7 100644 --- a/packages/runtime/src/helpers/edge.ts +++ b/packages/runtime/src/helpers/edge.ts @@ -71,14 +71,21 @@ const sanitizeName = (name: string) => `next_${name.replace(/\W/g, '_')}` /** * Initialization added to the top of the edge function bundle */ -const bootstrap = /* js */ ` +const preamble = /* js */ ` + globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } } -globalThis._ENTRIES ||= {} +let _ENTRIES = {} // Deno defines "window", but naughty libraries think this means it's a browser delete globalThis.window - +// Next uses "self" as a function-scoped global-like object +const self = {} ` +// Slightly different spacing in different versions! +const IMPORT_UNSUPPORTED = [ + `Object.defineProperty(globalThis,"__import_unsupported"`, + ` Object.defineProperty(globalThis, "__import_unsupported"`, +] /** * Concatenates the Next edge function code with the required chunks and adds an export */ @@ -90,17 +97,20 @@ const getMiddlewareBundle = async ({ netlifyConfig: NetlifyConfig }): Promise => { const { publish } = netlifyConfig.build - const chunks: Array = [bootstrap] + const chunks: Array = [preamble] for (const file of edgeFunctionDefinition.files) { const filePath = join(publish, file) - const data = await fs.readFile(filePath, 'utf8') + + let data = await fs.readFile(filePath, 'utf8') + // Next defines an immutable global variable, which is fine unless you have more than one in the bundle + // This adds a check to see if the global is already defined + data = IMPORT_UNSUPPORTED.reduce( + (acc, val) => acc.replace(val, `('__import_unsupported' in globalThis)||${val}`), + data, + ) chunks.push('{', data, '}') } - const middleware = await fs.readFile(join(publish, `server`, `${edgeFunctionDefinition.name}.js`), 'utf8') - - chunks.push(middleware) - const exports = /* js */ `export default _ENTRIES["middleware_${edgeFunctionDefinition.name}"].default;` chunks.push(exports) return chunks.join('\n')