import type { ComponentRegistry } from '../compiler/ComponentRegistry'; /** * Render a Svelte component to a standalone HTML email body through the registry's stateless `renderStatic` path, * collect its scoped CSS, then inline that into `style=""` attributes with `@css-inline/css-inline-wasm` for * email-client compatibility. Media queries and pseudo-classes that resist inlining stay in a `` : ''}${body}`; const { inline } = await loadCssInline(); return inline(doc, { keepStyleTags: true }); } // The `-wasm` build stands in for the default `@css-inline/css-inline`, whose native N-API addons ship per-platform ABI // binaries through optionalDependencies; one portable `.wasm` keeps installs binary-free and identical everywhere, // Docker image included. // // `initWasm` must run once before `inline()` is usable and refuses to run twice, so the load+init promise is cached. // The bytes go to `initWasm` directly, since a relative `fetch()` has no origin to hit server-side. let cssInlinePromise: Promise | undefined; function loadCssInline(): Promise { if (!cssInlinePromise) { cssInlinePromise = (async () => { const mod = await import('@css-inline/css-inline-wasm'); const wasmBytes = await Bun.file(new URL(import.meta.resolve('@css-inline/css-inline-wasm/index_bg.wasm'))).arrayBuffer(); // wasm-bindgen deprecated the positional `initWasm(bytes)` form the shipped `.d.ts` still types, in favour of // this object form; the cast is needed because the object form isn't in those types. await mod.initWasm({ module_or_path: wasmBytes } as unknown as Parameters[0]); return mod; })().catch((err) => { cssInlinePromise = undefined; throw err; }); } return cssInlinePromise; } function stripScriptsAndStyles(html: string): string { return new HTMLRewriter() .on('script, style', { element: (el) => { el.remove(); }, }) .transform(html); }