Skip to content

Commit b54b682

Browse files
authored
fix: generate correct import path when 'src' directory is used and middleware imports wasm module (#2583)
* test: add fixture using wasm in middleware in src directory mode * fix: generate correct import path when 'src' directory is used and middleware imports wasm module
1 parent da7b73a commit b54b682

File tree

11 files changed

+154
-8
lines changed

11 files changed

+154
-8
lines changed

src/build/functions/edge.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
2-
import { dirname, join } from 'node:path'
2+
import { dirname, join, relative, sep } from 'node:path'
3+
import { sep as posixSep } from 'node:path/posix'
34

45
import type { Manifest, ManifestFunction } from '@netlify/edge-functions'
56
import { glob } from 'fast-glob'
@@ -8,6 +9,8 @@ import { pathToRegexp } from 'path-to-regexp'
89

910
import { EDGE_HANDLER_NAME, PluginContext } from '../plugin-context.js'
1011

12+
const toPosixPath = (path: string) => path.split(sep).join(posixSep)
13+
1114
const writeEdgeManifest = async (ctx: PluginContext, manifest: Manifest) => {
1215
await mkdir(ctx.edgeFunctionsDir, { recursive: true })
1316
await writeFile(join(ctx.edgeFunctionsDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
@@ -107,10 +110,19 @@ const copyHandlerDependencies = async (
107110

108111
const parts = [shim]
109112

113+
const outputFile = join(destDir, `server/${name}.js`)
114+
110115
if (wasm?.length) {
111-
parts.push(
112-
`import { decode as _base64Decode } from "../edge-runtime/vendor/deno.land/[email protected]/encoding/base64.ts";`,
116+
const base64ModulePath = join(
117+
destDir,
118+
'edge-runtime/vendor/deno.land/[email protected]/encoding/base64.ts',
113119
)
120+
121+
const base64ModulePathRelativeToOutputFile = toPosixPath(
122+
relative(dirname(outputFile), base64ModulePath),
123+
)
124+
125+
parts.push(`import { decode as _base64Decode } from "${base64ModulePathRelativeToOutputFile}";`)
114126
for (const wasmChunk of wasm ?? []) {
115127
const data = await readFile(join(srcDir, wasmChunk.filePath))
116128
parts.push(
@@ -126,9 +138,9 @@ const copyHandlerDependencies = async (
126138
parts.push(`;// Concatenated file: ${file} \n`, entrypoint)
127139
}
128140
const exports = `const middlewareEntryKey = Object.keys(_ENTRIES).find(entryKey => entryKey.startsWith("middleware_${name}")); export default _ENTRIES[middlewareEntryKey].default;`
129-
await mkdir(dirname(join(destDir, `server/${name}.js`)), { recursive: true })
141+
await mkdir(dirname(outputFile), { recursive: true })
130142

131-
await writeFile(join(destDir, `server/${name}.js`), [...parts, exports].join('\n'))
143+
await writeFile(outputFile, [...parts, exports].join('\n'))
132144
}
133145

134146
const createEdgeHandler = async (ctx: PluginContext, definition: NextDefinition): Promise<void> => {
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { platform } = require('process')
2+
const fsPromises = require('fs/promises')
3+
4+
// Next.js uses `fs.promises.copyFile` to copy files from `.next`to the `.next/standalone` directory
5+
// It tries copying the same file twice in parallel. Unix is fine with that, but Windows fails
6+
// with "Resource busy or locked", failing the build.
7+
// We work around this by memoizing the copy operation, so that the second copy is a no-op.
8+
// Tracked in TODO: report to Next.js folks
9+
if (platform === 'win32') {
10+
const copies = new Map()
11+
12+
const originalCopy = fsPromises.copyFile
13+
fsPromises.copyFile = (src, dest, mode) => {
14+
const key = `${dest}:${src}`
15+
const existingCopy = copies.get(key)
16+
if (existingCopy) return existingCopy
17+
18+
const copy = originalCopy(src, dest, mode)
19+
copies.set(key, copy)
20+
return copy
21+
}
22+
}
23+
24+
/** @type {import('next').NextConfig} */
25+
module.exports = {
26+
output: 'standalone',
27+
eslint: {
28+
ignoreDuringBuilds: true,
29+
},
30+
}

tests/fixtures/wasm-src/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "og-api",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"postinstall": "next build",
7+
"dev": "next dev",
8+
"build": "next build"
9+
},
10+
"dependencies": {
11+
"@vercel/og": "latest",
12+
"next": "latest",
13+
"react": "18.2.0",
14+
"react-dom": "18.2.0"
15+
}
16+
}

tests/fixtures/wasm-src/src/add.wasm

126 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ImageResponse } from '@vercel/og'
2+
3+
export async function GET() {
4+
return new ImageResponse(<div>hi</div>, {
5+
width: 1200,
6+
height: 630,
7+
})
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ImageResponse } from '@vercel/og'
2+
3+
export async function GET() {
4+
return new ImageResponse(<div>hi</div>, {
5+
width: 1200,
6+
height: 630,
7+
})
8+
}
9+
10+
export const runtime = 'edge'
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import wasm from './add.wasm?module'
2+
const instance$ = WebAssembly.instantiate(wasm)
3+
4+
async function increment(a) {
5+
const { instance } = await instance$
6+
return instance.exports.add_one(a)
7+
}
8+
export default async function middleware(request) {
9+
const input = Number(request.nextUrl.searchParams.get('input')) || 1
10+
const value = await increment(input)
11+
return new Response(null, { headers: { data: JSON.stringify({ input, value }) } })
12+
}
13+
14+
export const config = {
15+
matcher: '/wasm',
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// /pages/api/og.jsx
2+
import { ImageResponse } from '@vercel/og'
3+
4+
export default function () {
5+
return new ImageResponse(
6+
(
7+
<div
8+
style={{
9+
width: '100%',
10+
height: '100%',
11+
display: 'flex',
12+
alignItems: 'center',
13+
justifyContent: 'center',
14+
fontSize: 128,
15+
background: 'lavender',
16+
}}
17+
>
18+
Hello!
19+
</div>
20+
),
21+
)
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// /pages/api/og.jsx
2+
import { ImageResponse } from '@vercel/og'
3+
4+
export const config = {
5+
runtime: 'edge',
6+
}
7+
8+
export default function () {
9+
return new ImageResponse(
10+
(
11+
<div
12+
style={{
13+
width: '100%',
14+
height: '100%',
15+
display: 'flex',
16+
alignItems: 'center',
17+
justifyContent: 'center',
18+
fontSize: 128,
19+
background: 'lavender',
20+
}}
21+
>
22+
Hello!
23+
</div>
24+
),
25+
)
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>hello world</p>
3+
}

tests/integration/wasm.test.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ beforeEach<FixtureTestContext>(async (ctx) => {
2121
await startMockBlobStore(ctx)
2222
})
2323

24-
describe('WASM', () => {
24+
describe.each([
25+
{ fixture: 'wasm', edgeHandlerFunction: '___netlify-edge-handler-middleware' },
26+
{ fixture: 'wasm-src', edgeHandlerFunction: '___netlify-edge-handler-src-middleware' },
27+
])('$fixture', ({ fixture, edgeHandlerFunction }) => {
2528
beforeEach<FixtureTestContext>(async (ctx) => {
2629
// set for each test a new deployID and siteID
2730
ctx.deployID = generateRandomObjectID()
@@ -33,7 +36,7 @@ describe('WASM', () => {
3336

3437
await startMockBlobStore(ctx)
3538

36-
await createFixture('wasm', ctx)
39+
await createFixture(fixture, ctx)
3740
await runPlugin(ctx)
3841
})
3942

@@ -58,7 +61,7 @@ describe('WASM', () => {
5861
test<FixtureTestContext>('should work in middleware', async (ctx) => {
5962
const origin = new LocalServer()
6063
const response = await invokeEdgeFunction(ctx, {
61-
functions: ['___netlify-edge-handler-middleware'],
64+
functions: [edgeHandlerFunction],
6265
origin,
6366
url: '/wasm?input=3',
6467
})

0 commit comments

Comments
 (0)