From 03f7738027fd118c4b70409d75cc82a2720af121 Mon Sep 17 00:00:00 2001 From: Varun Ramesh Date: Sat, 2 Sep 2023 14:16:40 -0700 Subject: [PATCH] Separate out JS-execution code into a 'Kernel' --- src/blog-cells.tsx | 47 ++++++------------- src/javascript-kernel/index.ts | 45 ++++++++++++++++++ src/javascript-kernel/types.ts | 19 ++++++++ .../worker.ts} | 2 +- src/kernel.ts | 11 +++++ 5 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 src/javascript-kernel/index.ts create mode 100644 src/javascript-kernel/types.ts rename src/{blog-cells-worker.ts => javascript-kernel/worker.ts} (98%) create mode 100644 src/kernel.ts diff --git a/src/blog-cells.tsx b/src/blog-cells.tsx index 87220fb..d05c7ea 100644 --- a/src/blog-cells.tsx +++ b/src/blog-cells.tsx @@ -1,9 +1,5 @@ -// @ts-ignore -import WORKER_SRC from "!raw-loader!ts-loader!./blog-cells-worker.ts"; - import { history, defaultKeymap, historyKeymap } from "@codemirror/commands"; import { EditorView, lineNumbers, keymap } from "@codemirror/view"; -import { javascript } from "@codemirror/lang-javascript"; import { oneDark } from "@codemirror/theme-one-dark"; import * as React from "react"; @@ -13,18 +9,12 @@ import * as ReactDOM from "react-dom/client"; const SCRIPT_URL = import.meta.url; const SCRIPT_DIR = SCRIPT_URL.substring(0, SCRIPT_URL.lastIndexOf("/")); -// Create webworker. -const blob = new Blob([WORKER_SRC], { type: "application/javascript" }); -const worker: Worker = new Worker(URL.createObjectURL(blob)); - -let requestID = 0; -function getRequestID() { - return requestID++; -} - const editors: any[] = []; const events = new EventTarget(); +import { JavaScriptKernel } from "./javascript-kernel"; +const kernel = new JavaScriptKernel(); + class Cell extends React.Component< { code: string; @@ -139,7 +129,7 @@ class Cell extends React.Component< extensions: [ history(), lineNumbers(), - javascript(), + kernel.getSyntaxHighlighter(), oneDark, keymap.of([...defaultKeymap, ...historyKeymap]), ], @@ -166,21 +156,19 @@ class Cell extends React.Component< this.state.output = []; } - const requestID = getRequestID(); - - worker.postMessage({ - kind: "run-code", - code: code, - requestID: requestID, - }); - const minimumWait = new Promise((resolve, reject) => { setTimeout(() => resolve(), 500); }); - const messageHandler = async (e) => { - if (e.data.requestID != requestID) return; - if (e.data.kind === "run-code-done") { + kernel.run( + code, + (line) => { + this.setState((state) => { + state.output.push(line); + return state; + }); + }, + async () => { // Wait the minimum amount of run-time. await minimumWait; @@ -191,15 +179,8 @@ class Cell extends React.Component< state.output.push({ type: "log", line: "Done." }); return state; }); - worker.removeEventListener("message", messageHandler); - } else if (e.data.kind === "run-code-output") { - this.setState((state) => { - state.output.push(e.data.output); - return state; - }); } - }; - worker.addEventListener("message", messageHandler); + ); } } diff --git a/src/javascript-kernel/index.ts b/src/javascript-kernel/index.ts new file mode 100644 index 0000000..5120968 --- /dev/null +++ b/src/javascript-kernel/index.ts @@ -0,0 +1,45 @@ +// @ts-ignore - Load worker source code. +import WORKER_SRC from "!raw-loader!ts-loader!./worker.ts"; +const blob = new Blob([WORKER_SRC], { type: "application/javascript" }); + +import { javascript } from "@codemirror/lang-javascript"; +import { Kernel, OutputLine } from "../kernel"; +import { ExecutionRequest, ExecutionResponse } from "./types"; + +export class JavaScriptKernel implements Kernel { + worker: Worker = new Worker(URL.createObjectURL(blob)); + + requestID: number = 0; + getRequestID() { + return this.requestID++; + } + + run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void) { + // Generate a unique ID to track this execution request. + const requestID = this.getRequestID(); + + const messageHandler = (e: MessageEvent) => { + if (e.data.requestID != requestID) return; + + if (e.data.kind === "run-code-output") { + onOutput(e.data.output); + } else if (e.data.kind === "run-code-done") { + this.worker.removeEventListener("message", messageHandler); + onDone(); + } + }; + + this.worker.addEventListener("message", messageHandler); + + // Post the code to the worker. + this.worker.postMessage({ + kind: "run-code", + code: code, + requestID: requestID, + } as ExecutionRequest); + } + + getSyntaxHighlighter() { + return javascript(); + } +} \ No newline at end of file diff --git a/src/javascript-kernel/types.ts b/src/javascript-kernel/types.ts new file mode 100644 index 0000000..b681a98 --- /dev/null +++ b/src/javascript-kernel/types.ts @@ -0,0 +1,19 @@ +import { OutputLine } from "../kernel"; + +export type ExecutionRequest = { + kind: "run-code"; + code: string; + requestID: number; +} + +export type ExecutionResponse = { + kind: "run-code-output"; + output: OutputLine; + requestID: number; +} | { + kind: "run-code-done"; + requestID: number; +} | { + kind: "run-code-waiting"; + requestID: number; +}; \ No newline at end of file diff --git a/src/blog-cells-worker.ts b/src/javascript-kernel/worker.ts similarity index 98% rename from src/blog-cells-worker.ts rename to src/javascript-kernel/worker.ts index 789604b..1659c16 100644 --- a/src/blog-cells-worker.ts +++ b/src/javascript-kernel/worker.ts @@ -147,7 +147,7 @@ ${transform(code, Object.keys(this.module))}`; const executor = new Executor(); -self.onmessage = async (e) => { +self.onmessage = async (e: MessageEvent) => { console.log("Worker received message: %o", e); const requestID = e.data.requestID; if (e.data.kind === "run-code") { diff --git a/src/kernel.ts b/src/kernel.ts new file mode 100644 index 0000000..75b6506 --- /dev/null +++ b/src/kernel.ts @@ -0,0 +1,11 @@ +import { LanguageSupport } from "@codemirror/language"; + +export type OutputLine = { + type: string; + line: string; +}; + +export interface Kernel { + run(code: string, onOutput: (line: OutputLine) => void, onDone: () => void); + getSyntaxHighlighter(): LanguageSupport; +}