Skip to content

fix: generate correct import path when 'src' directory is used and middleware imports wasm module #2583

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

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/build/functions/edge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { dirname, join, relative, sep } from 'node:path'
import { sep as posixSep } from 'node:path/posix'

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

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

const toPosixPath = (path: string) => path.split(sep).join(posixSep)

const writeEdgeManifest = async (ctx: PluginContext, manifest: Manifest) => {
await mkdir(ctx.edgeFunctionsDir, { recursive: true })
await writeFile(join(ctx.edgeFunctionsDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
@@ -107,10 +110,19 @@ const copyHandlerDependencies = async (

const parts = [shim]

const outputFile = join(destDir, `server/${name}.js`)

if (wasm?.length) {
parts.push(
`import { decode as _base64Decode } from "../edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64.ts";`,
const base64ModulePath = join(
destDir,
'edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64.ts',
)

const base64ModulePathRelativeToOutputFile = toPosixPath(
relative(dirname(outputFile), base64ModulePath),
)

parts.push(`import { decode as _base64Decode } from "${base64ModulePathRelativeToOutputFile}";`)
for (const wasmChunk of wasm ?? []) {
const data = await readFile(join(srcDir, wasmChunk.filePath))
parts.push(
@@ -126,9 +138,9 @@ const copyHandlerDependencies = async (
parts.push(`;// Concatenated file: ${file} \n`, entrypoint)
}
const exports = `const middlewareEntryKey = Object.keys(_ENTRIES).find(entryKey => entryKey.startsWith("middleware_${name}")); export default _ENTRIES[middlewareEntryKey].default;`
await mkdir(dirname(join(destDir, `server/${name}.js`)), { recursive: true })
await mkdir(dirname(outputFile), { recursive: true })

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

const createEdgeHandler = async (ctx: PluginContext, definition: NextDefinition): Promise<void> => {
30 changes: 30 additions & 0 deletions tests/fixtures/wasm-src/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { platform } = require('process')
const fsPromises = require('fs/promises')

// Next.js uses `fs.promises.copyFile` to copy files from `.next`to the `.next/standalone` directory
// It tries copying the same file twice in parallel. Unix is fine with that, but Windows fails
// with "Resource busy or locked", failing the build.
// We work around this by memoizing the copy operation, so that the second copy is a no-op.
// Tracked in TODO: report to Next.js folks
if (platform === 'win32') {
const copies = new Map()

const originalCopy = fsPromises.copyFile
fsPromises.copyFile = (src, dest, mode) => {
const key = `${dest}:${src}`
const existingCopy = copies.get(key)
if (existingCopy) return existingCopy

const copy = originalCopy(src, dest, mode)
copies.set(key, copy)
return copy
}
}

/** @type {import('next').NextConfig} */
module.exports = {
output: 'standalone',
eslint: {
ignoreDuringBuilds: true,
},
}
16 changes: 16 additions & 0 deletions tests/fixtures/wasm-src/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "og-api",
"version": "0.1.0",
"private": true,
"scripts": {
"postinstall": "next build",
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"@vercel/og": "latest",
"next": "latest",
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
Binary file added tests/fixtures/wasm-src/src/add.wasm
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/fixtures/wasm-src/src/app/og-node/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ImageResponse } from '@vercel/og'

export async function GET() {
return new ImageResponse(<div>hi</div>, {
width: 1200,
height: 630,
})
}
10 changes: 10 additions & 0 deletions tests/fixtures/wasm-src/src/app/og/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ImageResponse } from '@vercel/og'

export async function GET() {
return new ImageResponse(<div>hi</div>, {
width: 1200,
height: 630,
})
}

export const runtime = 'edge'
16 changes: 16 additions & 0 deletions tests/fixtures/wasm-src/src/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import wasm from './add.wasm?module'
const instance$ = WebAssembly.instantiate(wasm)

async function increment(a) {
const { instance } = await instance$
return instance.exports.add_one(a)
}
export default async function middleware(request) {
const input = Number(request.nextUrl.searchParams.get('input')) || 1
const value = await increment(input)
return new Response(null, { headers: { data: JSON.stringify({ input, value }) } })
}

export const config = {
matcher: '/wasm',
}
22 changes: 22 additions & 0 deletions tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// /pages/api/og.jsx
import { ImageResponse } from '@vercel/og'

export default function () {
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 128,
background: 'lavender',
}}
>
Hello!
</div>
),
)
}
26 changes: 26 additions & 0 deletions tests/fixtures/wasm-src/src/pages/api/og.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// /pages/api/og.jsx
import { ImageResponse } from '@vercel/og'

export const config = {
runtime: 'edge',
}

export default function () {
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 128,
background: 'lavender',
}}
>
Hello!
</div>
),
)
}
3 changes: 3 additions & 0 deletions tests/fixtures/wasm-src/src/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <p>hello world</p>
}
9 changes: 6 additions & 3 deletions tests/integration/wasm.test.ts
Original file line number Diff line number Diff line change
@@ -21,7 +21,10 @@ beforeEach<FixtureTestContext>(async (ctx) => {
await startMockBlobStore(ctx)
})

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

await startMockBlobStore(ctx)

await createFixture('wasm', ctx)
await createFixture(fixture, ctx)
await runPlugin(ctx)
})

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