diff --git a/.changeset/clean-dogs-launch.md b/.changeset/clean-dogs-launch.md new file mode 100644 index 00000000000..0a3027e0dd0 --- /dev/null +++ b/.changeset/clean-dogs-launch.md @@ -0,0 +1,8 @@ +--- +"@gradio/core": patch +"@self/app": patch +"@self/spa": patch +"gradio": patch +--- + +fix: Execute function strings passed to `Blocks.launch(js=...)` diff --git a/js/app/src/routes/[...catchall]/+page.svelte b/js/app/src/routes/[...catchall]/+page.svelte index 45b3b977868..de0a74a8995 100644 --- a/js/app/src/routes/[...catchall]/+page.svelte +++ b/js/app/src/routes/[...catchall]/+page.svelte @@ -301,16 +301,6 @@ await add_custom_html_head(config.head); - if (config.js) { - try { - const script = document.createElement("script"); - script.textContent = config.js; - document.head.appendChild(script); - } catch (e) { - console.error("Error executing custom JS:", e); - } - } - dispatch("loaded"); if (config.dev_mode) { setTimeout(() => { diff --git a/js/core/src/Blocks.svelte b/js/core/src/Blocks.svelte index 4fc4faf66b0..e144dbe88a7 100644 --- a/js/core/src/Blocks.svelte +++ b/js/core/src/Blocks.svelte @@ -17,6 +17,7 @@ import MountComponents from "./MountComponents.svelte"; import { prefix_css } from "./css"; + import { execute_custom_js } from "./custom_js"; import { reactive_formatter } from "./gradio_helper"; import logo from "./images/logo.svg"; @@ -467,7 +468,15 @@ }); res.observe(root_container); - app_tree.ready.then(() => { + app_tree.ready.then(async () => { + if (js) { + try { + await execute_custom_js(js); + } catch (e) { + console.error("Error executing custom JS:", e); + } + } + ready = true; dep_manager.dispatch_load_events(); }); diff --git a/js/core/src/custom_js.test.ts b/js/core/src/custom_js.test.ts new file mode 100644 index 00000000000..c177375a1b8 --- /dev/null +++ b/js/core/src/custom_js.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, test } from "vitest"; +import { execute_custom_js } from "./custom_js"; + +declare global { + var custom_js_result: string | undefined; +} + +afterEach(() => { + delete globalThis.custom_js_result; +}); + +describe("execute_custom_js", () => { + test("invokes a function expression", async () => { + await execute_custom_js( + "() => { globalThis.custom_js_result = 'function'; }" + ); + + expect(globalThis.custom_js_result).toBe("function"); + }); + + test("awaits an async function expression", async () => { + await execute_custom_js( + "async () => { await Promise.resolve(); globalThis.custom_js_result = 'async'; }" + ); + + expect(globalThis.custom_js_result).toBe("async"); + }); + + test("executes raw JavaScript", async () => { + await execute_custom_js( + "const result = 'raw'; globalThis.custom_js_result = result;" + ); + + expect(globalThis.custom_js_result).toBe("raw"); + }); +}); diff --git a/js/core/src/custom_js.ts b/js/core/src/custom_js.ts new file mode 100644 index 00000000000..ee13723ce0b --- /dev/null +++ b/js/core/src/custom_js.ts @@ -0,0 +1,16 @@ +import { AsyncFunction } from "./init_utils"; + +export async function execute_custom_js(js: string): Promise { + let custom_js: () => Promise; + + try { + custom_js = new AsyncFunction(`return (${js});`); + } catch { + custom_js = new AsyncFunction(js); + } + + const result = await custom_js(); + if (typeof result === "function") { + await result(); + } +} diff --git a/js/spa/src/Index.svelte b/js/spa/src/Index.svelte index c1c31398ffe..da27ed69d99 100644 --- a/js/spa/src/Index.svelte +++ b/js/spa/src/Index.svelte @@ -387,15 +387,6 @@ if (config.deep_link_state === "invalid") { pending_deep_link_error = true; } - if (config.js) { - try { - const script = document.createElement("script"); - script.textContent = config.js; - document.head.appendChild(script); - } catch (e) { - console.error("Error executing custom JS:", e); - } - } if (config.dev_mode) { setTimeout(() => { const { host } = new URL(api_url);