1+ import { existsSync , readFileSync , readdirSync } from 'node:fs'
2+ import { createRequire } from 'node:module'
3+ import { join } from 'node:path'
14import { ImageResponse } from '@takumi-rs/image-response'
25import { findLibrary } from '~/libraries'
36import type { LibraryId } from '~/libraries'
@@ -12,6 +15,65 @@ import {
1215
1316const ISLAND_KEY = 'island'
1417
18+ // Force takumi to render via @takumi -rs/wasm instead of @takumi-rs/core's
19+ // native napi binding. The native loader requires platform-specific
20+ // .node binaries (e.g. @takumi-rs/core-linux-x64-gnu) which Netlify's
21+ // zip-it-and-ship-it consistently dropped from the function bundle —
22+ // `external_node_modules` and explicit optionalDependencies didn't fix
23+ // it. WASM is platform-agnostic and ships a single .wasm asset (listed
24+ // in netlify.toml `included_files`).
25+ const WASM_REL_PATH = 'node_modules/@takumi-rs/wasm/pkg/takumi_wasm_bg.wasm'
26+ const WASM_PNPM_REL_PATH =
27+ 'node_modules/@takumi-rs/wasm/pkg/takumi_wasm_bg.wasm'
28+
29+ let cachedWasmBytes : Uint8Array | null = null
30+ function loadTakumiWasm ( ) : Uint8Array {
31+ if ( cachedWasmBytes ) return cachedWasmBytes
32+ const candidatePaths = [
33+ // Standard module resolution — works in dev and any environment that
34+ // hoists @takumi -rs/wasm to top-level node_modules.
35+ tryRequireResolve ( '@takumi-rs/wasm/takumi_wasm_bg.wasm' ) ,
36+ // Top-level pnpm hoist (also via require but without the subpath
37+ // exports indirection).
38+ join ( process . cwd ( ) , WASM_REL_PATH ) ,
39+ // Netlify Functions deploy: pnpm packages live under
40+ // node_modules/.pnpm/<pkg>@<version>/node_modules/<pkg>/. The function
41+ // bundler isn't symlinking @takumi-rs/wasm at top-level, so walk .pnpm
42+ // and find the matching directory.
43+ findInPnpmStore ( '@takumi-rs+wasm@' , WASM_PNPM_REL_PATH ) ,
44+ ] . filter ( ( p ) : p is string => Boolean ( p ) )
45+
46+ for ( const path of candidatePaths ) {
47+ if ( existsSync ( path ) ) {
48+ cachedWasmBytes = readFileSync ( path )
49+ return cachedWasmBytes
50+ }
51+ }
52+ throw new Error (
53+ `Could not locate @takumi-rs/wasm/pkg/takumi_wasm_bg.wasm. Tried: ${ candidatePaths . join ( ', ' ) } ` ,
54+ )
55+ }
56+
57+ function tryRequireResolve ( specifier : string ) : string | null {
58+ try {
59+ return createRequire ( import . meta. url ) . resolve ( specifier )
60+ } catch {
61+ return null
62+ }
63+ }
64+
65+ function findInPnpmStore ( pkgPrefix : string , relPath : string ) : string | null {
66+ const pnpmDir = join ( process . cwd ( ) , 'node_modules' , '.pnpm' )
67+ if ( ! existsSync ( pnpmDir ) ) return null
68+ for ( const entry of readdirSync ( pnpmDir ) ) {
69+ if ( entry . startsWith ( pkgPrefix ) ) {
70+ const candidate = join ( pnpmDir , entry , relPath )
71+ if ( existsSync ( candidate ) ) return candidate
72+ }
73+ }
74+ return null
75+ }
76+
1577type GenerateInput = {
1678 libraryId : LibraryId | string
1779 title ?: string
@@ -50,6 +112,9 @@ export function generateOgImageResponse(
50112 width : 1200 ,
51113 height : 630 ,
52114 format : 'png' ,
115+ // Passing `module` switches takumi-js's renderer to WASM (see
116+ // takumi-js/dist/render-*.mjs `getImports`).
117+ module : loadTakumiWasm ( ) ,
53118 fonts : [
54119 {
55120 name : 'Inter' ,
0 commit comments