Skip to content

Commit eb037e3

Browse files
committed
refactor: use a WASM-based renderer for user pubpages.
This prepares for custom user themes and renderers.
1 parent f6a42cb commit eb037e3

File tree

8 files changed

+3198
-193
lines changed

8 files changed

+3198
-193
lines changed

src/lib/renderer/index.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { browser, building, dev } from '$app/environment';
2+
import { env } from '$env/dynamic/public';
3+
4+
type RendererExports = {
5+
memory: WebAssembly.Memory;
6+
OUTPUT: WebAssembly.Global<'i32'>;
7+
drop_output(): void;
8+
wasm_alloc(size: number, align: number): number;
9+
wasm_dealloc(ptr: number, size: number, align: number): void;
10+
wasm_render(
11+
profile_data_json_ptr: number,
12+
profile_data_json_len: number,
13+
theme_data_ptr: number,
14+
theme_data_len: number
15+
): [number, number];
16+
};
17+
18+
let wasm: RendererExports;
19+
20+
const encoder = new TextEncoder();
21+
const decoder = new TextDecoder();
22+
23+
const wasmImports = {
24+
console: {
25+
error(ptr: number, len: number) {
26+
console.error(decoder.decode(wasm.memory.buffer.slice(ptr, ptr + len)));
27+
}
28+
}
29+
};
30+
if (!building) {
31+
if (browser) {
32+
const resp = fetch('/renderers/minijinja.wasm');
33+
wasm = (await WebAssembly.instantiateStreaming(resp, wasmImports)).instance
34+
.exports as RendererExports;
35+
} else {
36+
const filePath = dev
37+
? './static/renderers/minijinja.wasm'
38+
: './client/renderers/minijinja.wasm';
39+
const wasmData = await (await import('fs')).promises.readFile(filePath);
40+
wasm = (await WebAssembly.instantiate(wasmData, wasmImports)).instance
41+
.exports as RendererExports;
42+
}
43+
}
44+
45+
const getOutputPtrLen = () => {
46+
const wasmDataView = new DataView(
47+
wasm.memory.buffer.slice(wasm.OUTPUT.value, wasm.OUTPUT.value + 8)
48+
);
49+
return [wasmDataView.getInt32(0, true), wasmDataView.getInt32(4, true)];
50+
};
51+
52+
export type ProfileData = {
53+
instance_info: {
54+
url: string;
55+
};
56+
handle: string;
57+
display_name?: string;
58+
bio?: string;
59+
tags?: string[];
60+
links?: { url: string; label?: string }[];
61+
pages?: { slug: string; name?: string }[];
62+
};
63+
64+
export function render(
65+
profileData: Omit<ProfileData, 'instance_info'>,
66+
themeData: Uint8Array
67+
): string {
68+
const profileDataJson = JSON.stringify({
69+
...profileData,
70+
...{ instance_info: { url: env.PUBLIC_URL } }
71+
} as ProfileData);
72+
const profileDataJsonBinary = encoder.encode(profileDataJson);
73+
const profileDataJsonBinaryLen = profileDataJsonBinary.length;
74+
const profileDataJsonPtr = wasm.wasm_alloc(profileDataJsonBinaryLen, 1);
75+
76+
const themeDataLen = themeData.length;
77+
const themeDataPtr = wasm.wasm_alloc(themeDataLen, 1);
78+
79+
const view = new Uint8Array(wasm.memory.buffer);
80+
view.set(profileDataJsonBinary, profileDataJsonPtr);
81+
view.set(themeData, themeDataPtr);
82+
83+
wasm.wasm_render(profileDataJsonPtr, profileDataJsonBinaryLen, themeDataPtr, themeDataLen);
84+
const [renderedPtr, renderedLen] = getOutputPtrLen();
85+
86+
const renderedData = wasm.memory.buffer.slice(renderedPtr, renderedPtr + renderedLen);
87+
const renderedString = decoder.decode(renderedData);
88+
89+
wasm.drop_output();
90+
91+
wasm.wasm_dealloc(themeDataPtr, themeDataLen, 1);
92+
wasm.wasm_dealloc(profileDataJsonPtr, profileDataJsonBinaryLen, 1);
93+
94+
return renderedString;
95+
}

0 commit comments

Comments
 (0)