mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-10 21:54:58 +01:00
164 lines
4.7 KiB
JavaScript
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}`);
|
|
}
|
|
},
|
|
};
|