wasm-idle 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +77 -11
  2. package/dist/clangd/session.js +1 -1
  3. package/dist/clangd/worker.js +1 -1
  4. package/dist/lsp/cpp/server.js +1 -1
  5. package/dist/lsp/python/server.js +1 -1
  6. package/dist/playground/assets.d.ts +43 -0
  7. package/dist/playground/assets.js +164 -0
  8. package/dist/playground/elixir.d.ts +32 -0
  9. package/dist/playground/elixir.js +173 -0
  10. package/dist/playground/go.d.ts +31 -0
  11. package/dist/playground/go.js +166 -0
  12. package/dist/playground/index.js +36 -1
  13. package/dist/playground/ocaml.d.ts +32 -0
  14. package/dist/playground/ocaml.js +341 -0
  15. package/dist/playground/options.d.ts +4 -0
  16. package/dist/playground/tinygo.d.ts +75 -0
  17. package/dist/playground/tinygo.js +510 -0
  18. package/dist/playground/wasmElixirVersion.d.ts +1 -0
  19. package/dist/playground/wasmElixirVersion.js +1 -0
  20. package/dist/playground/wasmGoVersion.d.ts +1 -0
  21. package/dist/playground/wasmGoVersion.js +1 -0
  22. package/dist/playground/wasmOcamlVersion.d.ts +1 -0
  23. package/dist/playground/wasmOcamlVersion.js +1 -0
  24. package/dist/playground/wasmRustVersion.d.ts +1 -1
  25. package/dist/playground/wasmRustVersion.js +1 -1
  26. package/dist/playground/wasmTinyGoVersion.d.ts +1 -0
  27. package/dist/playground/wasmTinyGoVersion.js +1 -0
  28. package/dist/playground/worker/elixir.d.ts +1 -0
  29. package/dist/playground/worker/elixir.js +591 -0
  30. package/dist/playground/worker/go.d.ts +1 -0
  31. package/dist/playground/worker/go.js +163 -0
  32. package/dist/playground/worker/ocaml.d.ts +1 -0
  33. package/dist/playground/worker/ocaml.js +449 -0
  34. package/dist/playground/worker/tinygo.d.ts +1 -0
  35. package/dist/playground/worker/tinygo.js +140 -0
  36. package/dist/terminal/Terminal.svelte +69 -29
  37. package/dist/utils/vscodeJsonrpcBrowser.d.ts +5 -0
  38. package/dist/utils/vscodeJsonrpcBrowser.js +4 -0
  39. package/package.json +33 -21
@@ -0,0 +1,140 @@
1
+ import { ConsoleStdout, Fd, WASI, WASIProcExit, wasi } from '@bjorn3/browser_wasi_shim';
2
+ import { waitForBufferedStdin } from '../stdinBuffer';
3
+ self.document = {
4
+ querySelectorAll() {
5
+ return [];
6
+ }
7
+ };
8
+ const encoder = new TextEncoder();
9
+ const decoder = new TextDecoder();
10
+ let stdinBufferTinyGo = null;
11
+ let stdinChunkTinyGo = new Uint8Array(0);
12
+ let stdinChunkOffsetTinyGo = 0;
13
+ class TinyGoStdin extends Fd {
14
+ fd_fdstat_get() {
15
+ const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);
16
+ fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);
17
+ fdstat.fs_rights_inherited = 0n;
18
+ return { ret: wasi.ERRNO_SUCCESS, fdstat };
19
+ }
20
+ fd_filestat_get() {
21
+ return {
22
+ ret: wasi.ERRNO_SUCCESS,
23
+ filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n)
24
+ };
25
+ }
26
+ fd_read(size) {
27
+ if (stdinBufferTinyGo === null) {
28
+ return {
29
+ ret: wasi.ERRNO_SUCCESS,
30
+ data: new Uint8Array(0)
31
+ };
32
+ }
33
+ if (stdinChunkOffsetTinyGo >= stdinChunkTinyGo.length) {
34
+ const nextChunk = waitForBufferedStdin(stdinBufferTinyGo, () => postMessage({ buffer: true }));
35
+ if (nextChunk === null) {
36
+ stdinChunkTinyGo = new Uint8Array(0);
37
+ stdinChunkOffsetTinyGo = 0;
38
+ return {
39
+ ret: wasi.ERRNO_SUCCESS,
40
+ data: new Uint8Array(0)
41
+ };
42
+ }
43
+ stdinChunkTinyGo = encoder.encode(nextChunk);
44
+ stdinChunkOffsetTinyGo = 0;
45
+ }
46
+ const end = Math.min(stdinChunkOffsetTinyGo + size, stdinChunkTinyGo.length);
47
+ const data = stdinChunkTinyGo.slice(stdinChunkOffsetTinyGo, end);
48
+ stdinChunkOffsetTinyGo = end;
49
+ return {
50
+ ret: wasi.ERRNO_SUCCESS,
51
+ data
52
+ };
53
+ }
54
+ }
55
+ self.onmessage = async (event) => {
56
+ const { load, artifact, buffer, args = [], log } = event.data;
57
+ try {
58
+ if (load) {
59
+ postMessage({ load: true });
60
+ return;
61
+ }
62
+ const bytes = artifact instanceof Uint8Array
63
+ ? artifact
64
+ : artifact instanceof ArrayBuffer
65
+ ? new Uint8Array(artifact)
66
+ : ArrayBuffer.isView(artifact)
67
+ ? new Uint8Array(artifact.buffer, artifact.byteOffset, artifact.byteLength)
68
+ : null;
69
+ if (!bytes) {
70
+ throw new Error('TinyGo worker expected a wasm artifact');
71
+ }
72
+ stdinBufferTinyGo = new Int32Array(buffer);
73
+ stdinChunkTinyGo = new Uint8Array(0);
74
+ stdinChunkOffsetTinyGo = 0;
75
+ const stdout = new ConsoleStdout((chunk) => {
76
+ const text = decoder.decode(chunk);
77
+ if (text)
78
+ postMessage({ output: text });
79
+ });
80
+ const stderr = new ConsoleStdout((chunk) => {
81
+ const text = decoder.decode(chunk);
82
+ if (text)
83
+ postMessage({ output: text });
84
+ });
85
+ const wasiRuntime = new WASI(args, ['USER=jungol'], [new TinyGoStdin(), stdout, stderr]);
86
+ const wasmModule = (await WebAssembly.instantiate(bytes, {
87
+ wasi_snapshot_preview1: wasiRuntime.wasiImport,
88
+ wasi_unstable: wasiRuntime.wasiImport
89
+ }));
90
+ const instance = typeof WebAssembly.Instance === 'function' && wasmModule instanceof WebAssembly.Instance
91
+ ? wasmModule
92
+ : 'instance' in wasmModule
93
+ ? wasmModule.instance
94
+ : wasmModule;
95
+ const exportsObject = instance.exports;
96
+ let exitCode = 0;
97
+ try {
98
+ if (typeof exportsObject._start === 'function') {
99
+ exitCode = wasiRuntime.start(instance);
100
+ }
101
+ else if (typeof exportsObject.memory === 'object' &&
102
+ (typeof exportsObject.main === 'function' ||
103
+ typeof exportsObject._initialize === 'function')) {
104
+ wasiRuntime.initialize(instance);
105
+ if (typeof exportsObject._initialize !== 'function' &&
106
+ typeof exportsObject.__wasm_call_ctors === 'function') {
107
+ exportsObject.__wasm_call_ctors();
108
+ }
109
+ if (typeof exportsObject.main === 'function') {
110
+ const mainResult = exportsObject.main();
111
+ exitCode = typeof mainResult === 'number' ? mainResult : 0;
112
+ }
113
+ }
114
+ else {
115
+ throw new Error('TinyGo worker expected a WASI artifact with _start or a reactor artifact with _initialize or main');
116
+ }
117
+ }
118
+ catch (error) {
119
+ if (error instanceof WASIProcExit) {
120
+ exitCode = error.code;
121
+ }
122
+ else {
123
+ throw error;
124
+ }
125
+ }
126
+ if (log) {
127
+ console.log(`[wasm-idle:tinygo-worker] wasi run complete exitCode=${String(exitCode)}`);
128
+ }
129
+ if (exitCode !== 0) {
130
+ throw new Error(`TinyGo program exited with code ${exitCode}`);
131
+ }
132
+ postMessage({ results: true });
133
+ }
134
+ catch (error) {
135
+ if (log) {
136
+ console.error('[wasm-idle:tinygo-worker] failed', error);
137
+ }
138
+ postMessage({ error: error?.message || String(error) });
139
+ }
140
+ };
@@ -61,8 +61,7 @@
61
61
  tc = 0,
62
62
  plugin = $state(),
63
63
  ll: string | null = null,
64
- loadedRuntimeAssets = $state<SandboxRuntimeAssets | undefined>(undefined),
65
- loadedPlayground = $state<PlaygroundBinding | undefined>(undefined),
64
+ loadedRuntimeAssetsKey: string | undefined = undefined,
66
65
  stopRequested = false;
67
66
 
68
67
  function writeTerminalOutput(text: string) {
@@ -99,23 +98,39 @@
99
98
  async function initSandbox(language: string) {
100
99
  const currentPlayground = playground;
101
100
  const currentRuntimeAssets = currentPlayground?.runtimeAssets || runtimeAssets || path;
101
+ const currentRuntimeAssetsKey =
102
+ typeof currentRuntimeAssets === 'string'
103
+ ? currentRuntimeAssets
104
+ : JSON.stringify({
105
+ rootUrl: currentRuntimeAssets?.rootUrl || '',
106
+ pythonBaseUrl: currentRuntimeAssets?.python?.baseUrl || '',
107
+ hasPythonLoader: !!currentRuntimeAssets?.python?.loader,
108
+ javaBaseUrl: currentRuntimeAssets?.java?.baseUrl || '',
109
+ hasJavaLoader: !!currentRuntimeAssets?.java?.loader,
110
+ rustCompilerUrl: currentRuntimeAssets?.rust?.compilerUrl || '',
111
+ goCompilerUrl: currentRuntimeAssets?.go?.compilerUrl || '',
112
+ elixirBundleUrl: currentRuntimeAssets?.elixir?.bundleUrl || '',
113
+ ocamlModuleUrl: currentRuntimeAssets?.ocaml?.moduleUrl || '',
114
+ ocamlManifestUrl: currentRuntimeAssets?.ocaml?.manifestUrl || '',
115
+ tinygoAppUrl: currentRuntimeAssets?.tinygo?.appUrl || '',
116
+ tinygoHostCompileUrl: currentRuntimeAssets?.tinygo?.hostCompileUrl || '',
117
+ tinygoModuleUrl: currentRuntimeAssets?.tinygo?.moduleUrl || '',
118
+ tinygoDisableHostCompile: !!currentRuntimeAssets?.tinygo?.disableHostCompile
119
+ });
120
+ const requiresSandboxReset =
121
+ ll !== language || loadedRuntimeAssetsKey !== currentRuntimeAssetsKey;
102
122
  let _tc = ++tc;
103
123
  await wait();
104
- if (sandbox) await sandbox.clear();
124
+ if (sandbox && requiresSandboxReset) await sandbox.clear();
105
125
  input = '';
106
126
  finish = false;
107
- if (
108
- ll !== language ||
109
- loadedRuntimeAssets !== currentRuntimeAssets ||
110
- loadedPlayground !== currentPlayground
111
- ) {
127
+ if (!sandbox || requiresSandboxReset) {
112
128
  sandbox = currentPlayground
113
129
  ? await currentPlayground.load(language)
114
130
  : await load(language, currentRuntimeAssets);
115
131
  await sandbox.clear();
116
132
  ll = language;
117
- loadedRuntimeAssets = currentRuntimeAssets;
118
- loadedPlayground = currentPlayground;
133
+ loadedRuntimeAssetsKey = currentRuntimeAssetsKey;
119
134
  }
120
135
  sandbox.image = onimage;
121
136
  sandbox.ondebug = ondebug;
@@ -203,9 +218,17 @@
203
218
  ) {
204
219
  prog?.set?.(0);
205
220
  const loadProgress =
206
- language === 'RUST' ? phaseProgress(prog, 0, 0.05) : phaseProgress(prog, 0, 0.85);
221
+ language === 'RUST' ||
222
+ language === 'GO' ||
223
+ language === 'TINYGO' ||
224
+ language === 'OCAML'
225
+ ? phaseProgress(prog, 0, 0.05)
226
+ : phaseProgress(prog, 0, 0.85);
207
227
  const prepareProgress =
208
- language === 'RUST'
228
+ language === 'RUST' ||
229
+ language === 'GO' ||
230
+ language === 'TINYGO' ||
231
+ language === 'OCAML'
209
232
  ? phaseProgress(prog, 0.05, 0.99)
210
233
  : phaseProgress(prog, 0.85, 0.99);
211
234
  await Promise.all([
@@ -298,10 +321,40 @@
298
321
  allowProposedApi: true
299
322
  });
300
323
  term.open(ref);
324
+ term.onData((data: string) => {
325
+ if (!term || finish) return;
326
+ let pendingText = '';
327
+ for (const chunk of data) {
328
+ if (chunk === '\r' || chunk === '\n') {
329
+ if (pendingText) {
330
+ appendInputText(pendingText);
331
+ pendingText = '';
332
+ }
333
+ submitCurrentInput();
334
+ continue;
335
+ }
336
+ if (chunk === '\u007f') {
337
+ if (pendingText) {
338
+ appendInputText(pendingText);
339
+ pendingText = '';
340
+ }
341
+ if (term.buffer.active.cursorX > 0 && input.length > 0) {
342
+ term.write('\b \b');
343
+ input = Array.from(input).slice(0, -1).join('');
344
+ }
345
+ continue;
346
+ }
347
+ if ((chunk.codePointAt(0) || 0) >= 0x20) {
348
+ pendingText += chunk;
349
+ }
350
+ }
351
+ if (pendingText) {
352
+ appendInputText(pendingText);
353
+ }
354
+ });
301
355
  term.onKey((e: { key: string; domEvent: KeyboardEvent }) => {
302
356
  if (!term) return;
303
357
  const ev = e.domEvent;
304
- const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
305
358
  const isCopyShortcut = (ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'c';
306
359
  if (isCopyShortcut && term.hasSelection()) {
307
360
  const selectedText = term.getSelection();
@@ -313,28 +366,15 @@
313
366
  }
314
367
  if (finish) return;
315
368
  onkey?.(ev);
316
- if (ev.key === 'Enter') {
317
- submitCurrentInput();
318
- } else if (ev.key === 'Backspace') {
319
- if (term.buffer.active.cursorX > 0) {
320
- term.write('\b \b');
321
- if (input.length > 0) input = input.substring(0, input.length - 1);
322
- }
323
- } else if (printable) {
324
- if (
325
- (e.key >= String.fromCharCode(0x20) &&
326
- e.key <= String.fromCharCode(0x7e)) ||
327
- e.key >= '\u00a0'
328
- ) {
329
- appendInputText(e.key);
330
- }
331
- } else if (isCopyShortcut) {
369
+ if (isCopyShortcut) {
332
370
  ev.preventDefault();
333
371
  sandbox.kill?.();
334
372
  } else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'd') {
373
+ ev.preventDefault();
335
374
  if (input.length > 0) submitCurrentInput();
336
375
  sandbox?.eof?.();
337
376
  } else if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'v') {
377
+ ev.preventDefault();
338
378
  navigator.clipboard.readText().then((text) => {
339
379
  applyPastedText(text);
340
380
  });
@@ -0,0 +1,5 @@
1
+ import type { BrowserMessageReader as BrowserMessageReaderInstance, BrowserMessageWriter as BrowserMessageWriterInstance } from 'vscode-jsonrpc/lib/browser/main';
2
+ export declare const BrowserMessageReader: new (port: MessagePort | Worker | DedicatedWorkerGlobalScope) => BrowserMessageReaderInstance;
3
+ export declare const BrowserMessageWriter: new (port: MessagePort | Worker | DedicatedWorkerGlobalScope) => BrowserMessageWriterInstance;
4
+ export type BrowserMessageReader = BrowserMessageReaderInstance;
5
+ export type BrowserMessageWriter = BrowserMessageWriterInstance;
@@ -0,0 +1,4 @@
1
+ import jsonrpc from 'vscode-jsonrpc';
2
+ const browserJsonrpc = jsonrpc;
3
+ export const BrowserMessageReader = browserJsonrpc.BrowserMessageReader;
4
+ export const BrowserMessageWriter = browserJsonrpc.BrowserMessageWriter;
package/package.json CHANGED
@@ -1,17 +1,28 @@
1
1
  {
2
2
  "name": "wasm-idle",
3
- "version": "0.6.1",
4
- "description": "A simple web app to run C++/Python/Java code in the browser",
3
+ "version": "0.7.1",
4
+ "description": "A simple web app to run code in the browser",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "vite dev",
9
+ "build:preview": "vite build",
9
10
  "build": "vite build && npm run package",
10
11
  "preview": "vite preview",
11
- "probe:rust-browser": "pnpm run sync:wasm-rust && pnpm build && node scripts/probe-rust-browser.mjs",
12
+ "probe:go-browser": "node scripts/probe-go-browser.mjs",
13
+ "probe:ocaml-browser": "node scripts/probe-ocaml-browser.mjs",
14
+ "probe:rust-browser": "node scripts/probe-rust-browser.mjs",
15
+ "probe:tinygo-browser": "node scripts/probe-tinygo-browser.mjs",
16
+ "probe:tinygo-browser:static": "WASM_IDLE_DISABLE_TINYGO_HOST_COMPILE=1 node scripts/probe-tinygo-browser.mjs",
12
17
  "sync:pyodide": "node scripts/sync-pyodide.mjs",
13
- "test:browser:playwright": "pnpm run sync:wasm-rust && pnpm build && WASM_IDLE_RUN_REAL_BROWSER_RUST=1 pnpm exec vitest run src/lib/playground/rust.playwright.test.ts",
18
+ "test:browser:go": "WASM_IDLE_RUN_REAL_BROWSER_GO=1 pnpm exec vitest run src/lib/playground/go.playwright.test.ts",
19
+ "test:browser:ocaml": "WASM_IDLE_RUN_REAL_BROWSER_OCAML=1 pnpm exec vitest run src/lib/playground/ocaml.playwright.test.ts",
20
+ "test:browser:playwright": "WASM_IDLE_RUN_REAL_BROWSER_RUST=1 pnpm exec vitest run src/lib/playground/rust.playwright.test.ts",
21
+ "test:browser:tinygo": "WASM_IDLE_RUN_REAL_BROWSER_TINYGO=1 pnpm exec vitest run src/lib/playground/tinygo.playwright.test.ts",
22
+ "sync:wasm-of-js-of-ocaml": "node scripts/sync-wasm-of-js-of-ocaml.mjs",
23
+ "sync:wasm-go": "node scripts/sync-wasm-go.mjs",
14
24
  "sync:wasm-rust": "node scripts/sync-wasm-rust.mjs",
25
+ "sync:wasm-tinygo": "node scripts/sync-wasm-tinygo.mjs",
15
26
  "package": "svelte-kit sync && svelte-package && publint",
16
27
  "prepublishOnly": "npm run package",
17
28
  "test": "vitest run",
@@ -26,36 +37,37 @@
26
37
  "@eslint/js": "^10.0.1",
27
38
  "@sveltejs/adapter-auto": "^7.0.1",
28
39
  "@sveltejs/adapter-static": "^3.0.10",
29
- "@sveltejs/kit": "^2.55.0",
40
+ "@sveltejs/kit": "^2.57.1",
30
41
  "@sveltejs/package": "^2.5.7",
31
42
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
32
43
  "@testing-library/svelte": "^5.3.1",
33
44
  "@testing-library/user-event": "^14.6.1",
34
- "@types/node": "^25.5.0",
35
- "@typescript-eslint/eslint-plugin": "^8.57.2",
36
- "@typescript-eslint/parser": "^8.57.2",
37
- "eslint": "^10.1.0",
45
+ "@types/node": "^25.6.0",
46
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
47
+ "@typescript-eslint/parser": "^8.58.2",
48
+ "eslint": "^10.2.0",
38
49
  "eslint-config-prettier": "^10.1.8",
39
- "eslint-plugin-svelte": "^3.16.0",
50
+ "eslint-plugin-svelte": "^3.17.0",
40
51
  "eslint-plugin-svelte3": "^4.0.0",
41
52
  "gh-pages": "^6.3.0",
42
- "globals": "^17.4.0",
43
- "jsdom": "^29.0.1",
53
+ "globals": "^17.5.0",
54
+ "jsdom": "^29.0.2",
44
55
  "monaco-editor": "^0.55.1",
45
- "playwright-core": "^1.58.2",
46
- "prettier": "^3.8.1",
56
+ "playwright-core": "^1.59.1",
57
+ "prettier": "^3.8.3",
47
58
  "prettier-plugin-svelte": "^3.5.1",
48
- "svelte": "^5.55.0",
49
- "svelte-check": "^4.4.5",
59
+ "svelte": "^5.55.4",
60
+ "svelte-check": "^4.4.6",
50
61
  "tslib": "^2.8.1",
51
- "typescript": "^5.9.3",
52
- "typescript-eslint": "^8.57.1",
53
- "vite": "^8.0.3",
54
- "vitest": "^4.1.1"
62
+ "typescript": "^6.0.2",
63
+ "typescript-eslint": "^8.58.2",
64
+ "vite": "^8.0.8",
65
+ "vitest": "^4.1.4"
55
66
  },
56
67
  "dependencies": {
57
68
  "@bjorn3/browser_wasi_shim": "^0.4.2",
58
69
  "@hancomac/monaco-languageclient": "^1.1.0",
70
+ "@swmansion/popcorn": "0.2.2",
59
71
  "@xterm/addon-canvas": "^0.7.0",
60
72
  "@xterm/addon-fit": "^0.11.0",
61
73
  "@xterm/addon-ligatures": "^0.10.0",
@@ -65,7 +77,7 @@
65
77
  "@xterm/addon-web-links": "^0.12.0",
66
78
  "@xterm/addon-webgl": "^0.19.0",
67
79
  "@xterm/xterm": "^6.0.0",
68
- "@zip.js/zip.js": "^2.8.23",
80
+ "@zip.js/zip.js": "^2.8.26",
69
81
  "publint": "^0.3.18",
70
82
  "pyodide": "0.29.3",
71
83
  "vscode-jsonrpc": "^8.2.1"