@marimo-team/frontend 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.
Files changed (114) hide show
  1. package/dist/assets/{ConnectedDataExplorerComponent-COS9QB8y.js → ConnectedDataExplorerComponent-BaunPY14.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-B0WULAu7.js → ErrorBoundary-B7tf_7B1.js} +2 -2
  3. package/dist/assets/{LazyAnyLanguageCodeMirror-BBJUf-Np.js → LazyAnyLanguageCodeMirror-C3FCflY3.js} +2 -2
  4. package/dist/assets/{RunButton-BfJ-ltdu.js → RunButton-CE8Rn7A2.js} +1 -1
  5. package/dist/assets/__vite-browser-external-BKSyd8Ok.js +1 -0
  6. package/dist/assets/__vite-browser-external-ZTY8EBdt.js +1 -0
  7. package/dist/assets/{abnfDiagram-VCTEODGH-DboXQG6R.js → abnfDiagram-VCTEODGH-CgLkNDl-.js} +1 -1
  8. package/dist/assets/{add-cell-with-ai-COKLzuEj.js → add-cell-with-ai-DZpYEFLT.js} +1 -1
  9. package/dist/assets/{agent-panel-BhikzzeA.js → agent-panel-Blde_5Nw.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-CDPQlQb4.js → ai-model-dropdown-BAV2ckFD.js} +1 -1
  11. package/dist/assets/{any-language-editor-JD5Y88Ry.js → any-language-editor-Br1nAB4H.js} +1 -1
  12. package/dist/assets/{app-config-button-B0d_lhhw.js → app-config-button-CAcrPkSw.js} +1 -1
  13. package/dist/assets/{architectureDiagram-5GKGNRK7-BvWNI_5q.js → architectureDiagram-5GKGNRK7--6i_ophu.js} +1 -1
  14. package/dist/assets/{blockDiagram-I7D4REHJ-Dlsb_LDd.js → blockDiagram-I7D4REHJ-8Kt9Qfvn.js} +1 -1
  15. package/dist/assets/{cache-panel-TpjG4mLz.js → cache-panel-B7eXY-wH.js} +1 -1
  16. package/dist/assets/{chat-display-Blm598xg.js → chat-display-Cm9AooD6.js} +1 -1
  17. package/dist/assets/{chat-panel-IDQSw-2o.js → chat-panel-BJulebpm.js} +1 -1
  18. package/dist/assets/{chat-ui-BCSAa-3N.js → chat-ui-CbvVmHX5.js} +1 -1
  19. package/dist/assets/{chunk-IMKFNOWR-mx4yRsyK.js → chunk-IMKFNOWR-LOXuyKpq.js} +1 -1
  20. package/dist/assets/{chunk-SHT3W25Y--1D03dcz.js → chunk-SHT3W25Y-DSHCLgMi.js} +1 -1
  21. package/dist/assets/{chunk-SVP7TREG-D-8rbb0u.js → chunk-SVP7TREG-DBEt2afY.js} +1 -1
  22. package/dist/assets/{chunk-TICWLB2K-BS0UFo-m.js → chunk-TICWLB2K-HWt9N6bw.js} +1 -1
  23. package/dist/assets/{classDiagram-ZZMXUADV-D-Yj3iaX.js → classDiagram-ZZMXUADV-_R63PS_W.js} +1 -1
  24. package/dist/assets/{classDiagram-v2-VYDZK3BY-D-Yj3iaX.js → classDiagram-v2-VYDZK3BY-_R63PS_W.js} +1 -1
  25. package/dist/assets/{code-visibility-CtcGTxtC.js → code-visibility-BAomF5-u.js} +1 -1
  26. package/dist/assets/{command-palette-BdrVdLjt.js → command-palette-ls_CWFw2.js} +1 -1
  27. package/dist/assets/{common-Ck3N5kYX.js → common-CFEYk4ZD.js} +1 -1
  28. package/dist/assets/{components-D4Ld_zGl.js → components-B19v8LfN.js} +1 -1
  29. package/dist/assets/{components-CZyPJUeR.js → components-CoCDuD3v.js} +1 -1
  30. package/dist/assets/{cynefinDiagram-5FMLGOSQ-CmSj7gh3.js → cynefinDiagram-5FMLGOSQ-DzvzfeBu.js} +1 -1
  31. package/dist/assets/{dagre-B7KPVDPi.js → dagre-Du9KcfwW.js} +1 -1
  32. package/dist/assets/{dagre-GXQ25YYZ-CWQaKLCd.js → dagre-GXQ25YYZ-Cn_twVWm.js} +1 -1
  33. package/dist/assets/{data-grid-overlay-editor-APdEQBzl.js → data-grid-overlay-editor-BybtNCnw.js} +1 -1
  34. package/dist/assets/{dependency-graph-panel-BN7HnK2P.js → dependency-graph-panel-bkpt5uV5.js} +1 -1
  35. package/dist/assets/{diagram-S7CK7UJ4-9eNHnvs_.js → diagram-S7CK7UJ4-r38unUVd.js} +1 -1
  36. package/dist/assets/{diagram-UQ7AKVKN-BRuPC1Wd.js → diagram-UQ7AKVKN-B4nKOViW.js} +1 -1
  37. package/dist/assets/{diagram-VX7I27RA-8hfdpbGT.js → diagram-VX7I27RA-BWKnMlwu.js} +1 -1
  38. package/dist/assets/{diagram-Z3DM3KII-DRSErXLE.js → diagram-Z3DM3KII-DhLOR-V2.js} +1 -1
  39. package/dist/assets/{documentation-panel-DLoR1lNz.js → documentation-panel-CT9lUKBw.js} +1 -1
  40. package/dist/assets/{ebnfDiagram-PWID7BFC--5KxiZWp.js → ebnfDiagram-PWID7BFC-DeT9RQwz.js} +1 -1
  41. package/dist/assets/{edit-page-DEA_9yzH.js → edit-page-D_JZxdDI.js} +4 -4
  42. package/dist/assets/{erDiagram-RLTQ6QDP-CvEz2ZIr.js → erDiagram-RLTQ6QDP-BRjuZ28D.js} +1 -1
  43. package/dist/assets/{error-panel-DiprKIxB.js → error-panel-BKzymMJt.js} +1 -1
  44. package/dist/assets/{feedback-button-BbkK__l4.js → feedback-button-BZ4eWgUl.js} +1 -1
  45. package/dist/assets/{file-explorer-panel-Bb4Apx-N.js → file-explorer-panel-CFSRfg8a.js} +1 -1
  46. package/dist/assets/{file-name-input-Dv-bmt0C.js → file-name-input-DCYH4Kqg.js} +1 -1
  47. package/dist/assets/{flowDiagram-HODETNUW-jk_3qz7x.js → flowDiagram-HODETNUW-B930yEc1.js} +1 -1
  48. package/dist/assets/{gallery-page-BtIDlcO9.js → gallery-page-C6msmdvq.js} +1 -1
  49. package/dist/assets/{glide-data-editor-DTuLqk5M.js → glide-data-editor-BsfOFrfe.js} +4 -4
  50. package/dist/assets/{graphlib-BQHSCXhd.js → graphlib-D47HZg_Z.js} +1 -1
  51. package/dist/assets/has-DkxB0Eh3.js +1 -0
  52. package/dist/assets/{home-page-DcLD2SYq.js → home-page-BbcGWO0y.js} +1 -1
  53. package/dist/assets/index-nH9dQ13U.js +62 -0
  54. package/dist/assets/{infoDiagram-27XIBGKW-BvTlbZly.js → infoDiagram-27XIBGKW-C1-xZcQU.js} +1 -1
  55. package/dist/assets/{ishikawaDiagram-5VMMS53U-Bd0JPSkJ.js → ishikawaDiagram-5VMMS53U-B60ECldo.js} +1 -1
  56. package/dist/assets/{kanban-definition-UXKFOSKX-DnWi5lN9.js → kanban-definition-UXKFOSKX-DK3xk3aO.js} +1 -1
  57. package/dist/assets/{logs-panel-WXuCywjT.js → logs-panel-Dk5nRS20.js} +1 -1
  58. package/dist/assets/{mermaid-FMOV29Gw.js → mermaid-CeSZtPQ7.js} +4 -4
  59. package/dist/assets/{mindmap-definition-YA3MSWOX-CE7bu4uQ.js → mindmap-definition-YA3MSWOX-BB-BWlMB.js} +1 -1
  60. package/dist/assets/{number-overlay-editor-qnMbBZBS.js → number-overlay-editor-BBlzp0N9.js} +1 -1
  61. package/dist/assets/{outline-panel-Ra4ZfPaK.js → outline-panel-B4nxCpLH.js} +1 -1
  62. package/dist/assets/{packages-panel-Bx507J_e.js → packages-panel-CfPZKjPk.js} +1 -1
  63. package/dist/assets/{panels-WxJBrHDO.js → panels-BWU5O17j.js} +3 -3
  64. package/dist/assets/{pegDiagram-XKGWAZYB-Di8y4ZKK.js → pegDiagram-XKGWAZYB-C1RBmCCz.js} +1 -1
  65. package/dist/assets/{pieDiagram-E7YTZNPT-D2WKj0zg.js → pieDiagram-E7YTZNPT-BVo3j1st.js} +1 -1
  66. package/dist/assets/{process-output-BvPR1InQ.js → process-output-CPvGqY7r.js} +1 -1
  67. package/dist/assets/{railroadDiagram-O6MQD6OU-BSD9hFAB.js → railroadDiagram-O6MQD6OU-CKqTw0oi.js} +1 -1
  68. package/dist/assets/{requirementDiagram-BXWQKSXE-C4Is-Eo9.js → requirementDiagram-BXWQKSXE-DnjSRQN8.js} +1 -1
  69. package/dist/assets/{reveal-component-CLf7EQgk.js → reveal-component-CbEXjUJn.js} +1 -1
  70. package/dist/assets/{run-page-CjJJvVx_.js → run-page-txqyXqLW.js} +1 -1
  71. package/dist/assets/save-worker-CMa6Cvnx.js +113 -0
  72. package/dist/assets/{scratchpad-panel-u5F0VTl4.js → scratchpad-panel-BirgND3s.js} +1 -1
  73. package/dist/assets/{secrets-panel-DUFKRVaH.js → secrets-panel-Dy_oak_1.js} +1 -1
  74. package/dist/assets/{sequenceDiagram-WJ2MYXX4-BHyxbrPL.js → sequenceDiagram-WJ2MYXX4-CZVbl-1N.js} +1 -1
  75. package/dist/assets/{session-panel-BY4S5P_s.js → session-panel-C2dlC4QT.js} +1 -1
  76. package/dist/assets/{snippets-panel-Cq4K4hBV.js → snippets-panel-BmO3Y6z8.js} +1 -1
  77. package/dist/assets/{stateDiagram-D77RDMKH-Cv5gcew4.js → stateDiagram-D77RDMKH-BHcKHZmR.js} +1 -1
  78. package/dist/assets/{stateDiagram-v2-MP3YSRHH-DkKFuvSs.js → stateDiagram-v2-MP3YSRHH-CmqQh4T2.js} +1 -1
  79. package/dist/assets/{swimlanes-42K2YHIH-BHKDxubG.js → swimlanes-42K2YHIH-WuvP6CDS.js} +1 -1
  80. package/dist/assets/{swimlanesDiagram-VR7AAH4N-Bh4LMjQ2.js → swimlanesDiagram-VR7AAH4N-B4c5y_p2.js} +1 -1
  81. package/dist/assets/{swiper-component-CcIKtHf1.js → swiper-component-BR-epejw.js} +1 -1
  82. package/dist/assets/{terminal-BjVPspc3.js → terminal-DrvDl_d5.js} +1 -1
  83. package/dist/assets/{timeline-definition-24CTP7MA-DwkTapoq.js → timeline-definition-24CTP7MA-DKn76-RQ.js} +1 -1
  84. package/dist/assets/{tracing-Caq9OuIL.js → tracing-BXDOsNGf.js} +1 -1
  85. package/dist/assets/{tracing-panel-CtPZXpUL.js → tracing-panel-V_ZVLq68.js} +2 -2
  86. package/dist/assets/{useCellActionButton-OUIwiRu0.js → useCellActionButton-BeczgoeM.js} +1 -1
  87. package/dist/assets/{vega-component-D-72a839.js → vega-component-B8gGF1Ew.js} +1 -1
  88. package/dist/assets/{vega-embed-container-BBdTcDp3.js → vega-embed-container-CXv4_JzG.js} +1 -1
  89. package/dist/assets/{vennDiagram-4TSXK5OY-KTiFwgD1.js → vennDiagram-4TSXK5OY-nQpUsTlH.js} +1 -1
  90. package/dist/assets/{wardleyDiagram-VM6X3IG4-Bfwv76R-.js → wardleyDiagram-VM6X3IG4-BQk9IGjL.js} +1 -1
  91. package/dist/assets/{worker-BXMsnprR.js → worker-B8c-1v-F.js} +5 -5
  92. package/dist/assets/{xychartDiagram-S5SC5T6Z-kFI-x2jp.js → xychartDiagram-S5SC5T6Z-FkkMK-fP.js} +1 -1
  93. package/dist/index.html +3 -3
  94. package/package.json +1 -1
  95. package/src/core/islands/worker-factory.ts +2 -12
  96. package/src/core/wasm/__tests__/bootstrap.test.ts +63 -0
  97. package/src/core/wasm/__tests__/bridge.test.ts +8 -0
  98. package/src/core/wasm/__tests__/fs.test.ts +22 -0
  99. package/src/core/wasm/__tests__/runtime-config.test.ts +58 -0
  100. package/src/core/wasm/bridge.ts +18 -16
  101. package/src/core/wasm/rpc.ts +2 -0
  102. package/src/core/wasm/runtime-config.ts +27 -0
  103. package/src/core/wasm/worker/bootstrap.ts +21 -11
  104. package/src/core/wasm/worker/fs.ts +4 -0
  105. package/src/core/wasm/worker/save-worker.ts +7 -0
  106. package/src/core/wasm/worker/types.ts +7 -4
  107. package/src/core/wasm/worker/worker.ts +6 -0
  108. package/src/utils/__tests__/worker.test.ts +81 -0
  109. package/src/utils/worker.ts +22 -0
  110. package/dist/assets/__vite-browser-external-BzcgEWaD.js +0 -1
  111. package/dist/assets/__vite-browser-external-D87rEcrz.js +0 -1
  112. package/dist/assets/has-DZ-sm-ft.js +0 -1
  113. package/dist/assets/index-DKfkO1Lu.js +0 -62
  114. package/dist/assets/save-worker-BstmibH4.js +0 -113
@@ -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 = new Worker(
86
- // oxlint-disable-next-line unicorn/relative-url-style
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
- return getWorkerRPC<SaveWorkerSchema>(saveWorker).proxy.request;
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 Worker(
106
- // oxlint-disable-next-line unicorn/relative-url-style
107
- new URL("./worker/worker.ts", import.meta.url),
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", () => {
@@ -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(opts: {
45
- version: string;
46
- pyodideVersion: string;
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(opts: {
59
- version: string;
60
- pyodideVersion: string;
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 = `https://cdn.jsdelivr.net/pyodide/${opts.pyodideVersion}/full/`;
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: `https://wasm.marimo.app/pyodide-lock.json?v=${opts.version}&pyodide=${opts.pyodideVersion}`,
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 back to the jsDelivr CDN to restore
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() {
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import type { PyodideInterface } from "pyodide";
3
+ import type { WasmRuntimeConfig } from "../runtime-config";
3
4
  import type { UserConfig } from "@/core/config/config-schema";
4
5
  import type {
5
6
  CommandMessage,
@@ -43,10 +44,12 @@ export interface WasmController {
43
44
  * Prepare the wasm environment
44
45
  * @param opts.version - The marimo version
45
46
  */
46
- bootstrap(opts: {
47
- version: string;
48
- pyodideVersion: string;
49
- }): Promise<PyodideInterface>;
47
+ bootstrap(
48
+ opts: WasmRuntimeConfig & {
49
+ version: string;
50
+ pyodideVersion: string;
51
+ },
52
+ ): Promise<PyodideInterface>;
50
53
  /**
51
54
  * Mount the filesystem
52
55
  * @param opts.code - The code to mount
@@ -22,6 +22,7 @@ import { prettyError } from "../../../utils/errors";
22
22
  import { invariant } from "../../../utils/invariant";
23
23
  import { Logger } from "../../../utils/Logger";
24
24
  import type { ParentSchema } from "../rpc";
25
+ import type { WasmRuntimeConfig } from "../runtime-config";
25
26
  import { TRANSPORT_ID } from "./constants";
26
27
  import { WasmFileSystem } from "./fs";
27
28
  import { getController } from "./getController";
@@ -47,6 +48,8 @@ declare const self: Window & {
47
48
 
48
49
  const workerInitSpan = t.startSpan("worker:init");
49
50
 
51
+ const runtimeConfig = new Deferred<WasmRuntimeConfig>();
52
+
50
53
  // Initialize pyodide
51
54
  async function loadPyodideAndPackages() {
52
55
  try {
@@ -58,6 +61,7 @@ async function loadPyodideAndPackages() {
58
61
  message: "Loading marimo...",
59
62
  });
60
63
  self.pyodide = await t.wrapAsync(controller.bootstrap.bind(controller))({
64
+ ...(await runtimeConfig.promise),
61
65
  version: marimoVersion,
62
66
  pyodideVersion: pyodideVersion,
63
67
  });
@@ -354,6 +358,8 @@ const rpc = createRPC<WorkerSchema, ParentSchema>({
354
358
  requestHandler,
355
359
  });
356
360
 
361
+ rpc.addMessageListener("bootstrap", (config) => runtimeConfig.resolve(config));
362
+
357
363
  rpc.send("ready", {});
358
364
 
359
365
  /// Listeners
@@ -0,0 +1,81 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { createModuleWorker } from "../worker";
4
+
5
+ afterEach(() => {
6
+ vi.unstubAllGlobals();
7
+ vi.restoreAllMocks();
8
+ });
9
+
10
+ describe("createModuleWorker", () => {
11
+ it.each(["http", "blob"])(
12
+ "preserves caller-owned %s worker URLs",
13
+ (scheme) => {
14
+ const Worker = vi.fn(function (
15
+ _url: string | URL,
16
+ _options: WorkerOptions,
17
+ ) {
18
+ return {};
19
+ });
20
+ vi.stubGlobal("Worker", Worker);
21
+ const url = new URL(
22
+ scheme === "blob"
23
+ ? `blob:${location.origin}/worker`
24
+ : new URL("/worker.js", location.href),
25
+ );
26
+ const revoke = vi.spyOn(URL, "revokeObjectURL");
27
+ createModuleWorker(url, { name: "marimo" });
28
+ expect(Worker).toHaveBeenCalledWith(url.href, {
29
+ type: "module",
30
+ name: "marimo",
31
+ });
32
+ expect(revoke).not.toHaveBeenCalled();
33
+ },
34
+ );
35
+
36
+ it("preserves the page origin for hosted CDN workers", () => {
37
+ const Worker = vi.fn(function (
38
+ _url: string | URL,
39
+ _options: WorkerOptions,
40
+ ) {
41
+ return {};
42
+ });
43
+ vi.stubGlobal("Worker", Worker);
44
+ const create = vi
45
+ .spyOn(URL, "createObjectURL")
46
+ .mockReturnValue("blob:worker");
47
+ const revoke = vi
48
+ .spyOn(URL, "revokeObjectURL")
49
+ .mockImplementation(() => undefined);
50
+ createModuleWorker(new URL("https://cdn.example.com/worker.js"), {
51
+ name: "islands",
52
+ });
53
+ expect(create).toHaveBeenCalledWith(expect.any(Blob));
54
+ expect(Worker).toHaveBeenCalledWith("blob:worker", {
55
+ type: "module",
56
+ name: "islands",
57
+ });
58
+ expect(revoke).toHaveBeenCalledWith("blob:worker");
59
+ });
60
+
61
+ it("imports a CDN worker from an opaque origin", () => {
62
+ const Worker = vi.fn(function (
63
+ _url: string | URL,
64
+ _options: WorkerOptions,
65
+ ) {
66
+ return {};
67
+ });
68
+ vi.stubGlobal("Worker", Worker);
69
+ vi.stubGlobal("origin", "null");
70
+ const url = new URL("https://cdn.example.com/worker.js");
71
+ createModuleWorker(url, { name: "0.24.2::controller" });
72
+ const [source, options] = Worker.mock.calls[0];
73
+ expect(decodeURIComponent(String(source))).toBe(
74
+ 'data:application/javascript,import "https://cdn.example.com/worker.js"',
75
+ );
76
+ expect(options).toEqual({
77
+ type: "module",
78
+ name: "0.24.2::controller",
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,22 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /** Load a module worker across origins, including from file:// pages. */
4
+ export function createModuleWorker(url: URL, options: WorkerOptions): Worker {
5
+ if (url.origin === globalThis.origin) {
6
+ return new Worker(url.href, { ...options, type: "module" });
7
+ }
8
+ const js = `import ${JSON.stringify(url.href)}`;
9
+ // Blob workers preserve the page origin, but file:// needs a data URL.
10
+ const source =
11
+ globalThis.origin === "null"
12
+ ? `data:application/javascript,${encodeURIComponent(js)}`
13
+ : URL.createObjectURL(new Blob([js], { type: "application/javascript" }));
14
+ try {
15
+ return new Worker(source, { ...options, type: "module" });
16
+ } finally {
17
+ if (source.startsWith("blob:")) {
18
+ // Worker() captures the Blob before returning, even while imports load.
19
+ URL.revokeObjectURL(source);
20
+ }
21
+ }
22
+ }
@@ -1 +0,0 @@
1
- import{t as e}from"./worker-BXMsnprR.js";var t=e(((e,t)=>{t.exports={}}));export default t();
@@ -1 +0,0 @@
1
- import{t as e}from"./save-worker-BstmibH4.js";var t=e(((e,t)=>{t.exports={}}));export default t();
@@ -1 +0,0 @@
1
- import{Ka as e}from"./index-DKfkO1Lu.js";var t=Object.prototype.hasOwnProperty;function n(e,n){return e!=null&&t.call(e,n)}function r(t,r){return t!=null&&e(t,r,n)}export{r as t};