Skip to content

Commit 730df6b

Browse files
fix: prevent Next from defining duplicate global property in edge functions (#1682)
* fix: prevent Next from defining duplicate global property in edge functions * chore: add e2e test Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 8e2ce15 commit 730df6b

File tree

3 files changed

+32
-9
lines changed

3 files changed

+32
-9
lines changed

cypress/integration/middleware/standard.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ describe('Middleware matchers', () => {
4343
})
4444
})
4545
})
46+
47+
describe('Middleware with edge API', () => {
48+
it('serves API routes from the edge runtime', () => {
49+
cy.request('/api/edge').then((response) => {
50+
expect(response.body).to.include('Hello world')
51+
})
52+
})
53+
})

demos/middleware/pages/api/edge.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const config = {
2+
runtime: 'experimental-edge',
3+
}
4+
5+
export default (req) => new Response('Hello world!')

packages/runtime/src/helpers/edge.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,21 @@ const sanitizeName = (name: string) => `next_${name.replace(/\W/g, '_')}`
7171
/**
7272
* Initialization added to the top of the edge function bundle
7373
*/
74-
const bootstrap = /* js */ `
74+
const preamble = /* js */ `
75+
7576
globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
76-
globalThis._ENTRIES ||= {}
77+
let _ENTRIES = {}
7778
// Deno defines "window", but naughty libraries think this means it's a browser
7879
delete globalThis.window
79-
80+
// Next uses "self" as a function-scoped global-like object
81+
const self = {}
8082
`
8183

84+
// Slightly different spacing in different versions!
85+
const IMPORT_UNSUPPORTED = [
86+
`Object.defineProperty(globalThis,"__import_unsupported"`,
87+
` Object.defineProperty(globalThis, "__import_unsupported"`,
88+
]
8289
/**
8390
* Concatenates the Next edge function code with the required chunks and adds an export
8491
*/
@@ -90,17 +97,20 @@ const getMiddlewareBundle = async ({
9097
netlifyConfig: NetlifyConfig
9198
}): Promise<string> => {
9299
const { publish } = netlifyConfig.build
93-
const chunks: Array<string> = [bootstrap]
100+
const chunks: Array<string> = [preamble]
94101
for (const file of edgeFunctionDefinition.files) {
95102
const filePath = join(publish, file)
96-
const data = await fs.readFile(filePath, 'utf8')
103+
104+
let data = await fs.readFile(filePath, 'utf8')
105+
// Next defines an immutable global variable, which is fine unless you have more than one in the bundle
106+
// This adds a check to see if the global is already defined
107+
data = IMPORT_UNSUPPORTED.reduce(
108+
(acc, val) => acc.replace(val, `('__import_unsupported' in globalThis)||${val}`),
109+
data,
110+
)
97111
chunks.push('{', data, '}')
98112
}
99113

100-
const middleware = await fs.readFile(join(publish, `server`, `${edgeFunctionDefinition.name}.js`), 'utf8')
101-
102-
chunks.push(middleware)
103-
104114
const exports = /* js */ `export default _ENTRIES["middleware_${edgeFunctionDefinition.name}"].default;`
105115
chunks.push(exports)
106116
return chunks.join('\n')

0 commit comments

Comments
 (0)