wasm-idle 0.6.0 → 0.7.0
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.
- package/README.md +85 -11
- package/dist/playground/assets.d.ts +43 -0
- package/dist/playground/assets.js +164 -0
- package/dist/playground/elixir.d.ts +32 -0
- package/dist/playground/elixir.js +173 -0
- package/dist/playground/go.d.ts +31 -0
- package/dist/playground/go.js +166 -0
- package/dist/playground/index.js +36 -1
- package/dist/playground/ocaml.d.ts +32 -0
- package/dist/playground/ocaml.js +341 -0
- package/dist/playground/options.d.ts +4 -0
- package/dist/playground/tinygo.d.ts +75 -0
- package/dist/playground/tinygo.js +510 -0
- package/dist/playground/wasmElixirVersion.d.ts +1 -0
- package/dist/playground/wasmElixirVersion.js +1 -0
- package/dist/playground/wasmGoVersion.d.ts +1 -0
- package/dist/playground/wasmGoVersion.js +1 -0
- package/dist/playground/wasmOcamlVersion.d.ts +1 -0
- package/dist/playground/wasmOcamlVersion.js +1 -0
- package/dist/playground/wasmRustVersion.d.ts +1 -1
- package/dist/playground/wasmRustVersion.js +1 -1
- package/dist/playground/wasmTinyGoVersion.d.ts +1 -0
- package/dist/playground/wasmTinyGoVersion.js +1 -0
- package/dist/playground/worker/elixir.d.ts +1 -0
- package/dist/playground/worker/elixir.js +317 -0
- package/dist/playground/worker/go.d.ts +1 -0
- package/dist/playground/worker/go.js +163 -0
- package/dist/playground/worker/ocaml.d.ts +1 -0
- package/dist/playground/worker/ocaml.js +449 -0
- package/dist/playground/worker/tinygo.d.ts +1 -0
- package/dist/playground/worker/tinygo.js +137 -0
- package/dist/terminal/Terminal.svelte +69 -29
- package/package.json +35 -22
package/README.md
CHANGED
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
Executes C++, Python, Java, and
|
|
5
|
+
Executes C++, Python, Java, Rust, and TinyGo code with working stdio.
|
|
6
6
|
|
|
7
7
|
Refer to src/lib/clang.
|
|
8
8
|
|
|
9
9
|
Java uses TeaVM's browser compiler/runtime. TeaVM compiler/runtime/classlib assets are bundled under `static/teavm/` by default, and the asset base URL can be overridden with `PUBLIC_TEAVM_BASE_URL`.
|
|
10
10
|
|
|
11
|
+
Pyodide core assets are vendored under `static/pyodide/`. Refresh them after bumping the `pyodide`
|
|
12
|
+
package with:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd wasm-idle
|
|
16
|
+
pnpm run sync:pyodide
|
|
17
|
+
```
|
|
18
|
+
|
|
11
19
|
## Rust browser integration
|
|
12
20
|
|
|
13
21
|
The demo app now bundles a local `wasm-rust` browser compiler under `static/wasm-rust/` and points the example `Terminal` at `/wasm-rust/index.js` by default. Refresh that bundle after rebuilding the sibling `wasm-rust` project with:
|
|
@@ -21,26 +29,64 @@ The built-in Rust route now supports both `wasm32-wasip1` and `wasm32-wasip2`. T
|
|
|
21
29
|
target selector when Rust is active, defaults to `wasm32-wasip1`, and persists that choice in local
|
|
22
30
|
storage.
|
|
23
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
|
+
|
|
24
59
|
## Browser regression commands
|
|
25
60
|
|
|
26
|
-
Browser-level Rust checks are reproducible from this repo:
|
|
61
|
+
Browser-level Rust and TinyGo checks are reproducible from this repo:
|
|
27
62
|
|
|
28
63
|
```bash
|
|
29
64
|
cd wasm-idle
|
|
30
65
|
pnpm run probe:rust-browser
|
|
31
66
|
pnpm run test:browser:playwright
|
|
67
|
+
pnpm run probe:tinygo-browser
|
|
68
|
+
pnpm run test:browser:tinygo
|
|
32
69
|
|
|
33
70
|
WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
|
|
34
71
|
WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
|
|
35
72
|
pnpm run probe:rust-browser
|
|
36
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
|
+
|
|
37
78
|
WASM_IDLE_RUN_REAL_BROWSER_RUST=1 \
|
|
38
79
|
WASM_IDLE_BROWSER_URL='http://localhost:5173/absproxy/5173/' \
|
|
39
80
|
WASM_IDLE_REUSE_LOCAL_PREVIEW=1 \
|
|
40
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
|
|
41
87
|
```
|
|
42
88
|
|
|
43
|
-
Both
|
|
89
|
+
Both runtime probes exercise the real Chromium page path. The default Rust probe now feeds stdin with a
|
|
44
90
|
single line (`5\n`) and expects the page to finish without sending EOF, which keeps the regression
|
|
45
91
|
aligned with the default Rust sample and proves that pressing Enter is enough for line-based stdin.
|
|
46
92
|
Programs that intentionally read stdin until EOF can still be finished with `Ctrl+D` or the toolbar
|
|
@@ -48,6 +94,10 @@ Programs that intentionally read stdin until EOF can still be finished with `Ctr
|
|
|
48
94
|
The browser helper writes stdin through the page-owned `window.__wasmIdleDebug.writeTerminalInput(...)`
|
|
49
95
|
hook instead of trying to click xterm's hidden helper textarea, which proved too flaky for repeatable
|
|
50
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`.
|
|
51
101
|
If Rust ever reports `invalid metadata files for crate core` or `Unsupported archive identifier`,
|
|
52
102
|
the browser almost always fetched a stale or wrong `wasm-rust` sysroot asset. Hard refresh the page
|
|
53
103
|
and resync `static/wasm-rust/` from the sibling `wasm-rust/dist/`.
|
|
@@ -66,6 +116,13 @@ session; the repo-owned regression target is the local preview path above.
|
|
|
66
116
|
## Runtime expectations
|
|
67
117
|
|
|
68
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`.
|
|
69
126
|
|
|
70
127
|
The Rust browser path now executes returned artifacts through the target-appropriate runtime inside
|
|
71
128
|
the Rust worker:
|
|
@@ -91,23 +148,39 @@ const runtimeAssets: PlaygroundRuntimeAssets = {
|
|
|
91
148
|
},
|
|
92
149
|
rust: {
|
|
93
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'
|
|
94
155
|
}
|
|
95
156
|
};
|
|
96
157
|
```
|
|
97
158
|
|
|
98
|
-
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.
|
|
99
160
|
|
|
100
161
|
If you want a host app to reuse the same runtime asset configuration for both `<Terminal>` and direct `playground(...)` access, bind it once:
|
|
101
162
|
|
|
102
163
|
```ts
|
|
103
164
|
import Terminal, { createPlaygroundBinding } from 'wasm-idle';
|
|
104
165
|
|
|
105
|
-
const wasmIdle = createPlaygroundBinding({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
});
|
|
111
184
|
|
|
112
185
|
const sandbox = await wasmIdle.load('PYTHON');
|
|
113
186
|
await sandbox.load('print("hi")', false);
|
|
@@ -117,4 +190,5 @@ await sandbox.load('print("hi")', false);
|
|
|
117
190
|
<Terminal {...wasmIdle.terminalProps} bind:terminal />
|
|
118
191
|
```
|
|
119
192
|
|
|
120
|
-
Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM,
|
|
193
|
+
Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, TeaVM, `wasm-rust`, and
|
|
194
|
+
`wasm-tinygo`.
|
|
@@ -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;
|