0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-12-10 21:54:58 +01:00
2025-12-05 20:01:56 -08:00

164 lines
4.7 KiB
JavaScript

const wasmUrl = new URL('plugin.wasm', import.meta.url);
const wasmExecUrl = new URL('wasm_exec.js', import.meta.url);
let wasmBridgePromise;
let styleInjected = false;
function injectScriptOnce(url) {
return new Promise((resolve, reject) => {
const existing = document.querySelector(`script[data-go-runtime="${url.href}"]`);
if (existing) {
if (existing.dataset.loaded === 'true') {
resolve();
} else {
existing.addEventListener('load', resolve, {once: true});
existing.addEventListener('error', reject, {once: true});
}
return;
}
const script = document.createElement('script');
script.dataset.goRuntime = url.href;
script.src = url.href;
script.async = true;
script.addEventListener('load', () => {
script.dataset.loaded = 'true';
resolve();
}, {once: true});
script.addEventListener('error', reject, {once: true});
document.head.appendChild(script);
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForExport(name, timeoutMs = 2000) {
const start = Date.now();
while (typeof globalThis[name] !== 'function') {
if (Date.now() - start > timeoutMs) {
throw new Error(`Go runtime did not expose ${name} within ${timeoutMs}ms`);
}
await sleep(20);
}
return globalThis[name];
}
async function ensureWasmBridge() {
if (!wasmBridgePromise) {
wasmBridgePromise = (async () => {
if (typeof globalThis.Go === 'undefined') {
await injectScriptOnce(wasmExecUrl);
}
if (typeof globalThis.Go === 'undefined') {
throw new Error('Go runtime (wasm_exec.js) is unavailable');
}
const go = new globalThis.Go();
let result;
const fetchRequest = fetch(wasmUrl);
if (WebAssembly.instantiateStreaming) {
try {
result = await WebAssembly.instantiateStreaming(fetchRequest, go.importObject);
} catch (err) {
console.warn('instantiateStreaming failed; falling back to ArrayBuffer', err);
const buffer = await (await fetchRequest).arrayBuffer();
result = await WebAssembly.instantiate(buffer, go.importObject);
}
} else {
const buffer = await (await fetchRequest).arrayBuffer();
result = await WebAssembly.instantiate(buffer, go.importObject);
}
go.run(result.instance);
const processFile = await waitForExport('wasmProcessFile');
return {
process(content) {
const output = processFile(content);
return typeof output === 'string' ? output : String(output ?? '');
},
};
})();
}
return wasmBridgePromise;
}
async function fetchFileText(fileUrl) {
const response = await window.fetch(fileUrl, {headers: {'Accept': 'text/plain'}});
if (!response.ok) {
throw new Error(`failed to fetch file (${response.status})`);
}
return response.text();
}
function ensureStyles() {
if (styleInjected) return;
styleInjected = true;
const style = document.createElement('style');
style.textContent = `
.go-wasm-renderer {
font-family: var(--fonts-proportional, system-ui);
border: 1px solid var(--color-secondary);
border-radius: 6px;
overflow: hidden;
}
.go-wasm-renderer__header {
margin: 0;
padding: 0.75rem 1rem;
background: var(--color-secondary-alpha-20);
font-weight: 600;
}
.go-wasm-renderer pre {
margin: 0;
padding: 1rem;
background: var(--color-box-body);
font-family: var(--fonts-monospace, SFMono-Regular, monospace);
white-space: pre;
overflow-x: auto;
}
.go-wasm-renderer__error {
color: var(--color-danger);
}
`;
document.head.appendChild(style);
}
function renderError(container, message) {
container.innerHTML = '';
const wrapper = document.createElement('div');
wrapper.className = 'go-wasm-renderer';
const header = document.createElement('div');
header.className = 'go-wasm-renderer__header';
header.textContent = 'Go WASM Renderer';
const body = document.createElement('pre');
body.className = 'go-wasm-renderer__error';
body.textContent = message;
wrapper.append(header, body);
container.appendChild(wrapper);
}
export default {
name: 'Go WASM Renderer',
async render(container, fileUrl) {
ensureStyles();
try {
const [bridge, content] = await Promise.all([
ensureWasmBridge(),
fetchFileText(fileUrl),
]);
const processed = await bridge.process(content);
const wrapper = document.createElement('div');
wrapper.className = 'go-wasm-renderer';
const header = document.createElement('div');
header.className = 'go-wasm-renderer__header';
header.textContent = 'Go WASM Renderer';
const body = document.createElement('pre');
body.textContent = processed;
wrapper.append(header, body);
container.innerHTML = '';
container.appendChild(wrapper);
} catch (err) {
console.error('Go WASM plugin failed', err);
renderError(container, `Unable to render file: ${err.message}`);
}
},
};