@marimo-team/islands 0.24.3-dev55 → 0.24.3-dev58
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/dist/{ErrorBoundary-uQMMsMtf.js → ErrorBoundary-D1_JTlgF.js} +1 -1
- package/dist/assets/__vite-browser-external-C9iR87Zl.js +1 -0
- package/dist/assets/{worker-BemUy60C.js → worker-D7hWi02M.js} +2 -2
- package/dist/{code-visibility-DXFlGQTg.js → code-visibility-Ct164a9c.js} +1 -1
- package/dist/{common-DHIrhrev.js → common-B8x6sgCj.js} +1 -1
- package/dist/{data-grid-overlay-editor-DhUvLXgC.js → data-grid-overlay-editor-DOiNVNSM.js} +1 -1
- package/dist/{feedback-button-DejCYgl6.js → feedback-button-ajITqvHB.js} +2 -2
- package/dist/{glide-data-editor-BeDrnFzb.js → glide-data-editor-BZeeQUSi.js} +3 -3
- package/dist/{globals-DIuJGoqK.js → globals-S6heT9pz.js} +1 -1
- package/dist/main.js +42 -30
- package/dist/{number-overlay-editor-CKgrZ6UJ.js → number-overlay-editor-DSBousWi.js} +1 -1
- package/dist/{reveal-component-sLNrYulm.js → reveal-component-CegCLPOh.js} +2 -2
- package/package.json +1 -1
- package/src/core/islands/worker-factory.ts +2 -12
- package/src/core/wasm/__tests__/bootstrap.test.ts +63 -0
- package/src/core/wasm/__tests__/bridge.test.ts +8 -0
- package/src/core/wasm/__tests__/fs.test.ts +22 -0
- package/src/core/wasm/__tests__/runtime-config.test.ts +58 -0
- package/src/core/wasm/bridge.ts +18 -16
- package/src/core/wasm/rpc.ts +2 -0
- package/src/core/wasm/runtime-config.ts +27 -0
- package/src/core/wasm/worker/bootstrap.ts +21 -11
- package/src/core/wasm/worker/fs.ts +4 -0
- package/src/core/wasm/worker/save-worker.ts +7 -0
- package/src/core/wasm/worker/types.ts +7 -4
- package/src/core/wasm/worker/worker.ts +6 -0
- package/src/utils/__tests__/worker.test.ts +81 -0
- package/src/utils/worker.ts +22 -0
- package/dist/assets/__vite-browser-external-BajL8aoc.js +0 -1
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { C as T, F as E, G as D, I as O, J as k, K as A, L as j, P as M, R as N,
|
|
|
7
7
|
import { a as se, f as ce, g as V, i as le, n as ue, o as de, p as fe, r as pe, t as me } from "./tooltip-qB8NlSqV.js";
|
|
8
8
|
import { $ as he, $t as ge, At as _e, B as ve, C as ye, D as be, Dt as xe, F as Se, G as Ce, Gt as we, I as Te, J as Ee, L as De, Mt as Oe, Nt as ke, O as Ae, Ot as je, P as Me, Q as Ne, Qt as Pe, S as Fe, T as Ie, Tt as Le, U as Re, Ut as ze, W as Be, Wt as Ve, X as He, Xt as Ue, Y as We, Yt as Ge, Zt as Ke, _n as qe, _t as Je, an as Ye, bn as Xe, c as Ze, cn as Qe, ct as $e, d as et, dn as tt, dt as nt, f as rt, fn as it, ft as at, g as ot, gn as st, gt as ct, h as lt, in as ut, it as dt, jt as ft, k as pt, kt as mt, l as ht, m as gt, mn as _t, n as vt, o as yt, on as bt, p as xt, pn as St, pt as Ct, q as wt, qt as Tt, r as Et, rn as Dt, rt as Ot, u as kt, v as At, w as jt, xt as Mt, yn as Nt, zt as Pt } from "./cells-BSgNCBZ8.js";
|
|
9
9
|
import { D as Ft, E as It, O as Lt, S as Rt, T as zt, _ as Bt, a as Vt, c as Ht, d as Ut, f as Wt, g as Gt, h as Kt, i as qt, l as Jt, m as Yt, n as Xt, o as Zt, p as Qt, r as $t, s as en, t as tn, u as nn, v as rn, w as an, x as on, y as sn } from "./spec-CAYwFeV6.js";
|
|
10
|
-
import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-
|
|
10
|
+
import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-B8x6sgCj.js";
|
|
11
11
|
import { c as di, i as fi, n as pi, o as mi, t as hi } from "./useLifecycle-DPgiiwwU.js";
|
|
12
12
|
import { t as gi } from "./check-B7cIK6gb.js";
|
|
13
13
|
import { A as _i, C as vi, D as yi, E as bi, M as xi, O as Si, S as Ci, T as wi, a as Ti, b as Ei, c as Di, i as Oi, k as ki, l as Ai, n as ji, o as Mi, r as Ni, s as Pi, t as Fi, w as Ii, x as Li } from "./select-C6plhmZr.js";
|
|
@@ -15,8 +15,8 @@ import { B as Ri, C as zi, J as Bi, Mt as Vi, Nt as Hi, S as Ui, Vt as Wi, W as
|
|
|
15
15
|
import { _ as ha, c as ga, d as _a, f as va, g as ya, h as ba, m as xa, u as Sa, v as Ca } from "./toDate-WXg1yLcI.js";
|
|
16
16
|
import { a as wa, c as Ta, d as Ea, f as Da, i as Oa, l as ka, n as Aa, o as ja, r as Ma, s as Na, t as Pa } from "./process-output-DFe2tVIY.js";
|
|
17
17
|
import { a as Fa, c as Ia, d as La, i as Ra, l as za, r as Ba, u as Va } from "./glide-utils-B3rUT5PI.js";
|
|
18
|
-
import { n as Ha, t as Ua } from "./ErrorBoundary-
|
|
19
|
-
import { n as Wa, t as Ga } from "./globals-
|
|
18
|
+
import { n as Ha, t as Ua } from "./ErrorBoundary-D1_JTlgF.js";
|
|
19
|
+
import { n as Wa, t as Ga } from "./globals-S6heT9pz.js";
|
|
20
20
|
import { t as Ka } from "./react-dom-BUgJ-ab_.js";
|
|
21
21
|
import { t as qa } from "./jsx-runtime-DgDPJV9S.js";
|
|
22
22
|
import { B as Ja, C as H, Ct as Ya, D as Xa, E as Za, Et as Qa, F as U, G as $a, H as eo, I as to, L as no, M as W, N as ro, O as io, P as ao, Q as oo, R as so, S as G, St as co, Tt as lo, U as uo, W as fo, Y as po, Z as mo, _ as ho, a as go, b as _o, bt as vo, c as yo, d as bo, dt as xo, f as So, g as Co, gt as wo, h as To, i as Eo, j as K, k as Do, l as Oo, m as ko, n as Ao, o as jo, p as Mo, r as No, s as Po, u as Fo, v as Io, vt as Lo, w as Ro, wt as zo, x as Bo, xt as Vo, y as Ho, yt as Uo } from "./use-restore-focus-D5D9xEjZ.js";
|
|
@@ -40,7 +40,7 @@ import { t as Ns } from "./useAsyncData-BD6AStMt.js";
|
|
|
40
40
|
import { i as Ps, n as Fs, r as Is } from "./formats-BR5iGb4s.js";
|
|
41
41
|
import { M as Ls, N as Rs } from "./vega-loader.browser-D95WfUcj.js";
|
|
42
42
|
import { a as zs, n as Bs } from "./loader-D2W4oM-3.js";
|
|
43
|
-
import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-
|
|
43
|
+
import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-Ct164a9c.js";
|
|
44
44
|
import { t as Ys } from "./useDeepCompareMemoize-D_HNdyvI.js";
|
|
45
45
|
import { t as Xs } from "./esm-BElI3qKH.js";
|
|
46
46
|
//#region ../node_modules/.pnpm/iconify-icon@2.3.0/node_modules/iconify-icon/dist/iconify-icon.mjs
|
|
@@ -10458,7 +10458,7 @@ function RC(e, t) {
|
|
|
10458
10458
|
}
|
|
10459
10459
|
//#endregion
|
|
10460
10460
|
//#region src/plugins/impl/DataEditorPlugin.tsx
|
|
10461
|
-
var zC = J.lazy(() => import("./glide-data-editor-
|
|
10461
|
+
var zC = J.lazy(() => import("./glide-data-editor-BZeeQUSi.js").then((e) => e.t)), BC = xS("marimo-data-editor", { cssStyles: [WS] }).withData(W({
|
|
10462
10462
|
initialValue: W({ edits: G(W({
|
|
10463
10463
|
rowIdx: K(),
|
|
10464
10464
|
columnId: U(),
|
|
@@ -29922,7 +29922,7 @@ function bR(e) {
|
|
|
29922
29922
|
}
|
|
29923
29923
|
//#endregion
|
|
29924
29924
|
//#region src/components/editor/renderers/slides-layout/slides-layout.tsx
|
|
29925
|
-
var xR = J.lazy(() => import("./reveal-component-
|
|
29925
|
+
var xR = J.lazy(() => import("./reveal-component-CegCLPOh.js")), SR = (e) => {
|
|
29926
29926
|
let t = (0, X.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = rs(Ce), s = a === "read" || o, c = rs(xt) > 1, [l, u] = (0, J.useState)(null), d;
|
|
29927
29927
|
t[0] !== i || t[1] !== n ? (d = vP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
|
|
29928
29928
|
let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
|
|
@@ -31916,31 +31916,43 @@ function DB(e) {
|
|
|
31916
31916
|
});
|
|
31917
31917
|
}
|
|
31918
31918
|
//#endregion
|
|
31919
|
+
//#region src/utils/worker.ts
|
|
31920
|
+
function OB(e, t) {
|
|
31921
|
+
if (e.origin === globalThis.origin) return new Worker(e.href, {
|
|
31922
|
+
...t,
|
|
31923
|
+
type: "module"
|
|
31924
|
+
});
|
|
31925
|
+
let n = `import ${JSON.stringify(e.href)}`, r = globalThis.origin === "null" ? `data:application/javascript,${encodeURIComponent(n)}` : URL.createObjectURL(new Blob([n], { type: "application/javascript" }));
|
|
31926
|
+
try {
|
|
31927
|
+
return new Worker(r, {
|
|
31928
|
+
...t,
|
|
31929
|
+
type: "module"
|
|
31930
|
+
});
|
|
31931
|
+
} finally {
|
|
31932
|
+
r.startsWith("blob:") && URL.revokeObjectURL(r);
|
|
31933
|
+
}
|
|
31934
|
+
}
|
|
31935
|
+
//#endregion
|
|
31919
31936
|
//#region src/core/islands/worker/worker.tsx?worker&url
|
|
31920
|
-
var
|
|
31937
|
+
var kB = "/assets/worker-D7hWi02M.js", AB = class {
|
|
31921
31938
|
url;
|
|
31922
31939
|
name;
|
|
31923
31940
|
constructor(e = {}) {
|
|
31924
31941
|
this.url = e.workerUrl || this.getDefaultWorkerUrl(), this.name = e.workerName || Ga();
|
|
31925
31942
|
}
|
|
31926
31943
|
create() {
|
|
31927
|
-
|
|
31928
|
-
type: "module",
|
|
31929
|
-
/* @vite-ignore */
|
|
31930
|
-
name: this.name
|
|
31931
|
-
});
|
|
31932
|
-
return URL.revokeObjectURL(n), r;
|
|
31944
|
+
return OB(new URL(this.url, import.meta.url), { name: this.name });
|
|
31933
31945
|
}
|
|
31934
31946
|
getDefaultWorkerUrl() {
|
|
31935
|
-
return
|
|
31947
|
+
return jB(kB);
|
|
31936
31948
|
}
|
|
31937
31949
|
};
|
|
31938
|
-
function
|
|
31950
|
+
function jB(e) {
|
|
31939
31951
|
return e.startsWith("./") ? e : e.startsWith("/") ? `.${e}` : `./${e}`;
|
|
31940
31952
|
}
|
|
31941
31953
|
//#endregion
|
|
31942
31954
|
//#region src/core/islands/bridge.ts
|
|
31943
|
-
var
|
|
31955
|
+
var MB = class {
|
|
31944
31956
|
rpc;
|
|
31945
31957
|
messageConsumer;
|
|
31946
31958
|
store;
|
|
@@ -31955,7 +31967,7 @@ var jB = class {
|
|
|
31955
31967
|
constructor(e = {}) {
|
|
31956
31968
|
this.store = e.store || os, this.root = e.root || document;
|
|
31957
31969
|
try {
|
|
31958
|
-
let t = (e.workerFactory || new
|
|
31970
|
+
let t = (e.workerFactory || new AB()).create();
|
|
31959
31971
|
this.rpc = DB(t), this.setupMessageListeners();
|
|
31960
31972
|
} catch (e) {
|
|
31961
31973
|
throw c.error("Failed to initialize IslandsPyodideBridge:", e), this.initialized.reject(e), e;
|
|
@@ -32132,30 +32144,30 @@ var jB = class {
|
|
|
32132
32144
|
return await e, t ?? n.promise;
|
|
32133
32145
|
}
|
|
32134
32146
|
destroy() {}
|
|
32135
|
-
},
|
|
32136
|
-
function
|
|
32137
|
-
return
|
|
32147
|
+
}, NB = null;
|
|
32148
|
+
function PB() {
|
|
32149
|
+
return NB ||= new MB(), NB;
|
|
32138
32150
|
}
|
|
32139
32151
|
//#endregion
|
|
32140
32152
|
//#region src/core/islands/main.ts
|
|
32141
|
-
var
|
|
32142
|
-
function
|
|
32153
|
+
var FB = PB(), IB;
|
|
32154
|
+
function LB() {
|
|
32143
32155
|
return document.querySelector(yu.ISLAND) ? Lz(document, { materialize: !1 }).length === 1 : !1;
|
|
32144
32156
|
}
|
|
32145
|
-
async function
|
|
32157
|
+
async function RB() {
|
|
32146
32158
|
let e = document.querySelectorAll(yu.ISLAND);
|
|
32147
32159
|
if (e.length !== 0) {
|
|
32148
32160
|
for (let t of e) t.classList.add(Cu.NAMESPACE);
|
|
32149
|
-
|
|
32150
|
-
throw
|
|
32151
|
-
}), await
|
|
32161
|
+
IB ??= mB({ bridge: FB }).catch((e) => {
|
|
32162
|
+
throw IB = void 0, e;
|
|
32163
|
+
}), await IB, await FB.initializeApps();
|
|
32152
32164
|
}
|
|
32153
32165
|
}
|
|
32154
|
-
async function
|
|
32155
|
-
await
|
|
32166
|
+
async function zB(e) {
|
|
32167
|
+
await FB.stopSession(e);
|
|
32156
32168
|
}
|
|
32157
|
-
|
|
32169
|
+
RB().catch((e) => {
|
|
32158
32170
|
c.error("Failed to initialize islands:", e);
|
|
32159
32171
|
});
|
|
32160
32172
|
//#endregion
|
|
32161
|
-
export {
|
|
32173
|
+
export { LB as canReplaceApp, RB as initialize, zB as stopApp };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
2
|
import { t } from "./react-ofJbxM4x.js";
|
|
3
|
-
import { a as n } from "./glide-data-editor-
|
|
3
|
+
import { a as n } from "./glide-data-editor-BZeeQUSi.js";
|
|
4
4
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
|
|
5
5
|
var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
|
|
6
6
|
name: "NumberOverlayEditorStyle",
|
|
@@ -4,13 +4,13 @@ import { t as o } from "./react-ofJbxM4x.js";
|
|
|
4
4
|
import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
5
|
import { g as c, t as l } from "./tooltip-qB8NlSqV.js";
|
|
6
6
|
import { G as u, w as d } from "./cells-BSgNCBZ8.js";
|
|
7
|
-
import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-
|
|
7
|
+
import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-B8x6sgCj.js";
|
|
8
8
|
import { M as w } from "./select-C6plhmZr.js";
|
|
9
9
|
import { J as T } from "./dropdown-menu-CZgjYSAh.js";
|
|
10
10
|
import { t as E } from "./jsx-runtime-DgDPJV9S.js";
|
|
11
11
|
import { q as ee } from "./use-restore-focus-D5D9xEjZ.js";
|
|
12
12
|
import { A as D, C as O, b as k } from "./useTheme-DzXmYZ5V.js";
|
|
13
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
13
|
+
import { a as A, n as te, t as ne } from "./code-visibility-Ct164a9c.js";
|
|
14
14
|
/* empty css */
|
|
15
15
|
var j = c("sticky-note", [["path", {
|
|
16
16
|
d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { createModuleWorker } from "@/utils/worker";
|
|
3
4
|
import { getMarimoVersion } from "../meta/globals";
|
|
4
5
|
import workerUrl from "./worker/worker.tsx?worker&url";
|
|
5
6
|
|
|
@@ -47,20 +48,9 @@ export class DefaultWorkerFactory implements WorkerFactory {
|
|
|
47
48
|
* Creates a new Pyodide worker
|
|
48
49
|
*/
|
|
49
50
|
create(): Worker {
|
|
50
|
-
|
|
51
|
-
const blob = new Blob([js], { type: "application/javascript" });
|
|
52
|
-
const objURL = URL.createObjectURL(blob);
|
|
53
|
-
|
|
54
|
-
const worker = new Worker(objURL, {
|
|
55
|
-
type: "module",
|
|
56
|
-
/* @vite-ignore */
|
|
51
|
+
return createModuleWorker(new URL(this.url, import.meta.url), {
|
|
57
52
|
name: this.name,
|
|
58
53
|
});
|
|
59
|
-
|
|
60
|
-
// Blob URL can be revoked once the worker has loaded the script
|
|
61
|
-
URL.revokeObjectURL(objURL);
|
|
62
|
-
|
|
63
|
-
return worker;
|
|
64
54
|
}
|
|
65
55
|
|
|
66
56
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { loadPyodide } from "pyodide";
|
|
4
|
+
import type { PyodideInterface } from "pyodide";
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { DefaultWasmController } from "../worker/bootstrap";
|
|
7
|
+
|
|
8
|
+
vi.mock("pyodide", () => ({ loadPyodide: vi.fn() }));
|
|
9
|
+
vi.mock("../worker/getMarimoWheel", () => ({
|
|
10
|
+
getMarimoWheel: () => "marimo-base",
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const micropip = { set_index_urls: vi.fn(), destroy: vi.fn() };
|
|
14
|
+
const pyodide = { pyimport: vi.fn(() => micropip) };
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
vi.mocked(loadPyodide).mockResolvedValue(
|
|
19
|
+
pyodide as unknown as PyodideInterface,
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("WASM runtime sources", () => {
|
|
24
|
+
it("preserves the default runtime sources", async () => {
|
|
25
|
+
await new DefaultWasmController().bootstrap({
|
|
26
|
+
version: "0.24.2",
|
|
27
|
+
pyodideVersion: "v314.0.0",
|
|
28
|
+
});
|
|
29
|
+
expect(loadPyodide).toHaveBeenCalledWith(
|
|
30
|
+
expect.objectContaining({
|
|
31
|
+
indexURL: "https://cdn.jsdelivr.net/pyodide/v314.0.0/full/",
|
|
32
|
+
packageBaseUrl: "https://cdn.jsdelivr.net/pyodide/v314.0.0/full/",
|
|
33
|
+
lockFileURL:
|
|
34
|
+
"https://wasm.marimo.app/pyodide-lock.json?v=0.24.2&pyodide=v314.0.0",
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
expect(pyodide.pyimport).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("configures runtime and package sources before returning the interpreter", async () => {
|
|
41
|
+
const controller = new DefaultWasmController();
|
|
42
|
+
await controller.bootstrap({
|
|
43
|
+
version: "0.24.2",
|
|
44
|
+
pyodideVersion: "v314.0.0",
|
|
45
|
+
pyodideIndexUrl: "https://example.com/runtime/",
|
|
46
|
+
pyodideLockfileUrl: "https://example.com/lock.json",
|
|
47
|
+
pypiIndexUrl: "https://example.com/pypi/{package_name}/json",
|
|
48
|
+
});
|
|
49
|
+
expect(loadPyodide).toHaveBeenCalledWith(
|
|
50
|
+
expect.objectContaining({
|
|
51
|
+
indexURL: "https://example.com/runtime/",
|
|
52
|
+
packageBaseUrl: "https://example.com/runtime/",
|
|
53
|
+
lockFileURL: "https://example.com/lock.json",
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
expect(pyodide.pyimport).toHaveBeenCalledWith("micropip");
|
|
57
|
+
expect(micropip.set_index_urls).toHaveBeenCalledWith(
|
|
58
|
+
"https://example.com/pypi/{package_name}/json",
|
|
59
|
+
);
|
|
60
|
+
expect(micropip.destroy).toHaveBeenCalledOnce();
|
|
61
|
+
expect(controller.requirePyodide).toBe(pyodide);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -9,12 +9,14 @@ const {
|
|
|
9
9
|
mockNotebookReadFile,
|
|
10
10
|
mockReadNotebook,
|
|
11
11
|
mockSaveNotebook,
|
|
12
|
+
mockBootstrap,
|
|
12
13
|
rpcListeners,
|
|
13
14
|
} = vi.hoisted(() => ({
|
|
14
15
|
mockBridge: vi.fn(),
|
|
15
16
|
mockNotebookReadFile: vi.fn(),
|
|
16
17
|
mockReadNotebook: vi.fn(),
|
|
17
18
|
mockSaveNotebook: vi.fn(),
|
|
19
|
+
mockBootstrap: vi.fn(),
|
|
18
20
|
rpcListeners: {} as Record<string, () => void>,
|
|
19
21
|
}));
|
|
20
22
|
|
|
@@ -40,6 +42,7 @@ vi.stubGlobal("URL", MockURL);
|
|
|
40
42
|
|
|
41
43
|
vi.mock("@/core/wasm/rpc", () => ({
|
|
42
44
|
getWorkerRPC: () => ({
|
|
45
|
+
send: { bootstrap: mockBootstrap },
|
|
43
46
|
proxy: {
|
|
44
47
|
request: {
|
|
45
48
|
bridge: mockBridge,
|
|
@@ -122,6 +125,11 @@ describe("PyodideBridge.readCode", () => {
|
|
|
122
125
|
|
|
123
126
|
await PyodideBridge.INSTANCE.readCode();
|
|
124
127
|
|
|
128
|
+
expect(mockBootstrap).toHaveBeenCalledWith({
|
|
129
|
+
pyodideIndexUrl: undefined,
|
|
130
|
+
pyodideLockfileUrl: undefined,
|
|
131
|
+
pypiIndexUrl: undefined,
|
|
132
|
+
});
|
|
125
133
|
expect(mockNotebookReadFile).not.toHaveBeenCalled();
|
|
126
134
|
});
|
|
127
135
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { PyodideInterface } from "pyodide";
|
|
3
|
+
import { afterEach, expect, it, vi } from "vitest";
|
|
4
|
+
import { WasmFileSystem } from "../worker/fs";
|
|
5
|
+
|
|
6
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
7
|
+
|
|
8
|
+
it.each(["https://notebook.example", "null"])(
|
|
9
|
+
"mounts persistent storage only for a non-opaque origin: %s",
|
|
10
|
+
(origin) => {
|
|
11
|
+
vi.stubGlobal("origin", origin);
|
|
12
|
+
const mount = vi.fn();
|
|
13
|
+
const IDBFS = {};
|
|
14
|
+
const pyodide = {
|
|
15
|
+
FS: { mount, filesystems: { IDBFS } },
|
|
16
|
+
} as unknown as PyodideInterface;
|
|
17
|
+
WasmFileSystem.mountFS(pyodide);
|
|
18
|
+
expect(mount.mock.calls).toEqual(
|
|
19
|
+
origin === "null" ? [] : [[IDBFS, { root: "." }, "/marimo"]],
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, assert, describe, expect, it } from "vitest";
|
|
4
|
+
import { getWasmRuntimeConfig } from "../runtime-config";
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
document.head.innerHTML = "";
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("getWasmRuntimeConfig", () => {
|
|
11
|
+
it("uses runtime defaults when no URLs are configured", () => {
|
|
12
|
+
expect(getWasmRuntimeConfig()).toEqual({
|
|
13
|
+
pyodideIndexUrl: undefined,
|
|
14
|
+
pyodideLockfileUrl: undefined,
|
|
15
|
+
pypiIndexUrl: undefined,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("resolves URLs against the page base and preserves micropip placeholders", () => {
|
|
20
|
+
document.head.innerHTML = `
|
|
21
|
+
<base href="https://example.com/blog/post/">
|
|
22
|
+
<marimo-wasm
|
|
23
|
+
data-pyodide-index-url="../runtime/"
|
|
24
|
+
data-pyodide-lockfile-url="/locks/pyodide.json?v=1&x=2"
|
|
25
|
+
data-pypi-index-url="https://packages.example.com/{package_name}/json"
|
|
26
|
+
></marimo-wasm>`;
|
|
27
|
+
expect(getWasmRuntimeConfig()).toEqual({
|
|
28
|
+
pyodideIndexUrl: "https://example.com/blog/runtime/",
|
|
29
|
+
pyodideLockfileUrl: "https://example.com/locks/pyodide.json?v=1&x=2",
|
|
30
|
+
pypiIndexUrl: "https://packages.example.com/{package_name}/json",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it.each(["pyodideIndexUrl", "pyodideLockfileUrl", "pypiIndexUrl"])(
|
|
35
|
+
"ignores malformed %s while preserving valid overrides",
|
|
36
|
+
(key) => {
|
|
37
|
+
document.head.innerHTML = `
|
|
38
|
+
<base href="https://example.com/notebook/">
|
|
39
|
+
<marimo-wasm
|
|
40
|
+
data-pyodide-index-url="./pyodide/"
|
|
41
|
+
data-pyodide-lockfile-url="./lockfile/pyodide.json"
|
|
42
|
+
data-pypi-index-url="./packages/{package_name}.json"
|
|
43
|
+
></marimo-wasm>`;
|
|
44
|
+
const element = document.querySelector<HTMLElement>("marimo-wasm");
|
|
45
|
+
assert(element);
|
|
46
|
+
element.dataset[key] = "https://invalid host/";
|
|
47
|
+
|
|
48
|
+
expect(getWasmRuntimeConfig()).toEqual({
|
|
49
|
+
pyodideIndexUrl: "https://example.com/notebook/pyodide/",
|
|
50
|
+
pyodideLockfileUrl:
|
|
51
|
+
"https://example.com/notebook/lockfile/pyodide.json",
|
|
52
|
+
pypiIndexUrl:
|
|
53
|
+
"https://example.com/notebook/packages/{package_name}.json",
|
|
54
|
+
[key]: undefined,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
});
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { throwNotImplemented } from "@/utils/functions";
|
|
|
9
9
|
import { Logger } from "@/utils/Logger";
|
|
10
10
|
import { reloadSafe } from "@/utils/reload-safe";
|
|
11
11
|
import { generateUUID } from "@/utils/uuid";
|
|
12
|
+
import { createModuleWorker } from "@/utils/worker";
|
|
12
13
|
import { notebookIsRunningAtom } from "../cells/cells";
|
|
13
14
|
import type { CommandMessage } from "../kernel/messages";
|
|
14
15
|
import { getMarimoVersion } from "../meta/globals";
|
|
@@ -41,10 +42,13 @@ import { BasicTransport } from "../websocket/transports/basic";
|
|
|
41
42
|
import type { IConnectionTransport } from "../websocket/transports/transport";
|
|
42
43
|
import { PyodideRouter } from "./router";
|
|
43
44
|
import { getWorkerRPC } from "./rpc";
|
|
45
|
+
import { getWasmRuntimeConfig } from "./runtime-config";
|
|
44
46
|
import { createShareableLink } from "./share";
|
|
45
47
|
import { wasmInitStateAtom } from "./state";
|
|
46
48
|
import { fallbackFileStore, notebookFileStore } from "./store";
|
|
47
49
|
import { isWasm } from "./utils";
|
|
50
|
+
import saveWorkerUrl from "./worker/save-worker.ts?worker&url";
|
|
51
|
+
import workerUrl from "./worker/worker.ts?worker&url";
|
|
48
52
|
import type { SaveWorkerSchema } from "./worker/save-worker";
|
|
49
53
|
import type { WorkerSchema } from "./worker/worker";
|
|
50
54
|
|
|
@@ -81,19 +85,20 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
81
85
|
};
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
const runtimeConfig = getWasmRuntimeConfig();
|
|
89
|
+
|
|
84
90
|
// Create save worker
|
|
85
|
-
const saveWorker =
|
|
86
|
-
|
|
87
|
-
new URL("./worker/save-worker.ts", import.meta.url),
|
|
91
|
+
const saveWorker = createModuleWorker(
|
|
92
|
+
new URL(saveWorkerUrl, import.meta.url),
|
|
88
93
|
{
|
|
89
|
-
type: "module",
|
|
90
94
|
// Pass the version (and optional capability suffix) to the worker
|
|
91
|
-
/* @vite-ignore */
|
|
92
95
|
name: getWasmWorkerName(),
|
|
93
96
|
},
|
|
94
97
|
);
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
const rpc = getWorkerRPC<SaveWorkerSchema>(saveWorker);
|
|
100
|
+
rpc.send.bootstrap(runtimeConfig);
|
|
101
|
+
return rpc.proxy.request;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
private constructor() {
|
|
@@ -101,20 +106,17 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
101
106
|
return;
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
const runtimeConfig = getWasmRuntimeConfig();
|
|
110
|
+
|
|
104
111
|
// Create a worker
|
|
105
|
-
const worker = new
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
type: "module",
|
|
110
|
-
// Pass the version (and optional capability suffix) to the worker
|
|
111
|
-
/* @vite-ignore */
|
|
112
|
-
name: getWasmWorkerName(),
|
|
113
|
-
},
|
|
114
|
-
);
|
|
112
|
+
const worker = createModuleWorker(new URL(workerUrl, import.meta.url), {
|
|
113
|
+
// Pass the version (and optional capability suffix) to the worker
|
|
114
|
+
name: getWasmWorkerName(),
|
|
115
|
+
});
|
|
115
116
|
|
|
116
117
|
// Create the RPC
|
|
117
118
|
this.rpc = getWorkerRPC<WorkerSchema>(worker);
|
|
119
|
+
this.rpc.send.bootstrap(runtimeConfig);
|
|
118
120
|
|
|
119
121
|
// Listeners
|
|
120
122
|
this.rpc.addMessageListener("ready", () => {
|
package/src/core/wasm/rpc.ts
CHANGED
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
type RPCSchema,
|
|
7
7
|
} from "rpc-anywhere";
|
|
8
8
|
import { Logger } from "@/utils/Logger";
|
|
9
|
+
import type { WasmRuntimeConfig } from "./runtime-config";
|
|
9
10
|
import { TRANSPORT_ID } from "./worker/constants";
|
|
10
11
|
|
|
11
12
|
export type ParentSchema = RPCSchema<{
|
|
12
13
|
messages: {
|
|
13
14
|
consumerReady: {};
|
|
15
|
+
bootstrap: WasmRuntimeConfig;
|
|
14
16
|
};
|
|
15
17
|
}>;
|
|
16
18
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
export interface WasmRuntimeConfig {
|
|
4
|
+
pyodideIndexUrl?: string;
|
|
5
|
+
pyodideLockfileUrl?: string;
|
|
6
|
+
pypiIndexUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getWasmRuntimeConfig(): WasmRuntimeConfig {
|
|
10
|
+
const element = document.querySelector<HTMLElement>("marimo-wasm");
|
|
11
|
+
const resolve = (url: string | undefined) => {
|
|
12
|
+
try {
|
|
13
|
+
return url ? new URL(url, document.baseURI).href : undefined;
|
|
14
|
+
} catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
pyodideIndexUrl: resolve(element?.dataset.pyodideIndexUrl),
|
|
20
|
+
pyodideLockfileUrl: resolve(element?.dataset.pyodideLockfileUrl),
|
|
21
|
+
// URL serialization escapes the placeholder understood by micropip.
|
|
22
|
+
pypiIndexUrl: resolve(element?.dataset.pypiIndexUrl)?.replaceAll(
|
|
23
|
+
"%7Bpackage_name%7D",
|
|
24
|
+
"{package_name}",
|
|
25
|
+
),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -41,10 +41,9 @@ export class DefaultWasmController implements WasmController {
|
|
|
41
41
|
return this.pyodide;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async bootstrap(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}): Promise<PyodideInterface> {
|
|
44
|
+
async bootstrap(
|
|
45
|
+
opts: Parameters<WasmController["bootstrap"]>[0],
|
|
46
|
+
): Promise<PyodideInterface> {
|
|
48
47
|
const pyodide = await this.loadPyodideAndPackages(opts);
|
|
49
48
|
|
|
50
49
|
if (MAKE_SNAPSHOT) {
|
|
@@ -55,10 +54,9 @@ export class DefaultWasmController implements WasmController {
|
|
|
55
54
|
return pyodide;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
private async loadPyodideAndPackages(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}): Promise<PyodideInterface> {
|
|
57
|
+
private async loadPyodideAndPackages(
|
|
58
|
+
opts: Parameters<WasmController["bootstrap"]>[0],
|
|
59
|
+
): Promise<PyodideInterface> {
|
|
62
60
|
if (!loadPyodide) {
|
|
63
61
|
throw new Error("loadPyodide is not defined");
|
|
64
62
|
}
|
|
@@ -68,7 +66,9 @@ export class DefaultWasmController implements WasmController {
|
|
|
68
66
|
// Without this, this fails in Firefox with
|
|
69
67
|
// `Could not extract indexURL path from pyodide module`
|
|
70
68
|
// This fixes for Firefox and does not break Chrome/others
|
|
71
|
-
const indexURL =
|
|
69
|
+
const indexURL =
|
|
70
|
+
opts.pyodideIndexUrl ??
|
|
71
|
+
`https://cdn.jsdelivr.net/pyodide/${opts.pyodideVersion}/full/`;
|
|
72
72
|
const pyodide = await loadPyodide({
|
|
73
73
|
// Perf: These get loaded while pyodide is being bootstrapped
|
|
74
74
|
packages: [
|
|
@@ -81,16 +81,26 @@ export class DefaultWasmController implements WasmController {
|
|
|
81
81
|
"packaging",
|
|
82
82
|
],
|
|
83
83
|
_makeSnapshot: MAKE_SNAPSHOT,
|
|
84
|
-
lockFileURL:
|
|
84
|
+
lockFileURL:
|
|
85
|
+
opts.pyodideLockfileUrl ??
|
|
86
|
+
`https://wasm.marimo.app/pyodide-lock.json?v=${opts.version}&pyodide=${opts.pyodideVersion}`,
|
|
85
87
|
indexURL,
|
|
86
88
|
// Since Pyodide 0.28.0, when lockFileURL is set, the package base URL
|
|
87
89
|
// defaults to the lockfile's URL (wasm.marimo.app) instead of indexURL.
|
|
88
90
|
// Unlike Node, browsers get no CDN fallback on a failed fetch, so we
|
|
89
|
-
// should pin packageBaseUrl
|
|
91
|
+
// should pin packageBaseUrl to indexURL to restore
|
|
90
92
|
// the resolution akin to pre-0.28.
|
|
91
93
|
packageBaseUrl: indexURL,
|
|
92
94
|
convertNullToNone: true,
|
|
93
95
|
});
|
|
96
|
+
if (opts.pypiIndexUrl) {
|
|
97
|
+
const micropip = pyodide.pyimport("micropip");
|
|
98
|
+
try {
|
|
99
|
+
micropip.set_index_urls(opts.pypiIndexUrl);
|
|
100
|
+
} finally {
|
|
101
|
+
micropip.destroy();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
94
104
|
this.pyodide = pyodide;
|
|
95
105
|
span.end("ok");
|
|
96
106
|
return pyodide;
|
|
@@ -20,6 +20,10 @@ export const WasmFileSystem = {
|
|
|
20
20
|
FS.chdir(HOME_DIR);
|
|
21
21
|
},
|
|
22
22
|
mountFS: (pyodide: PyodideInterface) => {
|
|
23
|
+
// Opaque origins (single-file exports) use the in-memory filesystem.
|
|
24
|
+
if (globalThis.origin === "null") {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
23
27
|
const FS = getFS(pyodide);
|
|
24
28
|
// Mount the filesystem
|
|
25
29
|
FS.mount(pyodide.FS.filesystems.IDBFS, { root: "." }, HOME_DIR);
|
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
} from "rpc-anywhere";
|
|
10
10
|
import type { SaveNotebookRequest } from "@/core/network/types";
|
|
11
11
|
import { decodeUtf8 } from "@/utils/strings";
|
|
12
|
+
import { Deferred } from "../../../utils/Deferred";
|
|
12
13
|
import { prettyError } from "../../../utils/errors";
|
|
13
14
|
import { Logger } from "../../../utils/Logger";
|
|
14
15
|
import type { ParentSchema } from "../rpc";
|
|
16
|
+
import type { WasmRuntimeConfig } from "../runtime-config";
|
|
15
17
|
import { TRANSPORT_ID } from "./constants";
|
|
16
18
|
import { WasmFileSystem } from "./fs";
|
|
17
19
|
import { getController } from "./getController";
|
|
@@ -25,6 +27,8 @@ declare const self: Window & {
|
|
|
25
27
|
pyodide: PyodideInterface;
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
const runtimeConfig = new Deferred<WasmRuntimeConfig>();
|
|
31
|
+
|
|
28
32
|
// Initialize
|
|
29
33
|
async function loadPyodideAndPackages() {
|
|
30
34
|
try {
|
|
@@ -36,6 +40,7 @@ async function loadPyodideAndPackages() {
|
|
|
36
40
|
const controller = await getController(marimoVersion);
|
|
37
41
|
self.controller = controller;
|
|
38
42
|
self.pyodide = await controller.bootstrap({
|
|
43
|
+
...(await runtimeConfig.promise),
|
|
39
44
|
version: marimoVersion,
|
|
40
45
|
pyodideVersion: pyodideVersion,
|
|
41
46
|
});
|
|
@@ -100,6 +105,8 @@ const rpc = createRPC<SaveWorkerSchema, ParentSchema>({
|
|
|
100
105
|
requestHandler,
|
|
101
106
|
});
|
|
102
107
|
|
|
108
|
+
rpc.addMessageListener("bootstrap", (config) => runtimeConfig.resolve(config));
|
|
109
|
+
|
|
103
110
|
rpc.send("ready", {});
|
|
104
111
|
|
|
105
112
|
function getMarimoVersion() {
|