From de91b7cd1b3266ae9a1b4d6fe1999d3b8a7c70e5 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Fri, 19 Jan 2024 11:43:01 +0000 Subject: [PATCH] Run compiler and code in web worker --- static/index.js | 75 +++++++++--------------------------------------- static/worker.js | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 61 deletions(-) create mode 100644 static/worker.js diff --git a/static/index.js b/static/index.js index 3c7ef88..53c20e6 100644 --- a/static/index.js +++ b/static/index.js @@ -1,6 +1,4 @@ import CodeFlask from "https://cdn.jsdelivr.net/npm/codeflask@1.4.1/+esm"; -import initGleamCompiler from "./compiler.js"; -import stdlib from "./stdlib.js"; const output = document.querySelector("#output"); const initialCode = document.querySelector("#code").innerHTML; @@ -38,63 +36,12 @@ const prismGrammar = { /\b(?:0b[0-1]+|0o[0-7]+|[[:digit:]][[:digit:]_]*(\\.[[:digit:]]*)?|0x[[:xdigit:]]+)\b/, }; -// Monkey patch console.log to keep a copy of the output -let logged = ""; -const log = console.log; -console.log = (...args) => { - log(...args); - logged += args.map((e) => `${e}`).join(" ") + "\n"; -}; - -function resetLogCapture() { - logged = ""; -} - -async function compileEval(project, code) { - try { - project.writeModule("main", code); - project.compilePackage("javascript"); - const js = project.readCompiledJavaScript("main"); - const main = await loadProgram(js); - resetLogCapture(); - if (main) main(); - replaceOutput(logged, "log"); - } catch (error) { - console.error(error); - replaceOutput(logged, "log"); - appendOutput(error.toString(), "error"); - } - for (const warning of project.takeWarnings()) { - appendOutput(warning, "warning"); - } -} - -async function loadProgram(js) { - const url = new URL(import.meta.url); - url.pathname = ""; - url.hash = ""; - url.search = ""; - const href = url.toString(); - const js1 = js.replaceAll( - /from\s+"\.\/(.+)"/g, - `from "${href}precompiled/$1"`, - ); - const js2 = btoa(unescape(encodeURIComponent(js1))); - const module = await import("data:text/javascript;base64," + js2); - return module.main; -} - function clearOutput() { while (output.firstChild) { output.removeChild(output.firstChild); } } -function replaceOutput(content, className) { - clearOutput(); - appendOutput(content, className); -} - function appendOutput(content, className) { if (!content) return; const element = document.createElement("pre"); @@ -109,12 +56,6 @@ const editor = new CodeFlask("#editor-target", { editor.addLanguage("gleam", prismGrammar); editor.updateCode(initialCode); -const compiler = await initGleamCompiler(); -const project = compiler.newProject(); -for (const [name, code] of Object.entries(stdlib)) { - project.writeModule(name, code); -} - function debounce(fn, delay) { let timer = null; return (...args) => { @@ -123,5 +64,17 @@ function debounce(fn, delay) { }; } -editor.onUpdate(debounce((code) => compileEval(project, code), 200)); -compileEval(project, initialCode); +const worker = new Worker("worker.js", { type: "module" }); + +worker.onmessage = (event) => { + const result = event.data; + clearOutput(); + if (result.log) appendOutput(result.log, "log"); + if (result.error) appendOutput(result.error, "error"); + for (const warning of result.warnings) { + appendOutput(warning, "warning"); + } +}; + +editor.onUpdate(debounce((code) => worker.postMessage(code), 200)); +worker.postMessage(initialCode); diff --git a/static/worker.js b/static/worker.js new file mode 100644 index 0000000..9b975db --- /dev/null +++ b/static/worker.js @@ -0,0 +1,67 @@ +import initGleamCompiler from "./compiler.js"; +import stdlib from "./stdlib.js"; + +const compiler = await initGleamCompiler(); +const project = compiler.newProject(); + +for (const [name, code] of Object.entries(stdlib)) { + project.writeModule(name, code); +} + +// Monkey patch console.log to keep a copy of the output +let logged = ""; +const log = console.log; +console.log = (...args) => { + log(...args); + logged += args.map((e) => `${e}`).join(" ") + "\n"; +}; + +function resetLogCapture() { + logged = ""; +} + +async function loadProgram(js) { + const url = new URL(import.meta.url); + url.pathname = ""; + url.hash = ""; + url.search = ""; + const href = url.toString(); + const js1 = js.replaceAll( + /from\s+"\.\/(.+)"/g, + `from "${href}precompiled/$1"`, + ); + const js2 = btoa(unescape(encodeURIComponent(js1))); + const module = await import("data:text/javascript;base64," + js2); + return module.main; +} + +async function compileEval(code) { + const result = { + log: null, + error: null, + warnings: [], + }; + + try { + project.writeModule("main", code); + project.compilePackage("javascript"); + const js = project.readCompiledJavaScript("main"); + const main = await loadProgram(js); + resetLogCapture(); + if (main) main(); + } catch (error) { + console.error(error); + result.error = error.toString(); + } + for (const warning of project.takeWarnings()) { + result.warnings.push(warning); + } + result.log = logged; + + return result; +} + +self.onmessage = async (event) => { + const result = compileEval(event.data); + postMessage(await result); +};