Skip to content

Commit b3aaed6

Browse files
ascorbickodiakhq[bot]
andauthoredMar 4, 2024
fix: concatenate all edge chunks (#319)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent f0edaba commit b3aaed6

File tree

8 files changed

+309
-311
lines changed

8 files changed

+309
-311
lines changed
 

‎src/build/functions/edge.ts

+29-39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
2-
import { dirname, join, relative } from 'node:path'
2+
import { dirname, join } from 'node:path'
33

44
import { glob } from 'fast-glob'
55
import type { EdgeFunctionDefinition as NextDefinition } from 'next/dist/build/webpack/plugins/middleware-plugin.js'
@@ -116,44 +116,34 @@ const copyHandlerDependencies = async (
116116
const srcDir = join(ctx.standaloneDir, ctx.nextDistDir)
117117
const destDir = join(ctx.edgeFunctionsDir, getHandlerName({ name }))
118118

119-
await Promise.all(
120-
files.map(async (file) => {
121-
if (file === `server/${name}.js`) {
122-
const edgeRuntimeDir = join(ctx.pluginDir, 'edge-runtime')
123-
const shimPath = join(edgeRuntimeDir, 'shim/index.js')
124-
const shim = await readFile(shimPath, 'utf8')
125-
126-
const importsDir = relative(dirname(join(srcDir, file)), join(srcDir, 'server'))
127-
const importsSrc = `${importsDir || '.'}/edge-runtime-webpack.js`
128-
const imports = `import '${importsSrc}';`
129-
130-
const exports = `export default _ENTRIES["middleware_${name}"].default;`
131-
132-
const parts = [shim, imports]
133-
134-
if (wasm?.length) {
135-
parts.push(
136-
`import { decode as _base64Decode } from "../edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64.ts";`,
137-
)
138-
for (const wasmChunk of wasm ?? []) {
139-
const data = await readFile(join(srcDir, wasmChunk.filePath))
140-
parts.push(
141-
`const ${wasmChunk.name} = _base64Decode(${JSON.stringify(
142-
data.toString('base64'),
143-
)}).buffer`,
144-
)
145-
}
146-
}
147-
148-
const entrypoint = await readFile(join(srcDir, file), 'utf8')
149-
150-
await mkdir(dirname(join(destDir, file)), { recursive: true })
151-
await writeFile(join(destDir, file), [...parts, entrypoint, exports].join('\n;'))
152-
} else {
153-
await cp(join(srcDir, file), join(destDir, file))
154-
}
155-
}),
156-
)
119+
const edgeRuntimeDir = join(ctx.pluginDir, 'edge-runtime')
120+
const shimPath = join(edgeRuntimeDir, 'shim/index.js')
121+
const shim = await readFile(shimPath, 'utf8')
122+
123+
const parts = [shim]
124+
125+
if (wasm?.length) {
126+
parts.push(
127+
`import { decode as _base64Decode } from "../edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64.ts";`,
128+
)
129+
for (const wasmChunk of wasm ?? []) {
130+
const data = await readFile(join(srcDir, wasmChunk.filePath))
131+
parts.push(
132+
`const ${wasmChunk.name} = _base64Decode(${JSON.stringify(
133+
data.toString('base64'),
134+
)}).buffer`,
135+
)
136+
}
137+
}
138+
139+
for (const file of files) {
140+
const entrypoint = await readFile(join(srcDir, file), 'utf8')
141+
parts.push(`;// Concatenated file: ${file} \n`, entrypoint)
142+
}
143+
const exports = `export default _ENTRIES["middleware_${name}"].default;`
144+
await mkdir(dirname(join(destDir, `server/${name}.js`)), { recursive: true })
145+
146+
await writeFile(join(destDir, `server/${name}.js`), [...parts, exports].join('\n'))
157147
}
158148

159149
const createEdgeHandler = async (ctx: PluginContext, definition: NextDefinition): Promise<void> => {

‎tests/fixtures/middleware-pages/package-lock.json

+49-67
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/fixtures/middleware-pages/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"build": "next build"
99
},
1010
"dependencies": {
11-
"next": "^14.0.4",
11+
"next": "^14.1.1",
1212
"react": "18.2.0",
1313
"react-dom": "18.2.0"
1414
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const runtime = 'edge'
2+
3+
export default (req) => {
4+
return Response.json(Object.fromEntries(req.headers.entries()), {
5+
headers: {
6+
'headers-from-edge-function': '1',
7+
},
8+
})
9+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export default function handler(req, res) {
2+
res.headers = { 'headers-from-function': '1' }
23
res.json({ url: req.url, headers: req.headers })
34
}

‎tests/fixtures/middleware/package-lock.json

+49-67
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/fixtures/middleware/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"dependencies": {
1111
"@aws-amplify/adapter-nextjs": "^1.0.13",
12-
"next": "^14.0.4",
12+
"next": "^14.1.1",
1313
"react": "18.2.0",
1414
"react-dom": "18.2.0"
1515
}

‎tests/integration/edge-handler.test.ts

+170-136
Original file line numberDiff line numberDiff line change
@@ -19,63 +19,69 @@ beforeEach<FixtureTestContext>(async (ctx) => {
1919
await startMockBlobStore(ctx)
2020
})
2121

22-
test.skipIf(platform === "win32")<FixtureTestContext>('should add request/response headers', async (ctx) => {
23-
await createFixture('middleware', ctx)
24-
await runPlugin(ctx)
22+
test.skipIf(platform === 'win32')<FixtureTestContext>(
23+
'should add request/response headers',
24+
async (ctx) => {
25+
await createFixture('middleware', ctx)
26+
await runPlugin(ctx)
2527

26-
const origin = await LocalServer.run(async (req, res) => {
27-
expect(req.url).toBe('/test/next')
28-
expect(req.headers['x-hello-from-middleware-req']).toBe('hello')
28+
const origin = await LocalServer.run(async (req, res) => {
29+
expect(req.url).toBe('/test/next')
30+
expect(req.headers['x-hello-from-middleware-req']).toBe('hello')
2931

30-
res.write('Hello from origin!')
31-
res.end()
32-
})
32+
res.write('Hello from origin!')
33+
res.end()
34+
})
3335

34-
ctx.cleanup?.push(() => origin.stop())
36+
ctx.cleanup?.push(() => origin.stop())
3537

36-
const response = await invokeEdgeFunction(ctx, {
37-
functions: ['___netlify-edge-handler-middleware'],
38-
origin,
39-
url: '/test/next',
40-
})
38+
const response = await invokeEdgeFunction(ctx, {
39+
functions: ['___netlify-edge-handler-middleware'],
40+
origin,
41+
url: '/test/next',
42+
})
4143

42-
expect(await response.text()).toBe('Hello from origin!')
43-
expect(response.status).toBe(200)
44-
expect(response.headers.get('x-hello-from-middleware-res'), 'added a response header').toEqual(
45-
'hello',
46-
)
47-
expect(origin.calls).toBe(1)
48-
})
44+
expect(await response.text()).toBe('Hello from origin!')
45+
expect(response.status).toBe(200)
46+
expect(response.headers.get('x-hello-from-middleware-res'), 'added a response header').toEqual(
47+
'hello',
48+
)
49+
expect(origin.calls).toBe(1)
50+
},
51+
)
4952

50-
test.skipIf(platform === "win32")<FixtureTestContext>('should add request/response headers when using src dir', async (ctx) => {
51-
await createFixture('middleware-src', ctx)
52-
await runPlugin(ctx)
53+
test.skipIf(platform === 'win32')<FixtureTestContext>(
54+
'should add request/response headers when using src dir',
55+
async (ctx) => {
56+
await createFixture('middleware-src', ctx)
57+
await runPlugin(ctx)
5358

54-
const origin = await LocalServer.run(async (req, res) => {
55-
expect(req.url).toBe('/test/next')
56-
expect(req.headers['x-hello-from-middleware-req']).toBe('hello')
59+
const origin = await LocalServer.run(async (req, res) => {
60+
expect(req.url).toBe('/test/next')
61+
expect(req.headers['x-hello-from-middleware-req']).toBe('hello')
5762

58-
res.write('Hello from origin!')
59-
res.end()
60-
})
63+
res.write('Hello from origin!')
64+
res.end()
65+
})
6166

62-
ctx.cleanup?.push(() => origin.stop())
67+
ctx.cleanup?.push(() => origin.stop())
6368

64-
const response = await invokeEdgeFunction(ctx, {
65-
functions: ['___netlify-edge-handler-src-middleware'],
66-
origin,
67-
url: '/test/next',
68-
})
69+
const response = await invokeEdgeFunction(ctx, {
70+
functions: ['___netlify-edge-handler-src-middleware'],
71+
origin,
72+
url: '/test/next',
73+
})
6974

70-
expect(await response.text()).toBe('Hello from origin!')
71-
expect(response.status).toBe(200)
72-
expect(response.headers.get('x-hello-from-middleware-res'), 'added a response header').toEqual(
73-
'hello',
74-
)
75-
expect(origin.calls).toBe(1)
76-
})
75+
expect(await response.text()).toBe('Hello from origin!')
76+
expect(response.status).toBe(200)
77+
expect(response.headers.get('x-hello-from-middleware-res'), 'added a response header').toEqual(
78+
'hello',
79+
)
80+
expect(origin.calls).toBe(1)
81+
},
82+
)
7783

78-
describe.skipIf(platform === "win32")('redirect', () => {
84+
describe.skipIf(platform === 'win32')('redirect', () => {
7985
test<FixtureTestContext>('should return a redirect response', async (ctx) => {
8086
await createFixture('middleware', ctx)
8187
await runPlugin(ctx)
@@ -124,7 +130,7 @@ describe.skipIf(platform === "win32")('redirect', () => {
124130
})
125131
})
126132

127-
describe.skipIf(platform === "win32")('rewrite', () => {
133+
describe.skipIf(platform === 'win32')('rewrite', () => {
128134
test<FixtureTestContext>('should rewrite to an external URL', async (ctx) => {
129135
await createFixture('middleware', ctx)
130136
await runPlugin(ctx)
@@ -184,116 +190,122 @@ describe.skipIf(platform === "win32")('rewrite', () => {
184190
})
185191
})
186192

187-
describe.skipIf(platform === "win32")("aborts middleware execution when the matcher conditions don't match the request", () => {
188-
test<FixtureTestContext>('when the path is excluded', async (ctx) => {
189-
await createFixture('middleware', ctx)
190-
await runPlugin(ctx)
193+
describe.skipIf(platform === 'win32')(
194+
"aborts middleware execution when the matcher conditions don't match the request",
195+
() => {
196+
test<FixtureTestContext>('when the path is excluded', async (ctx) => {
197+
await createFixture('middleware', ctx)
198+
await runPlugin(ctx)
191199

192-
const origin = await LocalServer.run(async (req, res) => {
193-
expect(req.url).toBe('/_next/data')
194-
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
200+
const origin = await LocalServer.run(async (req, res) => {
201+
expect(req.url).toBe('/_next/data')
202+
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
195203

196-
res.write('Hello from origin!')
197-
res.end()
198-
})
199-
200-
ctx.cleanup?.push(() => origin.stop())
201-
202-
const response = await invokeEdgeFunction(ctx, {
203-
functions: ['___netlify-edge-handler-middleware'],
204-
origin,
205-
url: '/_next/data',
206-
})
207-
208-
expect(await response.text()).toBe('Hello from origin!')
209-
expect(response.status).toBe(200)
210-
expect(response.headers.has('x-hello-from-middleware-res')).toBeFalsy()
211-
expect(origin.calls).toBe(1)
212-
})
204+
res.write('Hello from origin!')
205+
res.end()
206+
})
213207

214-
test<FixtureTestContext>('when a request header matches a condition', async (ctx) => {
215-
await createFixture('middleware-conditions', ctx)
216-
await runPlugin(ctx)
208+
ctx.cleanup?.push(() => origin.stop())
217209

218-
const origin = await LocalServer.run(async (req, res) => {
219-
expect(req.url).toBe('/foo')
220-
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
210+
const response = await invokeEdgeFunction(ctx, {
211+
functions: ['___netlify-edge-handler-middleware'],
212+
origin,
213+
url: '/_next/data',
214+
})
221215

222-
res.write('Hello from origin!')
223-
res.end()
216+
expect(await response.text()).toBe('Hello from origin!')
217+
expect(response.status).toBe(200)
218+
expect(response.headers.has('x-hello-from-middleware-res')).toBeFalsy()
219+
expect(origin.calls).toBe(1)
224220
})
225221

226-
ctx.cleanup?.push(() => origin.stop())
222+
test<FixtureTestContext>('when a request header matches a condition', async (ctx) => {
223+
await createFixture('middleware-conditions', ctx)
224+
await runPlugin(ctx)
227225

228-
// Request 1: Middleware should run because we're not sending the header.
229-
const response1 = await invokeEdgeFunction(ctx, {
230-
functions: ['___netlify-edge-handler-middleware'],
231-
origin,
232-
url: '/foo',
233-
})
226+
const origin = await LocalServer.run(async (req, res) => {
227+
expect(req.url).toBe('/foo')
228+
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
234229

235-
expect(await response1.text()).toBe('Hello from origin!')
236-
expect(response1.status).toBe(200)
237-
expect(response1.headers.has('x-hello-from-middleware-res')).toBeTruthy()
238-
expect(origin.calls).toBe(1)
230+
res.write('Hello from origin!')
231+
res.end()
232+
})
239233

240-
// Request 2: Middleware should not run because we're sending the header.
241-
const response2 = await invokeEdgeFunction(ctx, {
242-
headers: {
243-
'x-custom-header': 'custom-value',
244-
},
245-
functions: ['___netlify-edge-handler-middleware'],
246-
origin,
247-
url: '/foo',
248-
})
234+
ctx.cleanup?.push(() => origin.stop())
249235

250-
expect(await response2.text()).toBe('Hello from origin!')
251-
expect(response2.status).toBe(200)
252-
expect(response2.headers.has('x-hello-from-middleware-res')).toBeFalsy()
253-
expect(origin.calls).toBe(2)
254-
})
236+
// Request 1: Middleware should run because we're not sending the header.
237+
const response1 = await invokeEdgeFunction(ctx, {
238+
functions: ['___netlify-edge-handler-middleware'],
239+
origin,
240+
url: '/foo',
241+
})
255242

256-
test<FixtureTestContext>('should handle locale matching correctly', async (ctx) => {
257-
await createFixture('middleware-conditions', ctx)
258-
await runPlugin(ctx)
243+
expect(await response1.text()).toBe('Hello from origin!')
244+
expect(response1.status).toBe(200)
245+
expect(response1.headers.has('x-hello-from-middleware-res')).toBeTruthy()
246+
expect(origin.calls).toBe(1)
259247

260-
const origin = await LocalServer.run(async (req, res) => {
261-
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
248+
// Request 2: Middleware should not run because we're sending the header.
249+
const response2 = await invokeEdgeFunction(ctx, {
250+
headers: {
251+
'x-custom-header': 'custom-value',
252+
},
253+
functions: ['___netlify-edge-handler-middleware'],
254+
origin,
255+
url: '/foo',
256+
})
262257

263-
res.write('Hello from origin!')
264-
res.end()
258+
expect(await response2.text()).toBe('Hello from origin!')
259+
expect(response2.status).toBe(200)
260+
expect(response2.headers.has('x-hello-from-middleware-res')).toBeFalsy()
261+
expect(origin.calls).toBe(2)
265262
})
266263

267-
ctx.cleanup?.push(() => origin.stop())
264+
test<FixtureTestContext>('should handle locale matching correctly', async (ctx) => {
265+
await createFixture('middleware-conditions', ctx)
266+
await runPlugin(ctx)
268267

269-
for (const path of ['/hello', '/en/hello', '/nl-NL/hello', '/nl-NL/about']) {
270-
const response = await invokeEdgeFunction(ctx, {
271-
functions: ['___netlify-edge-handler-middleware'],
272-
origin,
273-
url: path,
274-
})
275-
expect(response.headers.has('x-hello-from-middleware-res'), `does match ${path}`).toBeTruthy()
276-
expect(await response.text()).toBe('Hello from origin!')
277-
expect(response.status).toBe(200)
278-
}
268+
const origin = await LocalServer.run(async (req, res) => {
269+
expect(req.headers['x-hello-from-middleware-req']).toBeUndefined()
279270

280-
for (const path of ['/hello/invalid', '/about', '/en/about']) {
281-
const response = await invokeEdgeFunction(ctx, {
282-
functions: ['___netlify-edge-handler-middleware'],
283-
origin,
284-
url: path,
271+
res.write('Hello from origin!')
272+
res.end()
285273
})
286-
expect(
287-
response.headers.has('x-hello-from-middleware-res'),
288-
`does not match ${path}`,
289-
).toBeFalsy()
290-
expect(await response.text()).toBe('Hello from origin!')
291-
expect(response.status).toBe(200)
292-
}
293-
})
294-
})
295274

296-
describe.skipIf(platform === "win32")('should run middleware on data requests', () => {
275+
ctx.cleanup?.push(() => origin.stop())
276+
277+
for (const path of ['/hello', '/en/hello', '/nl-NL/hello', '/nl-NL/about']) {
278+
const response = await invokeEdgeFunction(ctx, {
279+
functions: ['___netlify-edge-handler-middleware'],
280+
origin,
281+
url: path,
282+
})
283+
expect(
284+
response.headers.has('x-hello-from-middleware-res'),
285+
`does match ${path}`,
286+
).toBeTruthy()
287+
expect(await response.text()).toBe('Hello from origin!')
288+
expect(response.status).toBe(200)
289+
}
290+
291+
for (const path of ['/hello/invalid', '/about', '/en/about']) {
292+
const response = await invokeEdgeFunction(ctx, {
293+
functions: ['___netlify-edge-handler-middleware'],
294+
origin,
295+
url: path,
296+
})
297+
expect(
298+
response.headers.has('x-hello-from-middleware-res'),
299+
`does not match ${path}`,
300+
).toBeFalsy()
301+
expect(await response.text()).toBe('Hello from origin!')
302+
expect(response.status).toBe(200)
303+
}
304+
})
305+
},
306+
)
307+
308+
describe.skipIf(platform === 'win32')('should run middleware on data requests', () => {
297309
test<FixtureTestContext>('when `trailingSlash: false`', async (ctx) => {
298310
await createFixture('middleware', ctx)
299311
await runPlugin(ctx)
@@ -344,6 +356,28 @@ describe.skipIf(platform === "win32")('should run middleware on data requests',
344356
})
345357

346358
describe('page router', () => {
359+
test<FixtureTestContext>('edge api routes should work with middleware', async (ctx) => {
360+
await createFixture('middleware-pages', ctx)
361+
await runPlugin(ctx)
362+
const origin = await LocalServer.run(async (req, res) => {
363+
res.write(
364+
JSON.stringify({
365+
url: req.url,
366+
headers: req.headers,
367+
}),
368+
)
369+
res.end()
370+
})
371+
ctx.cleanup?.push(() => origin.stop())
372+
const response = await invokeEdgeFunction(ctx, {
373+
functions: ['___netlify-edge-handler-middleware'],
374+
origin,
375+
url: `/api/edge-headers`,
376+
})
377+
const res = await response.json()
378+
expect(res.url).toBe('/api/edge-headers')
379+
expect(response.status).toBe(200)
380+
})
347381
test<FixtureTestContext>('middleware should rewrite data requests', async (ctx) => {
348382
await createFixture('middleware-pages', ctx)
349383
await runPlugin(ctx)

0 commit comments

Comments
 (0)
Please sign in to comment.