Skip to content

Commit

Permalink
fix: generate correct import path when 'src' directory is used and mi…
Browse files Browse the repository at this point in the history
…ddleware 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
  • Loading branch information
pieh authored Sep 6, 2024
1 parent da7b73a commit b54b682
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 8 deletions.
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'
Expand All @@ -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))
Expand Down Expand Up @@ -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/[email protected]/encoding/base64.ts";`,
const base64ModulePath = join(
destDir,
'edge-runtime/vendor/deno.land/[email protected]/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(
Expand All @@ -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> => {
Expand Down
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
Expand Up @@ -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()
Expand All @@ -33,7 +36,7 @@ describe('WASM', () => {

await startMockBlobStore(ctx)

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

Expand All @@ -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',
})
Expand Down

0 comments on commit b54b682

Please sign in to comment.