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
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![wasm-idle](static/image.jpeg)
4
4
 
5
- Executes C++, Python, Java, and Rust code with working stdio.
5
+ Executes C++, Python, Java, Rust, and TinyGo code with working stdio.
6
6
 
7
7
  Refer to src/lib/clang.
8
8
 
@@ -29,26 +29,64 @@ The built-in Rust route now supports both `wasm32-wasip1` and `wasm32-wasip2`. T
29
29
  target selector when Rust is active, defaults to `wasm32-wasip1`, and persists that choice in local
30
30
  storage.
31
31
 
32
+ ## TinyGo browser integration
33
+
34
+ The demo app can also vendor the sibling `wasm-tinygo` browser build under `static/wasm-tinygo/`
35
+ and load its `runtime.js` entry directly inside the TinyGo playground sandbox. The example page
36
+ only auto-discovers a host-assisted `/api/tinygo/compile` endpoint when the browser itself is
37
+ running on `localhost` during `vite dev` / `vite preview`. Non-local and static deployments do not
38
+ probe that path implicitly anymore; set `PUBLIC_WASM_TINYGO_HOST_COMPILE_URL` or
39
+ `runtimeAssets.tinygo.hostCompileUrl` explicitly if you want to use a real TinyGo compile service.
40
+ Refresh the
41
+ bundled runtime assets after rebuilding the sibling `wasm-tinygo` project with:
42
+
43
+ ```bash
44
+ cd wasm-tinygo
45
+ npm run build
46
+
47
+ cd ../wasm-idle
48
+ pnpm run sync:wasm-tinygo
49
+ ```
50
+
51
+ TinyGo currently exposes a single `wasm` target through the example page. The browser pipeline
52
+ still lives inside `wasm-tinygo`; `wasm-idle` now imports its reusable `runtime.js` library entry
53
+ directly, then runs the emitted artifact with browser WASI so terminal stdin/EOF behavior stays
54
+ consistent with the other runtimes. When host compile is unavailable, the bundled browser fallback
55
+ now loads the vendored TinyGo runtime in `direct` mode, skips the old bootstrap-only compile step,
56
+ and runs the resulting WASI artifact locally. A host compile service is still useful when you want
57
+ an explicit remote compile seam or broader compatibility than the shipped browser bundle.
58
+
32
59
  ## Browser regression commands
33
60
 
34
- Browser-level Rust checks are reproducible from this repo:
61
+ Browser-level Rust and TinyGo checks are reproducible from this repo:
35
62
 
36
63
  ```bash
37
64
  cd wasm-idle
38
65
  pnpm run probe:rust-browser
39
66
  pnpm run test:browser:playwright
67
+ pnpm run probe:tinygo-browser
68
+ pnpm run test:browser:tinygo
40
69
 
41
70
  WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
42
71
  WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
43
72
  pnpm run probe:rust-browser
44
73
 
74
+ WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
75
+ WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
76
+ pnpm run probe:tinygo-browser
77
+
45
78
  WASM_IDLE_RUN_REAL_BROWSER_RUST=1 \
46
79
  WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
47
80
  WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
48
81
  pnpm exec vitest run src/lib/playground/rust.playwright.test.ts
82
+
83
+ WASM_IDLE_RUN_REAL_BROWSER_TINYGO=1 \
84
+ WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
85
+ WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
86
+ pnpm exec vitest run src/lib/playground/tinygo.playwright.test.ts
49
87
  ```
50
88
 
51
- Both commands exercise the real Chromium page path. The default Rust probe now feeds stdin with a
89
+ Both runtime probes exercise the real Chromium page path. The default Rust probe now feeds stdin with a
52
90
  single line (`5\n`) and expects the page to finish without sending EOF, which keeps the regression
53
91
  aligned with the default Rust sample and proves that pressing Enter is enough for line-based stdin.
54
92
  Programs that intentionally read stdin until EOF can still be finished with `Ctrl+D` or the toolbar
@@ -56,6 +94,10 @@ Programs that intentionally read stdin until EOF can still be finished with `Ctr
56
94
  The browser helper writes stdin through the page-owned `window.__wasmIdleDebug.writeTerminalInput(...)`
57
95
  hook instead of trying to click xterm's hidden helper textarea, which proved too flaky for repeatable
58
96
  Playwright runs.
97
+ The TinyGo probe follows the same pattern. The host-assisted path still looks for
98
+ `tinygo host compile ready: target=wasip1`, while the browser-only path should load the vendored
99
+ runtime in `direct` mode and avoid the old bootstrap compile command altogether.
100
+ The TinyGo browser commands currently default to `vite dev`.
59
101
  If Rust ever reports `invalid metadata files for crate core` or `Unsupported archive identifier`,
60
102
  the browser almost always fetched a stale or wrong `wasm-rust` sysroot asset. Hard refresh the page
61
103
  and resync `static/wasm-rust/` from the sibling `wasm-rust/dist/`.
@@ -74,6 +116,13 @@ session; the repo-owned regression target is the local preview path above.
74
116
  ## Runtime expectations
75
117
 
76
118
  Rust still supports an external browser compiler module for library consumers. Point `PUBLIC_WASM_RUST_COMPILER_URL` at a built `wasm-rust` ESM entry such as `.../wasm-rust/dist/index.js`, or pass `runtimeAssets.rust.compilerUrl` at runtime.
119
+ TinyGo can use a host compile service when one is configured explicitly. Point
120
+ `PUBLIC_WASM_TINYGO_HOST_COMPILE_URL` at an endpoint that accepts `POST { source }` and returns the
121
+ compiled wasm artifact payload, or pass `runtimeAssets.tinygo.hostCompileUrl` at runtime. The
122
+ bundled browser fallback still expects a browser-loadable `wasm-tinygo` runtime module. Point
123
+ `PUBLIC_WASM_TINYGO_MODULE_URL` at a built entry such as `.../wasm-tinygo/dist/runtime.js`, or
124
+ pass `runtimeAssets.tinygo.moduleUrl` at runtime. The older `PUBLIC_WASM_TINYGO_APP_URL` /
125
+ `runtimeAssets.tinygo.appUrl` document path is still accepted and normalized to `runtime.js`.
77
126
 
78
127
  The Rust browser path now executes returned artifacts through the target-appropriate runtime inside
79
128
  the Rust worker:
@@ -99,23 +148,39 @@ const runtimeAssets: PlaygroundRuntimeAssets = {
99
148
  },
100
149
  rust: {
101
150
  compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
151
+ },
152
+ tinygo: {
153
+ hostCompileUrl: 'https://tinygo.example.com/api/compile',
154
+ moduleUrl: 'https://cdn.example.com/wasm-tinygo/runtime.js'
102
155
  }
103
156
  };
104
157
  ```
105
158
 
106
- Python custom loaders receive file names under the Pyodide asset root and can serve both core assets and package files. TeaVM custom loaders receive file names under the TeaVM asset root. Rust expects a browser-loadable compiler module URL; that module is responsible for serving its own nested runtime assets. Compressed TeaVM runtime assets are no longer unpacked inside the library; provide the final file URL or handle decompression in your own loader.
159
+ Python custom loaders receive file names under the Pyodide asset root and can serve both core assets and package files. TeaVM custom loaders receive file names under the TeaVM asset root. Rust expects a browser-loadable compiler module URL; that module is responsible for serving its own nested runtime assets. TinyGo can use either an explicit host compile URL or a browser-loadable runtime module. The browser runtime now ships a direct-mode fallback that can produce and run the bundled TinyGo WASI artifact locally, alongside its sibling `tools/go-probe.wasm` and vendored emception assets. A host compile service remains useful when you want a dedicated remote compile seam or compatibility beyond the shipped browser bundle. TinyGo also accepts a runtime asset loader + pack bundle in `runtimeAssets.tinygo` when you need to serve runtime assets out of a single compressed archive. Compressed TeaVM runtime assets are no longer unpacked inside the library; provide the final file URL or handle decompression in your own loader.
107
160
 
108
161
  If you want a host app to reuse the same runtime asset configuration for both `<Terminal>` and direct `playground(...)` access, bind it once:
109
162
 
110
163
  ```ts
111
164
  import Terminal, { createPlaygroundBinding } from 'wasm-idle';
112
165
 
113
- const wasmIdle = createPlaygroundBinding({
114
- rootUrl: 'https://cdn.example.com/repl',
115
- rust: {
116
- compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
117
- }
118
- });
166
+ const wasmIdle = createPlaygroundBinding({
167
+ rootUrl: 'https://cdn.example.com/repl',
168
+ rust: {
169
+ compilerUrl: 'https://cdn.example.com/wasm-rust/index.js'
170
+ },
171
+ tinygo: {
172
+ hostCompileUrl: 'https://tinygo.example.com/api/compile',
173
+ moduleUrl: 'https://cdn.example.com/wasm-tinygo/runtime.js',
174
+ assetPacks: [
175
+ {
176
+ index: 'https://cdn.example.com/wasm-tinygo/runtime-pack/runtime-pack.index.json',
177
+ asset: 'https://cdn.example.com/wasm-tinygo/runtime-pack/runtime-pack.bin',
178
+ fileCount: 12,
179
+ totalBytes: 123456
180
+ }
181
+ ]
182
+ }
183
+ });
119
184
 
120
185
  const sandbox = await wasmIdle.load('PYTHON');
121
186
  await sandbox.load('print("hi")', false);
@@ -125,4 +190,5 @@ await sandbox.load('print("hi")', false);
125
190
  <Terminal {...wasmIdle.terminalProps} bind:terminal />
126
191
  ```
127
192
 
128
- Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM, and `wasm-rust`.
193
+ Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM, `wasm-rust`, and
194
+ `wasm-tinygo`.
@@ -1,7 +1,7 @@
1
1
  import { CloseAction, ErrorAction, MonacoLanguageClient, MonacoServices } from '@hancomac/monaco-languageclient';
2
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser';
3
2
  import { CLANGD_CPP_FILE_URI, CLANGD_WORKSPACE_URI, normalizeClangdBaseUrl } from './config';
4
3
  import ClangdWorker from './worker?worker';
4
+ import { BrowserMessageReader, BrowserMessageWriter } from '../utils/vscodeJsonrpcBrowser';
5
5
  let servicesInstalled = false;
6
6
  export class ClangdSession {
7
7
  Monaco;
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="WebWorker" />
2
2
  import { CLANGD_CPP_FILE_PATH, CLANGD_WORKSPACE_PATH, createClangdCompileFlags, normalizeClangdBaseUrl } from './config';
3
3
  import { JsonStream } from './jsonStream';
4
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser';
4
+ import { BrowserMessageReader, BrowserMessageWriter } from '../utils/vscodeJsonrpcBrowser';
5
5
  const textEncoder = new TextEncoder();
6
6
  const jsonStream = new JsonStream();
7
7
  let resolveStdinReady = () => { };
@@ -1,6 +1,6 @@
1
1
  import { resolveCppLanguageServerBaseUrl } from '../runtime';
2
2
  import ClangdWorker from '../../clangd/worker?worker';
3
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser';
3
+ import { BrowserMessageReader, BrowserMessageWriter } from '../../utils/vscodeJsonrpcBrowser';
4
4
  const currentUrl = () => globalThis.location?.href || '';
5
5
  async function createServer(baseUrl) {
6
6
  let resolveReady = () => { };
@@ -1,6 +1,6 @@
1
1
  import { resolvePythonLanguageServerBaseUrl } from '../runtime';
2
2
  import PythonLspWorker from './main.worker?worker';
3
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser';
3
+ import { BrowserMessageReader, BrowserMessageWriter } from '../../utils/vscodeJsonrpcBrowser';
4
4
  const currentUrl = () => globalThis.location?.href || '';
5
5
  async function createServer(pyodideBaseUrl) {
6
6
  let resolveReady = () => { };
@@ -20,12 +20,47 @@ export interface RuntimeAssetConfig {
20
20
  export interface RustRuntimeAssetConfig {
21
21
  compilerUrl?: string;
22
22
  }
23
+ export interface GoRuntimeAssetConfig {
24
+ compilerUrl?: string;
25
+ }
26
+ export interface OcamlRuntimeAssetConfig {
27
+ moduleUrl?: string;
28
+ manifestUrl?: string;
29
+ }
30
+ export interface TinyGoRuntimeAssetConfig {
31
+ moduleUrl?: string;
32
+ appUrl?: string;
33
+ hostCompileUrl?: string;
34
+ disableHostCompile?: boolean;
35
+ assetLoader?: TinyGoRuntimeAssetLoader;
36
+ assetPacks?: TinyGoRuntimeAssetPackReference[];
37
+ }
38
+ export interface ElixirRuntimeAssetConfig {
39
+ bundleUrl?: string;
40
+ }
23
41
  export interface PlaygroundRuntimeAssets {
24
42
  rootUrl?: string;
25
43
  python?: RuntimeAssetConfig;
26
44
  java?: RuntimeAssetConfig;
27
45
  rust?: RustRuntimeAssetConfig;
46
+ go?: GoRuntimeAssetConfig;
47
+ ocaml?: OcamlRuntimeAssetConfig;
48
+ tinygo?: TinyGoRuntimeAssetConfig;
49
+ elixir?: ElixirRuntimeAssetConfig;
50
+ }
51
+ export interface TinyGoRuntimeAssetLoaderRequest {
52
+ assetPath: string;
53
+ assetUrl: string;
54
+ label: string;
55
+ }
56
+ export interface TinyGoRuntimeAssetPackReference {
57
+ index: string;
58
+ asset: string;
59
+ fileCount: number;
60
+ totalBytes: number;
28
61
  }
62
+ export type TinyGoRuntimeAssetLoaderResult = RuntimeAssetDataResult | RuntimeAssetUrlResult | string | URL | ArrayBuffer | Uint8Array | Blob | null | undefined;
63
+ export type TinyGoRuntimeAssetLoader = (request: TinyGoRuntimeAssetLoaderRequest) => TinyGoRuntimeAssetLoaderResult | Promise<TinyGoRuntimeAssetLoaderResult>;
29
64
  export interface ResolvedRuntimeAssetConfig {
30
65
  baseUrl: string;
31
66
  loader?: RuntimeAssetLoader;
@@ -35,3 +70,11 @@ export declare const PYTHON_RUNTIME_LOAD_ASSETS: readonly ["pyodide.asm.js", "py
35
70
  export declare const JAVA_RUNTIME_LOAD_ASSETS: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
36
71
  export declare function resolveRuntimeAssetConfig(runtime: RuntimeAssetRuntime, options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): ResolvedRuntimeAssetConfig;
37
72
  export declare function resolveRustCompilerUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): any;
73
+ export declare function resolveGoCompilerUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): any;
74
+ export declare function resolveOcamlModuleUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): any;
75
+ export declare function resolveOcamlManifestUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): any;
76
+ export declare function resolveTinyGoAppUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string;
77
+ export declare function resolveTinyGoModuleUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string;
78
+ export declare function resolveTinyGoHostCompileUrls(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string[];
79
+ export declare function resolveTinyGoHostCompileUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string;
80
+ export declare function resolveElixirBundleUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string;
@@ -18,7 +18,9 @@ const normalizeBaseUrl = (baseUrl, currentUrl = '') => {
18
18
  const normalized = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
19
19
  return currentUrl ? new URL(normalized, currentUrl).href : normalized;
20
20
  };
21
+ const resolveConfiguredUrl = (url, currentUrl = '') => currentUrl ? new URL(url, currentUrl).href : url;
21
22
  const normalizeRootUrl = (rootUrl) => rootUrl.endsWith('/') ? rootUrl.slice(0, -1) : rootUrl;
23
+ const isLocalHostname = (hostname) => hostname === 'localhost' || hostname === '127.0.0.1';
22
24
  const resolvePythonBaseUrl = (rootUrl = '', currentUrl = '') => normalizeBaseUrl(`${normalizeRootUrl(rootUrl) || ''}/pyodide/`, currentUrl);
23
25
  const resolvePythonRuntimeAssetConfig = (options, currentUrl = '') => {
24
26
  if (typeof options === 'string') {
@@ -103,3 +105,165 @@ export function resolveRustCompilerUrl(options, currentUrl = '') {
103
105
  return '';
104
106
  return currentUrl ? new URL(configuredCompilerUrl, currentUrl).href : configuredCompilerUrl;
105
107
  }
108
+ export function resolveGoCompilerUrl(options, currentUrl = '') {
109
+ const configuredCompilerUrl = (typeof options === 'object' && options?.go?.compilerUrl) ||
110
+ (publicEnv.PUBLIC_WASM_GO_COMPILER_URL || '').trim();
111
+ if (!configuredCompilerUrl)
112
+ return '';
113
+ return currentUrl ? new URL(configuredCompilerUrl, currentUrl).href : configuredCompilerUrl;
114
+ }
115
+ export function resolveOcamlModuleUrl(options, currentUrl = '') {
116
+ const configuredModuleUrl = (typeof options === 'object' && options?.ocaml?.moduleUrl) ||
117
+ (publicEnv.PUBLIC_WASM_OCAML_MODULE_URL || '').trim();
118
+ if (configuredModuleUrl) {
119
+ return currentUrl ? new URL(configuredModuleUrl, currentUrl).href : configuredModuleUrl;
120
+ }
121
+ if (typeof options === 'string') {
122
+ return currentUrl
123
+ ? new URL(`${normalizeRootUrl(options)}/wasm-of-js-of-ocaml/browser-native/src/index.js`, currentUrl).href
124
+ : `${normalizeRootUrl(options)}/wasm-of-js-of-ocaml/browser-native/src/index.js`;
125
+ }
126
+ if (options?.rootUrl) {
127
+ return currentUrl
128
+ ? new URL(`${normalizeRootUrl(options.rootUrl)}/wasm-of-js-of-ocaml/browser-native/src/index.js`, currentUrl).href
129
+ : `${normalizeRootUrl(options.rootUrl)}/wasm-of-js-of-ocaml/browser-native/src/index.js`;
130
+ }
131
+ return '';
132
+ }
133
+ export function resolveOcamlManifestUrl(options, currentUrl = '') {
134
+ const configuredManifestUrl = (typeof options === 'object' && options?.ocaml?.manifestUrl) ||
135
+ (publicEnv.PUBLIC_WASM_OCAML_MANIFEST_URL || '').trim();
136
+ if (configuredManifestUrl) {
137
+ return currentUrl ? new URL(configuredManifestUrl, currentUrl).href : configuredManifestUrl;
138
+ }
139
+ if (typeof options === 'string') {
140
+ return currentUrl
141
+ ? new URL(`${normalizeRootUrl(options)}/wasm-of-js-of-ocaml/browser-native-bundle/browser-native-manifest.v1.json`, currentUrl).href
142
+ : `${normalizeRootUrl(options)}/wasm-of-js-of-ocaml/browser-native-bundle/browser-native-manifest.v1.json`;
143
+ }
144
+ if (options?.rootUrl) {
145
+ return currentUrl
146
+ ? new URL(`${normalizeRootUrl(options.rootUrl)}/wasm-of-js-of-ocaml/browser-native-bundle/browser-native-manifest.v1.json`, currentUrl).href
147
+ : `${normalizeRootUrl(options.rootUrl)}/wasm-of-js-of-ocaml/browser-native-bundle/browser-native-manifest.v1.json`;
148
+ }
149
+ return '';
150
+ }
151
+ export function resolveTinyGoAppUrl(options, currentUrl = '') {
152
+ const configuredAppUrl = (typeof options === 'object' && options?.tinygo?.appUrl) ||
153
+ (publicEnv.PUBLIC_WASM_TINYGO_APP_URL || '').trim();
154
+ if (configuredAppUrl) {
155
+ return resolveConfiguredUrl(configuredAppUrl, currentUrl);
156
+ }
157
+ if (typeof options === 'string') {
158
+ return resolveConfiguredUrl(`${normalizeRootUrl(options) || ''}/wasm-tinygo/index.html`, currentUrl);
159
+ }
160
+ if (options?.rootUrl) {
161
+ return resolveConfiguredUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-tinygo/index.html`, currentUrl);
162
+ }
163
+ return '';
164
+ }
165
+ function deriveTinyGoModuleUrlFromAppUrl(appUrl, currentUrl = '') {
166
+ const resolvedAppUrl = resolveConfiguredUrl(appUrl, currentUrl);
167
+ const [withoutHash, hash = ''] = resolvedAppUrl.split('#', 2);
168
+ const [withoutQuery, query = ''] = withoutHash.split('?', 2);
169
+ let moduleUrlPath = withoutQuery;
170
+ if (moduleUrlPath.endsWith('/index.html')) {
171
+ moduleUrlPath = `${moduleUrlPath.slice(0, -'index.html'.length)}runtime.js`;
172
+ }
173
+ else if (moduleUrlPath.endsWith('/')) {
174
+ moduleUrlPath = `${moduleUrlPath}runtime.js`;
175
+ }
176
+ else {
177
+ moduleUrlPath = `${moduleUrlPath.replace(/\/$/, '')}/runtime.js`;
178
+ }
179
+ return `${moduleUrlPath}${query ? `?${query}` : ''}${hash ? `#${hash}` : ''}`;
180
+ }
181
+ export function resolveTinyGoModuleUrl(options, currentUrl = '') {
182
+ const configuredModuleUrl = (typeof options === 'object' && options?.tinygo?.moduleUrl) ||
183
+ (publicEnv.PUBLIC_WASM_TINYGO_MODULE_URL || '').trim();
184
+ if (configuredModuleUrl) {
185
+ return resolveConfiguredUrl(configuredModuleUrl, currentUrl);
186
+ }
187
+ const configuredLegacyAppUrl = (typeof options === 'object' && options?.tinygo?.appUrl) ||
188
+ (publicEnv.PUBLIC_WASM_TINYGO_APP_URL || '').trim();
189
+ if (configuredLegacyAppUrl) {
190
+ return deriveTinyGoModuleUrlFromAppUrl(configuredLegacyAppUrl, currentUrl);
191
+ }
192
+ if (typeof options === 'string') {
193
+ return resolveConfiguredUrl(`${normalizeRootUrl(options) || ''}/wasm-tinygo/runtime.js`, currentUrl);
194
+ }
195
+ if (options?.rootUrl) {
196
+ return resolveConfiguredUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-tinygo/runtime.js`, currentUrl);
197
+ }
198
+ return '';
199
+ }
200
+ export function resolveTinyGoHostCompileUrls(options, currentUrl = '') {
201
+ if (typeof options === 'object' && options?.tinygo?.disableHostCompile) {
202
+ return [];
203
+ }
204
+ const configuredHostCompileUrl = (typeof options === 'object' && options?.tinygo?.hostCompileUrl) ||
205
+ (publicEnv.PUBLIC_WASM_TINYGO_HOST_COMPILE_URL || '').trim();
206
+ if (configuredHostCompileUrl) {
207
+ return [resolveConfiguredUrl(configuredHostCompileUrl, currentUrl)];
208
+ }
209
+ const urls = [];
210
+ const pushUrl = (url) => {
211
+ const resolvedUrl = resolveConfiguredUrl(url, currentUrl);
212
+ if (!urls.includes(resolvedUrl)) {
213
+ urls.push(resolvedUrl);
214
+ }
215
+ };
216
+ let currentPageUrl = null;
217
+ try {
218
+ currentPageUrl = currentUrl ? new URL(currentUrl) : null;
219
+ }
220
+ catch {
221
+ currentPageUrl = null;
222
+ }
223
+ const canAutoDeriveHostCompileUrls = currentPageUrl !== null &&
224
+ isLocalHostname(currentPageUrl.hostname) &&
225
+ (currentPageUrl.protocol === 'http:' || currentPageUrl.protocol === 'https:');
226
+ if (!canAutoDeriveHostCompileUrls || currentPageUrl === null) {
227
+ return urls;
228
+ }
229
+ if (typeof options === 'string') {
230
+ pushUrl(`${normalizeRootUrl(options) || ''}/api/tinygo/compile`);
231
+ }
232
+ else if (options?.rootUrl) {
233
+ pushUrl(`${normalizeRootUrl(options.rootUrl) || ''}/api/tinygo/compile`);
234
+ }
235
+ else {
236
+ const currentPathname = currentPageUrl.pathname.endsWith('/')
237
+ ? currentPageUrl.pathname
238
+ : `${currentPageUrl.pathname}/`;
239
+ pushUrl(`${currentPageUrl.origin}${currentPathname}api/tinygo/compile`);
240
+ }
241
+ const moduleUrl = resolveTinyGoModuleUrl(options, currentUrl);
242
+ if (moduleUrl) {
243
+ const moduleAssetUrl = new URL(moduleUrl, currentUrl || undefined);
244
+ if (moduleAssetUrl.protocol === 'http:' || moduleAssetUrl.protocol === 'https:') {
245
+ pushUrl(new URL('../api/tinygo/compile', moduleAssetUrl).toString());
246
+ }
247
+ }
248
+ if (currentPageUrl.port !== '4175') {
249
+ pushUrl(`${currentPageUrl.protocol}//${currentPageUrl.hostname}:4175/api/tinygo/compile`);
250
+ }
251
+ return urls;
252
+ }
253
+ export function resolveTinyGoHostCompileUrl(options, currentUrl = '') {
254
+ return resolveTinyGoHostCompileUrls(options, currentUrl)[0] || '';
255
+ }
256
+ export function resolveElixirBundleUrl(options, currentUrl = '') {
257
+ const configuredBundleUrl = (typeof options === 'object' && options?.elixir?.bundleUrl) ||
258
+ (publicEnv.PUBLIC_WASM_ELIXIR_BUNDLE_URL || '').trim();
259
+ if (configuredBundleUrl) {
260
+ return resolveConfiguredUrl(configuredBundleUrl, currentUrl);
261
+ }
262
+ if (typeof options === 'string') {
263
+ return resolveConfiguredUrl(`${normalizeRootUrl(options) || ''}/wasm-elixir/bundle.avm`, currentUrl);
264
+ }
265
+ if (options?.rootUrl) {
266
+ return resolveConfiguredUrl(`${normalizeRootUrl(options.rootUrl) || ''}/wasm-elixir/bundle.avm`, currentUrl);
267
+ }
268
+ return '';
269
+ }
@@ -0,0 +1,32 @@
1
+ import { type PlaygroundRuntimeAssets } from './assets';
2
+ import type { SandboxExecutionOptions } from './options';
3
+ import type { Sandbox } from './sandbox';
4
+ declare class Elixir implements Sandbox {
5
+ output: any;
6
+ worker?: Worker;
7
+ buffer: SharedArrayBuffer;
8
+ pendingInput: string[];
9
+ begin: number;
10
+ elapse: number;
11
+ uid: number;
12
+ exit: boolean;
13
+ bundleUrl: string;
14
+ activeReject: ((reason: string) => void) | null;
15
+ prepared: boolean;
16
+ hasExecuted: boolean;
17
+ waitingForInput: boolean;
18
+ pendingEof: boolean;
19
+ load(runtimeAssets?: string | PlaygroundRuntimeAssets, _code?: string, log?: boolean, _args?: string[], _options?: SandboxExecutionOptions, progress?: {
20
+ set?: (value: number) => void;
21
+ } | import('svelte/store').Writable<number>): Promise<void>;
22
+ write(input: string): void;
23
+ eof(): void;
24
+ private flushPendingInput;
25
+ run(code: string, prepare: boolean, log?: boolean, _prog?: {
26
+ set?: (value: number) => void;
27
+ } | import('svelte/store').Writable<number>): Promise<boolean | string>;
28
+ kill(): void;
29
+ terminate(): void;
30
+ clear(): Promise<void>;
31
+ }
32
+ export default Elixir;
@@ -0,0 +1,173 @@
1
+ import { resolveElixirBundleUrl } from './assets';
2
+ import { flushBufferedEof, flushQueuedStdin, resetBufferedStdin } from './stdinBuffer';
3
+ class Elixir {
4
+ output = null;
5
+ worker = null;
6
+ buffer = new SharedArrayBuffer(1024);
7
+ pendingInput = [];
8
+ begin = 0;
9
+ elapse = 0;
10
+ uid = 0;
11
+ exit = true;
12
+ bundleUrl = '';
13
+ activeReject = null;
14
+ prepared = false;
15
+ hasExecuted = false;
16
+ waitingForInput = false;
17
+ pendingEof = false;
18
+ load(runtimeAssets = '', _code = '', log = true, _args = [], _options = {}, progress) {
19
+ return new Promise(async (resolve, reject) => {
20
+ this.pendingInput = [];
21
+ this.waitingForInput = false;
22
+ this.pendingEof = false;
23
+ const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
24
+ const nextBundleUrl = resolveElixirBundleUrl(runtimeAssets, currentUrl);
25
+ if (!nextBundleUrl) {
26
+ return reject('Elixir runtime is not configured. Set PUBLIC_WASM_ELIXIR_BUNDLE_URL or runtimeAssets.elixir.bundleUrl.');
27
+ }
28
+ const needsWorkerReset = !this.worker || this.bundleUrl !== nextBundleUrl;
29
+ this.bundleUrl = nextBundleUrl;
30
+ if (needsWorkerReset && this.worker) {
31
+ this.worker.terminate();
32
+ delete this.worker;
33
+ }
34
+ if (!this.worker) {
35
+ progress?.set?.(0.2);
36
+ this.worker = new (await import('./worker/elixir?worker')).default();
37
+ progress?.set?.(0.5);
38
+ this.worker.onerror = (event) => {
39
+ const location = event.filename && event.lineno
40
+ ? ` (${event.filename}:${event.lineno}:${event.colno})`
41
+ : '';
42
+ reject(`Elixir worker script error: ${event.message || 'unknown error'}${location}`);
43
+ };
44
+ this.worker.onmessageerror = () => {
45
+ reject('Elixir worker message deserialization failed');
46
+ };
47
+ this.worker.onmessage = (event) => {
48
+ if (event.data?.load) {
49
+ progress?.set?.(1);
50
+ resolve();
51
+ }
52
+ if (event.data?.error)
53
+ reject(event.data.error);
54
+ };
55
+ this.worker.postMessage({
56
+ load: true,
57
+ bundleUrl: this.bundleUrl,
58
+ log
59
+ });
60
+ }
61
+ else {
62
+ progress?.set?.(1);
63
+ resolve();
64
+ }
65
+ });
66
+ }
67
+ write(input) {
68
+ this.pendingInput.push(input);
69
+ this.pendingEof = false;
70
+ this.flushPendingInput();
71
+ }
72
+ eof() {
73
+ this.pendingEof = true;
74
+ this.flushPendingInput();
75
+ }
76
+ flushPendingInput() {
77
+ if (!this.waitingForInput)
78
+ return;
79
+ if (flushQueuedStdin(this.pendingInput, this.buffer)) {
80
+ this.waitingForInput = false;
81
+ return;
82
+ }
83
+ if (this.pendingEof) {
84
+ flushBufferedEof(this.buffer);
85
+ this.pendingEof = false;
86
+ this.waitingForInput = false;
87
+ }
88
+ }
89
+ run(code, prepare, log = true, _prog) {
90
+ this.exit = false;
91
+ return new Promise((resolve, reject) => {
92
+ if (!this.worker)
93
+ return reject('Worker not loaded');
94
+ const activeUid = ++this.uid;
95
+ this.activeReject = reject;
96
+ const handler = (event) => {
97
+ if (!this.worker)
98
+ return reject('Worker not loaded');
99
+ if (activeUid !== this.uid)
100
+ return (this.worker.onmessage = null);
101
+ const { output, error, buffer } = event.data;
102
+ const hasResults = Object.prototype.hasOwnProperty.call(event.data || {}, 'results');
103
+ if (buffer) {
104
+ this.waitingForInput = true;
105
+ this.flushPendingInput();
106
+ }
107
+ if (output) {
108
+ this.output?.(output);
109
+ }
110
+ if (hasResults) {
111
+ const { results } = event.data;
112
+ this.elapse = Date.now() - this.begin;
113
+ this.exit = true;
114
+ this.waitingForInput = false;
115
+ this.pendingEof = false;
116
+ this.activeReject = null;
117
+ this.prepared = prepare;
118
+ this.hasExecuted = !prepare;
119
+ if (!prepare && typeof results === 'string' && results) {
120
+ this.output?.(`=> ${results}\n`);
121
+ }
122
+ resolve(results || true);
123
+ }
124
+ if (error) {
125
+ this.elapse = Date.now() - this.begin;
126
+ this.exit = true;
127
+ this.waitingForInput = false;
128
+ this.pendingEof = false;
129
+ this.activeReject = null;
130
+ this.prepared = false;
131
+ this.hasExecuted = false;
132
+ reject(error);
133
+ }
134
+ };
135
+ this.worker.onmessage = handler;
136
+ this.begin = Date.now();
137
+ this.worker.postMessage({
138
+ code,
139
+ prepare,
140
+ buffer: this.buffer,
141
+ log
142
+ });
143
+ });
144
+ }
145
+ kill() {
146
+ this.terminate();
147
+ }
148
+ terminate() {
149
+ this.activeReject?.('Process terminated');
150
+ this.activeReject = null;
151
+ this.uid += 1;
152
+ this.prepared = false;
153
+ this.hasExecuted = false;
154
+ this.waitingForInput = false;
155
+ this.pendingEof = false;
156
+ this.worker?.terminate?.();
157
+ delete this.worker;
158
+ this.exit = true;
159
+ }
160
+ async clear() {
161
+ this.pendingInput = [];
162
+ this.waitingForInput = false;
163
+ this.pendingEof = false;
164
+ if (this.worker) {
165
+ this.worker.onmessage = null;
166
+ }
167
+ resetBufferedStdin(this.buffer);
168
+ if (!this.exit || this.hasExecuted) {
169
+ this.terminate();
170
+ }
171
+ }
172
+ }
173
+ export default Elixir;
@@ -0,0 +1,31 @@
1
+ import { type PlaygroundRuntimeAssets } from './assets';
2
+ import { type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
3
+ import type { Sandbox } from './sandbox';
4
+ declare class Go implements Sandbox {
5
+ output: any;
6
+ worker?: Worker;
7
+ buffer: SharedArrayBuffer;
8
+ pendingInput: string[];
9
+ begin: number;
10
+ elapse: number;
11
+ uid: number;
12
+ exit: boolean;
13
+ compilerUrl: string;
14
+ activeReject: ((reason: string) => void) | null;
15
+ oncompilerdiagnostic?: (diagnostic: CompilerDiagnostic) => void;
16
+ waitingForInput: boolean;
17
+ pendingEof: boolean;
18
+ load(runtimeAssets?: string | PlaygroundRuntimeAssets, _code?: string, _log?: boolean, _args?: string[], _options?: SandboxExecutionOptions, progress?: {
19
+ set?: (value: number) => void;
20
+ } | import('svelte/store').Writable<number>): Promise<void>;
21
+ write(input: string): void;
22
+ eof(): void;
23
+ private flushPendingInput;
24
+ run(code: string, prepare: boolean, _log?: boolean, _prog?: {
25
+ set?: (value: number) => void;
26
+ } | import('svelte/store').Writable<number>, args?: string[], options?: SandboxExecutionOptions): Promise<boolean | string>;
27
+ kill(): void;
28
+ terminate(): void;
29
+ clear(): Promise<void>;
30
+ }
31
+ export default Go;