From a7f03f9b8b4014a81620aabc1616f5fc654c188f Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 21 Dec 2023 21:45:11 +0100 Subject: [PATCH 1/5] feat(wasm): universal support with esm import syntax --- package.json | 1 - src/presets/vercel.ts | 3 +++ src/rollup/plugins/wasm.ts | 33 +++++++++++++++------------ test/fixture/routes/_ignored.ts | 3 --- test/fixture/routes/wasm/dynamic.ts | 14 +----------- test/fixture/routes/wasm/static.tsa | 5 ++++ test/fixture/tsconfig.json | 3 ++- test/fixture/wasm/sum.d.ts | 1 + test/presets/cloudflare-pages.test.ts | 1 + 9 files changed, 31 insertions(+), 33 deletions(-) delete mode 100644 test/fixture/routes/_ignored.ts create mode 100644 test/fixture/routes/wasm/static.tsa create mode 100644 test/fixture/wasm/sum.d.ts diff --git a/package.json b/package.json index 6552ee801a..0f627c4a81 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-wasm": "^6.2.2", "@rollup/pluginutils": "^5.1.0", "@types/http-proxy": "^1.17.14", "@vercel/nft": "^0.24.4", diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index 7863464815..a0ea8f0165 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -103,6 +103,9 @@ export const vercelEdge = defineNitroPreset({ process: undefined, }, }, + wasm: { + esmImport: true, + }, hooks: { "rollup:before": (nitro: Nitro) => { deprecateSWR(nitro); diff --git a/src/rollup/plugins/wasm.ts b/src/rollup/plugins/wasm.ts index 2a92a605c6..d6fcadd26e 100644 --- a/src/rollup/plugins/wasm.ts +++ b/src/rollup/plugins/wasm.ts @@ -2,17 +2,12 @@ import { createHash } from "node:crypto"; import { promises as fs, existsSync } from "node:fs"; import { basename, normalize } from "pathe"; import type { Plugin } from "rollup"; -import wasmBundle from "@rollup/plugin-wasm"; import MagicString from "magic-string"; import { WasmOptions } from "../../types"; -export function wasm(options: WasmOptions): Plugin { - return options.esmImport ? wasmImport() : wasmBundle(options.rollup); -} - const WASM_ID_PREFIX = "\0nitro-wasm:"; -export function wasmImport(): Plugin { +export function wasm(opts: WasmOptions): Plugin { type WasmAssetInfo = { fileName: string; id: string; @@ -22,7 +17,6 @@ export function wasmImport(): Plugin { const wasmSources = new Map(); const wasmImports = new Map(); - const wasmGlobals = new Map(); return { name: "nitro:wasm", @@ -80,6 +74,7 @@ export function wasmImport(): Plugin { return { code: `export default "${asset.id}";`, map: null, + syntheticNamedExports: true, }; }, renderChunk(code, chunk, options) { @@ -90,8 +85,6 @@ export function wasmImport(): Plugin { return; } - const isIIFE = options.format === "iife" || options.format === "umd"; - const s = new MagicString(code); const resolveImport = (id) => { @@ -103,9 +96,12 @@ export function wasmImport(): Plugin { return null; } const nestedLevel = chunk.fileName.split("/").length - 1; - return ( - (nestedLevel ? "../".repeat(nestedLevel) : "./") + asset.fileName - ); + const relativeId = + (nestedLevel ? "../".repeat(nestedLevel) : "./") + asset.fileName; + return { + relativeId, + asset, + }; }; const ReplaceRE = new RegExp(`"(${WASM_ID_PREFIX}[^"]+)"`, "g"); @@ -117,10 +113,17 @@ export function wasmImport(): Plugin { ); continue; } - let code = `await import("${resolved}").then(r => r?.default || r);`; - if (isIIFE) { - code = `undefined /* not supported */`; + + let dataCode: string; + if (opts.esmImport) { + dataCode = `await import("${resolved.relativeId}").then(r => r?.default || r)`; + } else { + const base64Str = resolved.asset.source.toString("base64"); + dataCode = `(()=>{const d=atob("${base64Str}");const s=d.length;const b=new Uint8Array(s);for(let i=0;i r?.exports || r?.instance?.exports || r);`; + s.overwrite(match.index, match.index + match[0].length, code); } if (s.hasChanged()) { diff --git a/test/fixture/routes/_ignored.ts b/test/fixture/routes/_ignored.ts deleted file mode 100644 index c2c27dadee..0000000000 --- a/test/fixture/routes/_ignored.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default eventHandler((event) => { - throw createError("This file should be ignored!"); -}); diff --git a/test/fixture/routes/wasm/dynamic.ts b/test/fixture/routes/wasm/dynamic.ts index bb5cac6747..eff110eec6 100644 --- a/test/fixture/routes/wasm/dynamic.ts +++ b/test/fixture/routes/wasm/dynamic.ts @@ -1,18 +1,6 @@ export default defineLazyEventHandler(async () => { - const { sum } = await importWasm(import("~/wasm/sum.wasm" as string)); - // const { sum } = await importWasm(import("../../wasm/sum.wasm" as string)); + const { sum } = await import("~/wasm/sum.wasm"); return eventHandler(() => { return `2+3=${sum(2, 3)}`; }); }); - -// TODO: Extract as reusable utility once stable -async function importWasm(input: any) { - const _input = await input; - const _module = _input.default || _input; - const _instance = - typeof _module === "function" - ? await _module({}).then((r) => r.instance || r) - : await WebAssembly.instantiate(_module, {}); - return _instance.exports; -} diff --git a/test/fixture/routes/wasm/static.tsa b/test/fixture/routes/wasm/static.tsa new file mode 100644 index 0000000000..ae0e9abf3f --- /dev/null +++ b/test/fixture/routes/wasm/static.tsa @@ -0,0 +1,5 @@ +import { sum } from "~/wasm/sum.wasm"; + +export default eventHandler(() => { + return `2+3=${sum(2, 3)}`; +}); diff --git a/test/fixture/tsconfig.json b/test/fixture/tsconfig.json index b0057ac7b8..cc71230971 100644 --- a/test/fixture/tsconfig.json +++ b/test/fixture/tsconfig.json @@ -9,7 +9,8 @@ "paths": { "nitropack": ["../../src/index"], "#internal/nitro": ["../../src/runtime/index"], - "#internal/nitro/*": ["../../src/runtime/*"] + "#internal/nitro/*": ["../../src/runtime/*"], + "~/wasm/sum.wasm": ["./wasm/sum.d.ts"] } } } diff --git a/test/fixture/wasm/sum.d.ts b/test/fixture/wasm/sum.d.ts new file mode 100644 index 0000000000..3f529c1298 --- /dev/null +++ b/test/fixture/wasm/sum.d.ts @@ -0,0 +1 @@ +export declare function sum(a: number, b: number): number; diff --git a/test/presets/cloudflare-pages.test.ts b/test/presets/cloudflare-pages.test.ts index 9180cdc011..f3b6fc4830 100644 --- a/test/presets/cloudflare-pages.test.ts +++ b/test/presets/cloudflare-pages.test.ts @@ -13,6 +13,7 @@ describe("nitro:preset:cloudflare-pages", async () => { const mf = new Miniflare({ modules: true, scriptPath: resolve(ctx.outDir, "_worker.js", "index.js"), + modulesRules: [{ type: "CompiledWasm", include: ["**/*.wasm"] }], globals: { __env__: {} }, compatibilityFlags: ["streams_enable_constructors"], bindings: { From ea4918e121fb03085604739215d7c2bb1ab68fab Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 21 Dec 2023 21:47:04 +0100 Subject: [PATCH 2/5] rename back --- test/fixture/routes/wasm/{static.tsa => static.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/fixture/routes/wasm/{static.tsa => static.ts} (100%) diff --git a/test/fixture/routes/wasm/static.tsa b/test/fixture/routes/wasm/static.ts similarity index 100% rename from test/fixture/routes/wasm/static.tsa rename to test/fixture/routes/wasm/static.ts From 7fd41b6b7e202c65779631d1c15a4f0b68ea6f01 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 21 Dec 2023 21:55:03 +0100 Subject: [PATCH 3/5] chore: update lockfile --- pnpm-lock.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 704812a32c..ed85180008 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,9 +39,6 @@ importers: '@rollup/plugin-terser': specifier: ^0.4.4 version: 0.4.4(rollup@4.8.0) - '@rollup/plugin-wasm': - specifier: ^6.2.2 - version: 6.2.2(rollup@4.8.0) '@rollup/pluginutils': specifier: ^5.1.0 version: 5.1.0(rollup@4.8.0) @@ -1642,19 +1639,6 @@ packages: terser: 5.24.0 dev: false - /@rollup/plugin-wasm@6.2.2(rollup@4.8.0): - resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.8.0) - rollup: 4.8.0 - dev: false - /@rollup/pluginutils@4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} From 86c7680e0552644bad7900a77d9f20719cc715ce Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 21 Dec 2023 22:29:09 +0100 Subject: [PATCH 4/5] update types --- src/types/nitro.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/types/nitro.ts b/src/types/nitro.ts index d1965a891b..d9f4797894 100644 --- a/src/types/nitro.ts +++ b/src/types/nitro.ts @@ -7,7 +7,6 @@ import type { NestedHooks, Hookable } from "hookable"; import type { ConsolaInstance, LogLevel } from "consola"; import type { WatchOptions } from "chokidar"; import type { RollupCommonJSOptions } from "@rollup/plugin-commonjs"; -import type { RollupWasmOptions } from "@rollup/plugin-wasm"; import type { Storage, BuiltinDriverName } from "unstorage"; import type { ProxyServerOptions } from "httpxy"; import type { ProxyOptions, RouterMethod } from "h3"; @@ -198,9 +197,9 @@ export interface WasmOptions { esmImport?: boolean; /** - * Options for `@rollup/plugin-wasm`, only used when `esmImport` is `false` + * @deprecated */ - rollup?: RollupWasmOptions; + rollup?: unknown; } export interface NitroFrameworkInfo { From 407672e35b9d69a60300f8228dc7415c1f4c106c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 21 Dec 2023 22:50:32 +0100 Subject: [PATCH 5/5] feat: lazy mode for better compatibility --- src/presets/cloudflare-module.ts | 4 ++++ src/presets/cloudflare-pages.ts | 4 ++++ src/presets/cloudflare.ts | 2 +- src/presets/vercel.ts | 1 + src/rollup/plugins/wasm.ts | 8 ++++++-- src/types/nitro.ts | 5 ++++- test/fixture/routes/wasm/static.ts | 5 +++-- 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/presets/cloudflare-module.ts b/src/presets/cloudflare-module.ts index bdb91f439c..c18005ae4a 100644 --- a/src/presets/cloudflare-module.ts +++ b/src/presets/cloudflare-module.ts @@ -19,6 +19,10 @@ export const cloudflareModule = defineNitroPreset({ inlineDynamicImports: false, }, }, + wasm: { + lazy: false, + esmImport: true, + }, hooks: { async compiled(nitro: Nitro) { await writeFile( diff --git a/src/presets/cloudflare-pages.ts b/src/presets/cloudflare-pages.ts index be1e843f17..e4b2439ff1 100644 --- a/src/presets/cloudflare-pages.ts +++ b/src/presets/cloudflare-pages.ts @@ -29,6 +29,10 @@ export const cloudflarePages = defineNitroPreset({ // https://github.com/unjs/nitro/pull/933 _mime: "mime/index.js", }, + wasm: { + lazy: false, + esmImport: true, + }, rollupConfig: { output: { entryFileNames: "index.js", diff --git a/src/presets/cloudflare.ts b/src/presets/cloudflare.ts index 04ee70aad8..93331a2485 100644 --- a/src/presets/cloudflare.ts +++ b/src/presets/cloudflare.ts @@ -12,7 +12,7 @@ export const cloudflare = defineNitroPreset({ deploy: "npx wrangler deploy", }, wasm: { - esmImport: true, + lazy: true, }, hooks: { async compiled(nitro: Nitro) { diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index a0ea8f0165..10e231684e 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -104,6 +104,7 @@ export const vercelEdge = defineNitroPreset({ }, }, wasm: { + lazy: true, esmImport: true, }, hooks: { diff --git a/src/rollup/plugins/wasm.ts b/src/rollup/plugins/wasm.ts index d6fcadd26e..8b29855087 100644 --- a/src/rollup/plugins/wasm.ts +++ b/src/rollup/plugins/wasm.ts @@ -119,10 +119,14 @@ export function wasm(opts: WasmOptions): Plugin { dataCode = `await import("${resolved.relativeId}").then(r => r?.default || r)`; } else { const base64Str = resolved.asset.source.toString("base64"); - dataCode = `(()=>{const d=atob("${base64Str}");const s=d.length;const b=new Uint8Array(s);for(let i=0;i{const d=atob("${base64Str}");const s=d.length;const b=new Uint8Array(s);for(let i=0;i r?.exports || r?.instance?.exports || r);`; + let code = `await WebAssembly.instantiate(${dataCode}).then(r => r?.exports||r?.instance?.exports || r);`; + + if (opts.lazy) { + code = `(()=>{const e=async()=>{return ${code}};let _p;const p=()=>{if(!_p)_p=e();return _p;};return {then:cb=>p().then(cb),catch:cb=>p().catch(cb)}})()`; + } s.overwrite(match.index, match.index + match[0].length, code); } diff --git a/src/types/nitro.ts b/src/types/nitro.ts index d9f4797894..7c90ead853 100644 --- a/src/types/nitro.ts +++ b/src/types/nitro.ts @@ -197,8 +197,11 @@ export interface WasmOptions { esmImport?: boolean; /** - * @deprecated + * Import `.wasm` files using a lazily evaluated promise for compatibility */ + lazy?: boolean; + + /** @deprecated */ rollup?: unknown; } diff --git a/test/fixture/routes/wasm/static.ts b/test/fixture/routes/wasm/static.ts index ae0e9abf3f..2499aec520 100644 --- a/test/fixture/routes/wasm/static.ts +++ b/test/fixture/routes/wasm/static.ts @@ -1,5 +1,6 @@ -import { sum } from "~/wasm/sum.wasm"; +import _mod from "~/wasm/sum.wasm"; -export default eventHandler(() => { +export default eventHandler(async () => { + const { sum } = await _mod; return `2+3=${sum(2, 3)}`; });