Gradio-lite (Gradio Wasm) (#4402)

This commit is contained in:
Yuichiro Tachibana (Tsuchiya) 2023-06-27 16:09:50 +09:00 committed by GitHub
parent 3abad71ce4
commit 4fbdefe94e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1939 additions and 687 deletions

View File

@ -2,6 +2,7 @@
**/pnpm-workspace.yaml
**/js/app/dist/**
**/client/js/dist/**
**/js/lite/dist/**
**/pnpm-lock.yaml
**/js/plot/src/Plot.svelte
**/.svelte-kit/**

View File

@ -27,6 +27,8 @@ jobs:
always-install-pnpm: true
- name: build client
run: pnpm --filter @gradio/client build
- name: build the wasm module
run: pnpm --filter @gradio/wasm build
- name: lint
run: pnpm lint
continue-on-error: true

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,8 @@
export { client, post_data, upload_files, duplicate } from "./client.js";
export {
client,
post_data,
upload_files,
duplicate,
api_factory
} from "./client.js";
export type { SpaceStatus } from "./types.js";

View File

@ -32,6 +32,7 @@ from gradio import (
strings,
themes,
utils,
wasm_utils,
)
from gradio.context import Context
from gradio.deprecation import check_deprecated_parameters
@ -739,7 +740,7 @@ class Blocks(BlockContext):
self.root_path = ""
self.root_urls = set()
if self.analytics_enabled:
if not wasm_utils.IS_WASM and self.analytics_enabled:
is_custom_theme = not any(
self.theme.to_dict() == built_in_theme.to_dict()
for built_in_theme in BUILT_IN_THEMES.values()
@ -1772,15 +1773,37 @@ Received outputs:
"Rerunning server... use `close()` to stop if you need to change `launch()` parameters.\n----"
)
else:
server_name, server_port, local_url, app, server = networking.start_server(
self,
server_name,
server_port,
ssl_keyfile,
ssl_certfile,
ssl_keyfile_password,
app_kwargs=app_kwargs,
)
if wasm_utils.IS_WASM:
server_name = "xxx"
server_port = 99999
local_url = ""
server = None
# In the Wasm environment, we only need the app object
# which the frontend app will directly communicate with through the Worker API,
# and we don't need to start a server.
# So we just create the app object and register it here,
# and avoid using `networking.start_server` that would start a server that don't work in the Wasm env.
from gradio.routes import App
app = App.create_app(self, app_kwargs=app_kwargs)
wasm_utils.register_app(app)
else:
(
server_name,
server_port,
local_url,
app,
server,
) = networking.start_server(
self,
server_name,
server_port,
ssl_keyfile,
ssl_certfile,
ssl_keyfile_password,
app_kwargs=app_kwargs,
)
self.server_name = server_name
self.local_url = local_url
self.server_port = server_port
@ -1802,7 +1825,11 @@ Received outputs:
# Cannot run async functions in background other than app's scope.
# Workaround by triggering the app endpoint
requests.get(f"{self.local_url}startup-events", verify=ssl_verify)
if not wasm_utils.IS_WASM:
requests.get(f"{self.local_url}startup-events", verify=ssl_verify)
if wasm_utils.IS_WASM:
return TupleNoPrint((self.server_app, self.local_url, self.share_url))
utils.launch_counter()
@ -2037,7 +2064,8 @@ Received outputs:
try:
if self.enable_queue:
self._queue.close()
self.server.close()
if self.server:
self.server.close()
self.is_running = False
# So that the startup events (starting the queue)
# happen the next time the app is launched
@ -2056,7 +2084,8 @@ Received outputs:
time.sleep(0.1)
except (KeyboardInterrupt, OSError):
print("Keyboard interruption in main thread... closing server.")
self.server.close()
if self.server:
self.server.close()
for tunnel in CURRENT_TUNNELS:
tunnel.kill()

View File

@ -7,16 +7,19 @@ import warnings
from pathlib import Path
from typing import Callable, Literal
from ffmpy import FFmpeg
from gradio_client import utils as client_utils
from gradio_client.data_classes import FileData
from gradio_client.documentation import document, set_documentation_group
from gradio_client.serializing import VideoSerializable
from gradio import processing_utils, utils
from gradio import processing_utils, utils, wasm_utils
from gradio.components.base import IOComponent, _Keywords
from gradio.events import Changeable, Clearable, Playable, Recordable, Uploadable
if not wasm_utils.IS_WASM:
# TODO: Support ffmpeg on Wasm
from ffmpy import FFmpeg
set_documentation_group("component")
@ -204,6 +207,10 @@ class Video(
)
if Path(output_file_name).exists():
return output_file_name
if wasm_utils.IS_WASM:
raise wasm_utils.WasmUnsupportedError(
"Video formatting is not supported in the Wasm mode."
)
ff = FFmpeg(
inputs={str(file_name): None},
outputs={output_file_name: output_options},
@ -212,6 +219,10 @@ class Video(
return output_file_name
elif not self.include_audio:
output_file_name = str(file_name.with_name(f"muted_{file_name.name}"))
if wasm_utils.IS_WASM:
raise wasm_utils.WasmUnsupportedError(
"include_audio=False is not supported in the Wasm mode."
)
ff = FFmpeg(
inputs={str(file_name): None},
outputs={output_file_name: ["-an"]},
@ -301,6 +312,10 @@ class Video(
# selected format
returned_format = video.split(".")[-1].lower()
if self.format is not None and returned_format != self.format:
if wasm_utils.IS_WASM:
raise wasm_utils.WasmUnsupportedError(
"Returning a video in a different format is not supported in the Wasm mode."
)
output_file_name = video[0 : video.rindex(".") + 1] + self.format
ff = FFmpeg(
inputs={video: None},

View File

@ -11,10 +11,15 @@ from io import BytesIO
from pathlib import Path
import numpy as np
from ffmpy import FFmpeg, FFprobe, FFRuntimeError
from gradio_client import utils as client_utils
from PIL import Image, ImageOps, PngImagePlugin
from gradio import wasm_utils
if not wasm_utils.IS_WASM:
# TODO: Support ffmpeg on Wasm
from ffmpy import FFmpeg, FFprobe, FFRuntimeError
with warnings.catch_warnings():
warnings.simplefilter("ignore") # Ignore pydub warning if ffmpeg is not installed
from pydub import AudioSegment
@ -478,6 +483,10 @@ def _convert(image, dtype, force_copy=False, uniform=False):
def ffmpeg_installed() -> bool:
if wasm_utils.IS_WASM:
# TODO: Support ffmpeg in WASM
return False
return shutil.which("ffmpeg") is not None

View File

@ -42,7 +42,7 @@ from starlette.websockets import WebSocketState
import gradio
import gradio.ranged_response as ranged_response
from gradio import utils
from gradio import utils, wasm_utils
from gradio.context import Context
from gradio.data_classes import PredictBody, ResetBody
from gradio.exceptions import Error
@ -167,16 +167,18 @@ class App(FastAPI):
blocks: gradio.Blocks, app_kwargs: Dict[str, Any] | None = None
) -> App:
app_kwargs = app_kwargs or {}
app_kwargs.setdefault("default_response_class", ORJSONResponse)
if not wasm_utils.IS_WASM:
app_kwargs.setdefault("default_response_class", ORJSONResponse)
app = App(**app_kwargs)
app.configure_app(blocks)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
if not wasm_utils.IS_WASM:
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/user")
@app.get("/user/")

View File

@ -4,6 +4,8 @@ from typing import Dict
import requests
from gradio import wasm_utils
MESSAGING_API_ENDPOINT = "https://api.gradio.app/gradio-messaging/en"
en = {
@ -41,5 +43,5 @@ def get_updated_messaging(en: Dict):
pass
if os.getenv("GRADIO_ANALYTICS_ENABLED", "True") == "True":
if os.getenv("GRADIO_ANALYTICS_ENABLED", "True") == "True" and not wasm_utils.IS_WASM:
threading.Thread(target=get_updated_messaging, args=(en,)).start()

24
gradio/wasm_utils.py Normal file
View File

@ -0,0 +1,24 @@
import sys
# See https://pyodide.org/en/stable/usage/faq.html#how-to-detect-that-code-is-run-with-pyodide
IS_WASM = sys.platform == "emscripten"
class WasmUnsupportedError(Exception):
pass
app = None
# `register_app` and `get_registered_app` are used
# for the Wasm worker to get a reference to
# the Gradio's FastAPI app instance (`app`).
def register_app(_app):
global app
app = _app
def get_registered_app():
global app
return app

43
js/app/lite.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<!-- An entrypoint for the Wasm version development -->
<html
lang="en"
style="
margin: 0;
padding: 0;
min-height: 100%;
display: flex;
flex-direction: column;
"
>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1"
/>
<script type="module" src="./src/lite/index.ts"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin="anonymous"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
</head>
<body
style="
width: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
flex-grow: 1;
"
>
<div id="gradio-app"></div>
</body>
</html>

View File

@ -5,9 +5,14 @@
"type": "module",
"scripts": {
"dev": "vite --port 9876",
"dev:lite": "vite --port 9876 --mode development:lite",
"build:cdn": "vite build --mode production:cdn --emptyOutDir",
"build:website": "vite build --mode production:website --emptyOutDir",
"build:local": "vite build --mode production:local --emptyOutDir",
"pybuild:gradio": "cd ../../ && rm -rf gradio/templates/frontend && python3 -m build",
"pybuild:gradio-client": "cd ../../client/python && python3 -m build",
"pybuild": "run-p pybuild:*",
"build:lite": "pnpm pybuild && vite build --mode production:lite --emptyOutDir",
"preview": "vite preview",
"test:snapshot": "pnpm exec playwright test snapshots/ --config=../../.config/playwright.config.js",
"test:browser": "pnpm exec playwright test test/ --config=../../.config/playwright.config.js",
@ -42,6 +47,7 @@
"@gradio/upload-button": "workspace:^0.0.1",
"@gradio/utils": "workspace:^0.0.1",
"@gradio/video": "workspace:^0.0.1",
"@gradio/wasm": "workspace:^0.0.1",
"@playwright/test": "^1.35.1",
"d3-dsv": "^3.0.1",
"mime-types": "^2.1.34",

View File

@ -1,6 +1,6 @@
<script context="module" lang="ts">
import { writable } from "svelte/store";
import { mount_css } from "./css";
import { mount_css as default_mount_css } from "./css";
import type {
ComponentMeta,
@ -58,8 +58,8 @@
</script>
<script lang="ts">
import { onMount } from "svelte";
import { client, SpaceStatus } from "@gradio/client";
import { onMount, setContext } from "svelte";
import type { api_factory, SpaceStatus } from "@gradio/client";
import Embed from "./Embed.svelte";
import type { ThemeMode } from "./components/types";
@ -76,6 +76,11 @@
export let info: boolean;
export let eager: boolean;
// These utilities are exported to be injectable for the Wasm version.
export let mount_css: typeof default_mount_css = default_mount_css;
export let client: ReturnType<typeof api_factory>["client"];
export let upload_files: ReturnType<typeof api_factory>["upload_files"];
export let space: string | null;
export let host: string | null;
export let src: string | null;
@ -216,6 +221,8 @@
}
});
setContext("upload_files", upload_files);
$: loader_status =
!ready && status.load_status !== "error"
? "pending"

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { createEventDispatcher, getContext } from "svelte";
import { File as FileComponent, FileUpload } from "@gradio/file";
import { blobToBase64, FileData } from "@gradio/upload";
import { normalise_file } from "@gradio/upload";
import { Block } from "@gradio/atoms";
import UploadText from "../UploadText.svelte";
import { upload_files } from "@gradio/client";
import { upload_files as default_upload_files } from "@gradio/client";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
@ -31,6 +31,10 @@
export let scale: number | null = null;
export let min_width: number | undefined = undefined;
const upload_files =
getContext<typeof default_upload_files>("upload_files") ??
default_upload_files;
$: _value = normalise_file(value, root, root_url);
let dragging = false;

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { createEventDispatcher, tick } from "svelte";
import { createEventDispatcher, tick, getContext } from "svelte";
import type { FileData } from "@gradio/upload";
import { UploadButton } from "@gradio/upload-button";
import { upload_files } from "@gradio/client";
import { upload_files as default_upload_files } from "@gradio/client";
import { blobToBase64 } from "@gradio/upload";
import { _ } from "svelte-i18n";
@ -20,6 +20,10 @@
export let mode: "static" | "dynamic" = "dynamic";
export let variant: "primary" | "secondary" | "stop" = "secondary";
const upload_files =
getContext<typeof default_upload_files>("upload_files") ??
default_upload_files;
async function handle_upload({ detail }: CustomEvent<FileData>) {
value = detail;
await tick();

36
js/app/src/lite/css.ts Normal file
View File

@ -0,0 +1,36 @@
import type { WorkerProxy } from "@gradio/wasm";
import { is_self_origin } from "./url";
import { mount_css as default_mount_css } from "../css";
export async function wasm_proxied_mount_css(
worker_proxy: WorkerProxy,
url_string: string,
target: HTMLElement
) {
const request = new Request(url_string); // Resolve a relative URL.
const url = new URL(request.url);
if (!is_self_origin(url)) {
// Fallback to the default implementation for external resources.
return default_mount_css(url_string, target);
}
const response = await worker_proxy.httpRequest({
method: "GET",
path: url.pathname,
query_string: "",
headers: {}
});
const css = new TextDecoder().decode(response.body);
const existing_link = document.querySelector(
`style[data-wasm-path='${url_string}']`
);
if (existing_link) return;
const style = document.createElement("style");
style.setAttribute("data-wasm-path", url_string);
style.textContent = css;
// @ts-ignore
target.appendChild(style);
}

54
js/app/src/lite/fetch.ts Normal file
View File

@ -0,0 +1,54 @@
import type { WorkerProxy } from "@gradio/wasm";
import { is_self_origin } from "./url";
/**
* A fetch() function that proxies HTTP requests to the worker,
* which also falls back to the original fetch() for external resource requests.
*/
export async function wasm_proxied_fetch(
workerProxy: WorkerProxy,
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> {
console.debug("wasm_proxied_fetch", input, init);
const request = new Request(input, init);
const url = new URL(request.url);
if (!is_self_origin(url)) {
console.debug("Fallback to original fetch");
return fetch(input, init);
}
const method = request.method;
if (
method !== "GET" &&
method !== "POST" &&
method !== "PUT" &&
method !== "DELETE"
) {
throw new Error(`Unsupported method: ${method}`);
}
const headers: Parameters<WorkerProxy["httpRequest"]>[0]["headers"] = {};
request.headers.forEach((value, key) => {
headers[key] = value;
});
const bodyArrayBuffer = await new Response(request.body).arrayBuffer();
const body: Parameters<WorkerProxy["httpRequest"]>[0]["body"] =
new Uint8Array(bodyArrayBuffer);
const response = await workerProxy.httpRequest({
path: url.pathname,
query_string: url.search,
method,
headers,
body
});
return new Response(response.body, {
status: response.status,
headers: new Headers(response.headers)
});
}

162
js/app/src/lite/index.ts Normal file
View File

@ -0,0 +1,162 @@
import "@gradio/theme";
import { WorkerProxy } from "@gradio/wasm";
import { api_factory } from "@gradio/client";
import { wasm_proxied_fetch } from "./fetch";
import { wasm_proxied_mount_css } from "./css";
import type { mount_css } from "../css";
import Index from "../Index.svelte";
import type { ThemeMode } from "../components/types";
// These imports are aliased at built time with Vite. See the `resolve.alias` config in `vite.config.ts`.
import gradioWheel from "gradio.whl";
import gradioClientWheel from "gradio_client.whl";
declare let GRADIO_VERSION: string;
// NOTE: The following line has been copied from `main.ts`.
// In `main.ts`, which is the normal Gradio app entry point,
// the string literal "__ENTRY_CSS__" will be replaced with the actual CSS file path
// by the Vite plugin `handle_ce_css` in `build_plugins.ts`,
// and the CSS file will be dynamically loaded at runtime
// as the file path (the `ENTRY_CSS` variable) will be passed to `mount_css()`.
// This mechanism has been introduced in https://github.com/gradio-app/gradio/pull/1444
// to make Gradio work as a Web Component library
// with which users can use Gradio by loading only one JS file,
// without a link tag referring to the CSS file.
// However, we don't rely on this mechanism here to make things simpler by leaving the Vite plugins as is,
// because it will be refactored in the near future.
// As a result, the users of the Wasm app will have to load the CSS file manually.
// const ENTRY_CSS = "__ENTRY_CSS__";
interface Options {
target: HTMLElement;
pyCode: string;
info: boolean;
container: boolean;
isEmbed: boolean;
initialHeight?: string;
eager: boolean;
themeMode: ThemeMode | null;
autoScroll: boolean;
controlPageTitle: boolean;
appMode: boolean;
}
export async function create(options: Options) {
// TODO: Runtime type validation for options.
const observer = new MutationObserver(() => {
document.body.style.padding = "0";
});
observer.observe(options.target, { childList: true });
const worker_proxy = new WorkerProxy({
gradioWheelUrl: gradioWheel,
gradioClientWheelUrl: gradioClientWheel,
requirements: []
});
// Internally, the execution of `runPythonAsync()` is queued
// and its promise will be resolved after the Pyodide is loaded and the worker initialization is done
// (see the await in the `onmessage` callback in the webworker code)
// So we don't await this promise because we want to mount the `Index` immediately and start the app initialization asynchronously.
worker_proxy.runPythonAsync(options.pyCode);
const overridden_fetch: typeof fetch = (input, init?) => {
return wasm_proxied_fetch(worker_proxy, input, init);
};
const { client, upload_files } = api_factory(overridden_fetch);
const overridden_mount_css: typeof mount_css = async (url, target) => {
return wasm_proxied_mount_css(worker_proxy, url, target);
};
const app = new Index({
target: options.target,
props: {
// embed source
space: null,
src: null,
host: null,
// embed info
info: options.info,
container: options.container,
is_embed: options.isEmbed,
initial_height: options.initialHeight ?? "300px", // default: 300px
eager: options.eager,
// gradio meta info
version: GRADIO_VERSION,
theme_mode: options.themeMode,
// misc global behaviour
autoscroll: options.autoScroll,
control_page_title: options.controlPageTitle,
// for gradio docs
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
app_mode: options.appMode,
// For Wasm mode
client,
upload_files,
mount_css: overridden_mount_css
}
});
}
/**
* I'm not sure if this is a correct way to export functions from a bundle created with Vite.
* However, at least, the library mode (https://vitejs.dev/guide/build.html#library-mode)
* with an exported function (`export async function create()`) didn't work for our case.
* In library mode with the `build.lib.entry = (this file)` config,
* Vite creates a bundle exporting the functions from this file, which looks nice,
* however, it inevitably enables inlining of all the static file assets,
* while we need to disable inlining for the wheel files to pass their URLs to `micropip.install()`.
*
* > If you specify build.lib, build.assetsInlineLimit will be ignored and assets will always be inlined, regardless of file size or being a Git LFS placeholder.
* > https://vitejs.dev/config/build-options.html#build-assetsinlinelimit
*
* There is an open issue about this: https://github.com/vitejs/vite/issues/4454
*
* FYI, stlite (https://github.com/whitphx/stlite) uses Webpack,
* which supports bundling libraries that export entities to the global scope and disabling assets inlining
* (https://webpack.js.org/guides/author-libraries/).
*/
// @ts-ignore
globalThis.createGradioApp = create;
declare let BUILD_MODE: string;
if (BUILD_MODE === "dev") {
create({
target: document.getElementById("gradio-app")!,
pyCode: `
import gradio as gr
def greet(name):
return "Hello " + name + "!"
def upload_file(files):
file_paths = [file.name for file in files]
return file_paths
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
greet_btn.click(fn=greet, inputs=name, outputs=output, api_name="greet")
gr.File()
file_output = gr.File()
upload_button = gr.UploadButton("Click to Upload a File", file_types=["image", "video"], file_count="multiple")
upload_button.upload(upload_file, upload_button, file_output)
demo.launch()
`,
info: true,
container: true,
isEmbed: false,
initialHeight: "300px",
eager: false,
themeMode: null,
autoScroll: false,
controlPageTitle: false,
appMode: true
});
}

6
js/app/src/lite/url.ts Normal file
View File

@ -0,0 +1,6 @@
export function is_self_origin(url: URL): boolean {
return (
url.origin === window.location.origin ||
url.origin === "http://localhost:7860" // Ref: https://github.com/gradio-app/gradio/blob/v3.32.0/js/app/src/Index.svelte#L194
);
}

View File

@ -1,4 +1,5 @@
import "@gradio/theme";
import { client, upload_files } from "@gradio/client";
import { mount_css } from "./css";
import Index from "./Index.svelte";
import type { ThemeMode } from "./components/types";
@ -93,6 +94,9 @@ function create_custom_element() {
// misc global behaviour
autoscroll: this.autoscroll === "true" ? true : false,
control_page_title: this.control_page_title === "true" ? true : false,
// injectables
client,
upload_files,
// for gradio docs
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
app_mode: window.__gradio_mode__ === "app"
@ -154,6 +158,9 @@ function create_custom_element() {
autoscroll: this.autoscroll === "true" ? true : false,
control_page_title:
this.control_page_title === "true" ? true : false,
// injectables
client,
upload_files,
// for gradio docs
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
app_mode: window.__gradio_mode__ === "app"

6
js/app/src/vite-env-override.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
// See https://vitejs.dev/guide/features.html#client-types
declare module "*.whl" {
const content: string;
export default content;
}

View File

@ -7,14 +7,21 @@ import global_data from "@csstools/postcss-global-data";
// @ts-ignore
import prefixer from "postcss-prefix-selector";
import { readFileSync } from "fs";
import { join } from "path";
import { resolve } from "path";
const version_path = join(__dirname, "..", "..", "gradio", "version.txt");
const theme_token_path = join(__dirname, "..", "theme", "src", "tokens.css");
const version_path = resolve(__dirname, "../../gradio/version.txt");
const theme_token_path = resolve(__dirname, "../theme/src/tokens.css");
const version_raw = readFileSync(version_path, { encoding: "utf-8" }).trim();
const version = version_raw.replace(/\./g, "-");
const version = readFileSync(version_path, { encoding: "utf-8" })
.trim()
.replace(/\./g, "-");
const client_version_path = resolve(
__dirname,
"../../client/python/gradio_client/version.txt"
);
const client_version_raw = readFileSync(client_version_path, {
encoding: "utf-8"
}).trim();
const client_version = client_version_raw.replace(/\./g, "-");
import {
inject_ejs,
@ -36,21 +43,51 @@ export default defineConfig(({ mode }) => {
const production =
mode === "production:cdn" ||
mode === "production:local" ||
mode === "production:website";
mode === "production:website" ||
mode === "production:lite";
const is_cdn = mode === "production:cdn" || mode === "production:website";
const is_lite = mode.endsWith(":lite");
return {
base: is_cdn ? CDN_URL : "./",
server: {
port: 9876
port: 9876,
open: is_lite ? "/lite.html" : "/"
},
build: {
sourcemap: true,
target: "esnext",
minify: production,
outDir: `../../gradio/templates/${is_cdn ? "cdn" : "frontend"}`
outDir: is_lite
? resolve(__dirname, "../lite/dist")
: `../../gradio/templates/${is_cdn ? "cdn" : "frontend"}`,
// To build Gradio-lite as a library, we can't use the library mode
// like `lib: is_lite && {}`
// because it inevitably enables inlining of all the static file assets,
// while we need to disable inlining for the wheel files to pass their URLs to `micropip.install()`.
// So we build it as an app and only use the bundled JS and CSS files as library assets, ignoring the HTML file.
// See also `lite.ts` about it.
rollupOptions: is_lite && {
input: "./lite.html",
output: {
// To use it as a library, we don't add the hash to the file name.
entryFileNames: "lite.js",
assetFileNames: (file) => {
if (file.name?.endsWith(".whl")) {
// Python wheel files must follow the naming rules to be installed, so adding a hash to the name is not allowed.
return `assets/[name].[ext]`;
}
if (file.name === "lite.css") {
// To use it as a library, we don't add the hash to the file name.
return `[name].[ext]`;
} else {
return `assets/[name]-[hash].[ext]`;
}
}
}
}
},
define: {
BUILD_MODE: production ? JSON.stringify("prod") : JSON.stringify("dev"),
@ -114,6 +151,20 @@ export default defineConfig(({ mode }) => {
? ["**/*.node-test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"]
: ["**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
globals: true
}
},
resolve: {
alias: {
// For the Wasm app to import the wheel file URLs.
"gradio.whl": resolve(
__dirname,
`../../dist/gradio-${version_raw}-py3-none-any.whl`
),
"gradio_client.whl": resolve(
__dirname,
`../../client/python/dist/gradio_client-${client_version_raw}-py3-none-any.whl`
)
}
},
assetsInclude: ["**/*.whl"] // To pass URLs of built wheel files to the Wasm worker.
};
});

72
js/lite/index.html Normal file
View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<!-- A demo HTML file to test the bundled JS and CSS files -->
<html
lang="en"
style="
margin: 0;
padding: 0;
min-height: 100%;
display: flex;
flex-direction: column;
"
>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1"
/>
<script type="module" crossorigin src="./dist/lite.js"></script>
<link rel="stylesheet" href="./dist/lite.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin="anonymous"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
</head>
<body
style="
width: 100%;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
flex-grow: 1;
"
>
<div id="gradio-app"></div>
</body>
<script>
window.addEventListener("DOMContentLoaded", function () {
// <script type="module" /> is loaded asynchronously, so we need to wait for it to load before we can use it.
createGradioApp({
target: document.getElementById("gradio-app"),
pyCode: `
import gradio as gr
def greet(name):
return "Hello " + name + "!"
demo = gr.Interface(fn=greet, inputs="text", outputs="text")
demo.launch()
`,
info: true,
container: true,
isEmbed: false,
initialHeight: "300px",
eager: false,
themeMode: null,
autoScroll: false,
controlPageTitle: false,
appMode: true
});
});
</script>
</html>

22
js/wasm/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "@gradio/wasm",
"version": "0.0.1",
"description": "Gradio Wasm package",
"type": "module",
"main": "dist/index.js",
"private": true,
"keywords": [],
"author": "",
"license": "ISC",
"scripts": {
"start:client": "tsc -w --incremental",
"start:worker": "vite build --config vite.worker.config.js --watch --emptyOutDir=false",
"start": "run-p start:*",
"build:client": "tsc",
"build:worker": "vite build --config vite.worker.config.js",
"build": "run-s build:worker build:client"
},
"devDependencies": {
"pyodide": "^0.23.2"
}
}

1
js/wasm/src/index.ts Normal file
View File

@ -0,0 +1 @@
export { WorkerProxy } from "./worker-proxy";

View File

@ -0,0 +1,62 @@
export interface HttpRequest {
method: "GET" | "POST" | "PUT" | "DELETE";
path: string;
query_string: string;
headers: Record<string, string>;
body?: Uint8Array;
}
export interface HttpResponse {
status: number;
headers: Record<string, string>;
body: Uint8Array;
}
export interface InMessageBase {
type: string;
data: unknown;
}
export interface InMessageInit extends InMessageBase {
type: "init";
data: {
gradioWheelUrl: string;
gradioClientWheelUrl: string;
requirements: string[];
};
}
export interface InMessageRunPython extends InMessageBase {
type: "run-python";
data: {
code: string;
};
}
export interface InMessageHttpRequest extends InMessageBase {
type: "http-request";
data: {
request: HttpRequest;
};
}
export interface InMessageEcho extends InMessageBase {
// For debug
type: "echo";
data: unknown;
}
export type InMessage =
| InMessageInit
| InMessageRunPython
| InMessageHttpRequest
| InMessageEcho;
export interface ReplyMessageSuccess<T = unknown> {
type: "reply:success";
data: T;
}
export interface ReplyMessageError {
type: "reply:error";
error: Error;
}
export type ReplyMessage = ReplyMessageSuccess | ReplyMessageError;

View File

@ -0,0 +1,2 @@
// Declarations for the WebWorker files where some variables are dynamically loaded through importScript.
declare let loadPyodide: any;

View File

@ -0,0 +1,135 @@
import type { PyProxy } from "pyodide/ffi";
import type { HttpRequest, HttpResponse } from "../message-types";
// Inspired by https://github.com/rstudio/shinylive/blob/v0.1.2/src/messageporthttp.ts
// A reference to an ASGI application instance in Python
// Ref: https://asgi.readthedocs.io/en/latest/specs/main.html#applications
type ASGIApplication = (
scope: Record<string, unknown>,
receive: () => Promise<ReceiveEvent>,
send: (event: PyProxy) => Promise<void>
) => Promise<void>;
type ReceiveEvent = RequestReceiveEvent | DisconnectReceiveEvent;
// https://asgi.readthedocs.io/en/latest/specs/www.html#request-receive-event
interface RequestReceiveEvent {
type: "http.request";
body?: Uint8Array; // `bytes` in Python
more_body: boolean;
}
// https://asgi.readthedocs.io/en/latest/specs/www.html#disconnect-receive-event
interface DisconnectReceiveEvent {
type: "http.disconnect";
}
type SendEvent = ResponseStartSendEvent | ResponseBodySendEvent;
// https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event
interface ResponseStartSendEvent {
type: "http.response.start";
status: number;
headers: Iterable<[Uint8Array, Uint8Array]>;
trailers: boolean;
}
// https://asgi.readthedocs.io/en/latest/specs/www.html#response-body-send-event
interface ResponseBodySendEvent {
type: "http.response.body";
body: Uint8Array; // `bytes` in Python
more_body: boolean;
}
function headersToASGI(
headers: HttpRequest["headers"]
): Array<[string, string]> {
const result: Array<[string, string]> = [];
for (const [key, value] of Object.entries(headers)) {
result.push([key, value]);
}
return result;
}
export function uint8ArrayToString(buf: Uint8Array): string {
let result = "";
for (let i = 0; i < buf.length; i++) {
result += String.fromCharCode(buf[i]);
}
return result;
}
function asgiHeadersToRecord(headers: any): Record<string, string> {
headers = headers.map(([key, val]: [Uint8Array, Uint8Array]) => {
return [uint8ArrayToString(key), uint8ArrayToString(val)];
});
return Object.fromEntries(headers);
}
export const makeHttpRequest = (
asgiApp: ASGIApplication,
request: HttpRequest
): Promise<HttpResponse> =>
new Promise((resolve, reject) => {
let sent = false;
async function receiveFromJs(): Promise<ReceiveEvent> {
if (sent) {
// NOTE: I implemented this block just referring to the spec. However, it is not reached in practice so it's not combat-proven.
return {
type: "http.disconnect"
};
}
const event: RequestReceiveEvent = {
type: "http.request",
more_body: false
};
if (request.body) {
event.body = request.body;
}
console.debug("receive", event);
sent = true;
return event;
}
let status: number;
let headers: { [key: string]: string };
let body: Uint8Array = new Uint8Array();
async function sendToJs(proxiedEvent: PyProxy): Promise<void> {
const event = Object.fromEntries(proxiedEvent.toJs()) as SendEvent;
console.debug("send", event);
if (event.type === "http.response.start") {
status = event.status;
headers = asgiHeadersToRecord(event.headers);
} else if (event.type === "http.response.body") {
body = new Uint8Array([...body, ...event.body]);
if (!event.more_body) {
const response: HttpResponse = {
status,
headers,
body
};
console.debug("HTTP response", response);
resolve(response);
}
} else {
throw new Error(`Unhandled ASGI event: ${JSON.stringify(event)}`);
}
}
// https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
const scope = {
type: "http",
asgi: {
version: "3.0",
spec_version: "2.1"
},
http_version: "1.1",
scheme: "http",
method: request.method,
path: request.path,
query_string: request.query_string,
root_path: "",
headers: headersToASGI(request.headers)
};
asgiApp(scope, receiveFromJs, sendToJs);
});

View File

@ -0,0 +1,205 @@
/// <reference lib="webworker" />
import type { PyodideInterface } from "pyodide";
import type {
InMessage,
ReplyMessageError,
ReplyMessageSuccess
} from "../message-types";
import { makeHttpRequest } from "./http";
importScripts("https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js");
let pyodide: PyodideInterface;
let pyodideReadyPromise: undefined | Promise<void> = undefined;
let call_asgi_app_from_js: (
scope: unknown,
receive: Function,
send: Function
) => Promise<void>;
interface InitOptions {
gradioWheelUrl: string;
gradioClientWheelUrl: string;
requirements: string[];
}
async function loadPyodideAndPackages(options: InitOptions) {
console.debug("Loading Pyodide.");
pyodide = await loadPyodide({
stdout: console.log,
stderr: console.error
});
console.debug("Pyodide is loaded.");
console.debug("Loading micropip");
await pyodide.loadPackage("micropip");
const micropip = pyodide.pyimport("micropip");
console.debug("micropip is loaded.");
const gradioWheelUrls = [
options.gradioWheelUrl,
options.gradioClientWheelUrl
];
console.debug("Loading Gradio wheels.", gradioWheelUrls);
await micropip.add_mock_package("ffmpy", "0.3.0");
await micropip.add_mock_package("orjson", "3.8.12");
await micropip.add_mock_package("aiohttp", "3.8.4");
await micropip.add_mock_package("multidict", "4.7.6");
await pyodide.loadPackage(["ssl", "distutils", "setuptools"]);
await micropip.install(["markdown-it-py~=2.2.0"]); // On 3rd June 2023, markdown-it-py 3.0.0 has been released. The `gradio` package depends on its `>=2.0.0` version so its 3.x will be resolved. However, it conflicts with `mdit-py-plugins`'s dependency `markdown-it-py >=1.0.0,<3.0.0` and micropip currently can't resolve it. So we explicitly install the compatible version of the library here.
await micropip.install.callKwargs(gradioWheelUrls, {
keep_going: true
});
console.debug("Gradio wheels are loaded.");
console.debug("Install packages.", options.requirements);
await micropip.install.callKwargs(options.requirements, { keep_going: true });
console.debug("Packages are installed.");
console.debug("Mock os module methods.");
// `os.link` is used in `aiofiles` (https://github.com/Tinche/aiofiles/blob/v23.1.0/src/aiofiles/os.py#L31),
// which is imported from `gradio.ranged_response` (https://github.com/gradio-app/gradio/blob/v3.32.0/gradio/ranged_response.py#L12).
// However, it's not available on Wasm.
await pyodide.runPythonAsync(`
import os
os.link = lambda src, dst: None
`);
console.debug("os module methods are mocked.");
console.debug("Import gradio package.");
// Importing the gradio package takes a long time, so we do it separately.
// This is necessary for accurate performance profiling.
await pyodide.runPythonAsync(`import gradio`);
console.debug("gradio package is imported.");
console.debug("Define a ASGI wrapper function.");
// TODO: Unlike Streamlit, user's code is executed in the global scope,
// so we should not define this function in the global scope.
await pyodide.runPythonAsync(`
# Based on Shiny's App.call_pyodide().
# https://github.com/rstudio/py-shiny/blob/v0.3.3/shiny/_app.py#L224-L258
async def _call_asgi_app_from_js(scope, receive, send):
# TODO: Pretty sure there are objects that need to be destroy()'d here?
scope = scope.to_py()
# ASGI requires some values to be byte strings, not character strings. Those are
# not that easy to create in JavaScript, so we let the JS side pass us strings
# and we convert them to bytes here.
if "headers" in scope:
# JS doesn't have \`bytes\` so we pass as strings and convert here
scope["headers"] = [
[value.encode("latin-1") for value in header]
for header in scope["headers"]
]
if "query_string" in scope and scope["query_string"]:
scope["query_string"] = scope["query_string"].encode("latin-1")
if "raw_path" in scope and scope["raw_path"]:
scope["raw_path"] = scope["raw_path"].encode("latin-1")
async def rcv():
event = await receive()
return event.to_py()
async def snd(event):
await send(event)
app = gradio.wasm_utils.get_registered_app()
if app is None:
raise RuntimeError("Gradio app has not been launched.")
await app(scope, rcv, snd)
`);
call_asgi_app_from_js = pyodide.globals.get("_call_asgi_app_from_js");
console.debug("The ASGI wrapper function is defined.");
console.debug("Mock async libraries.");
// FastAPI uses `anyio.to_thread.run_sync` internally which, however, doesn't work in Wasm environments where the `threading` module is not supported.
// So we mock `anyio.to_thread.run_sync` here not to use threads.
await pyodide.runPythonAsync(`
async def mocked_anyio_to_thread_run_sync(func, *args, cancellable=False, limiter=None):
return func(*args)
import anyio.to_thread
anyio.to_thread.run_sync = mocked_anyio_to_thread_run_sync
`);
console.debug("Async libraries are mocked.");
console.debug("Set matplotlib backend.");
// Ref: https://github.com/streamlit/streamlit/blob/1.22.0/lib/streamlit/web/bootstrap.py#L111
// This backend setting is required to use matplotlib in Wasm environment.
await pyodide.runPythonAsync(`
import matplotlib
matplotlib.use("agg")
`);
console.debug("matplotlib backend is set.");
}
self.onmessage = async (event: MessageEvent<InMessage>) => {
const msg = event.data;
console.debug("worker.onmessage", msg);
const messagePort = event.ports[0];
try {
if (msg.type === "init") {
pyodideReadyPromise = loadPyodideAndPackages({
gradioWheelUrl: msg.data.gradioWheelUrl,
gradioClientWheelUrl: msg.data.gradioClientWheelUrl,
requirements: msg.data.requirements
});
const replyMessage: ReplyMessageSuccess = {
type: "reply:success",
data: null
};
messagePort.postMessage(replyMessage);
}
if (pyodideReadyPromise == null) {
throw new Error("Pyodide Initialization is not started.");
}
await pyodideReadyPromise;
switch (msg.type) {
case "echo": {
const replyMessage: ReplyMessageSuccess = {
type: "reply:success",
data: msg.data
};
messagePort.postMessage(replyMessage);
break;
}
case "run-python": {
await pyodide.runPythonAsync(msg.data.code);
const replyMessage: ReplyMessageSuccess = {
type: "reply:success",
data: null // We don't send back the execution result because it's not needed for our purpose, and sometimes the result is of type `pyodide.ffi.PyProxy` which cannot be cloned across threads and causes an error.
};
messagePort.postMessage(replyMessage);
break;
}
case "http-request": {
const request = msg.data.request;
const response = await makeHttpRequest(call_asgi_app_from_js, request);
const replyMessage: ReplyMessageSuccess = {
type: "reply:success",
data: {
response
}
};
messagePort.postMessage(replyMessage);
break;
}
}
} catch (error) {
const replyMessage: ReplyMessageError = {
type: "reply:error",
error: error as Error
};
messagePort.postMessage(replyMessage);
}
};

104
js/wasm/src/worker-proxy.ts Normal file
View File

@ -0,0 +1,104 @@
import type {
HttpRequest,
HttpResponse,
InMessage,
ReplyMessage
} from "./message-types";
export interface WorkerProxyOptions {
gradioWheelUrl: string;
gradioClientWheelUrl: string;
requirements: string[];
}
export class WorkerProxy {
private worker: Worker;
constructor(options: WorkerProxyOptions) {
console.debug("WorkerProxy.constructor(): Create a new worker.");
// Loading a worker here relies on Vite's support for WebWorkers (https://vitejs.dev/guide/features.html#web-workers),
// assuming that this module is imported from the Gradio frontend (`@gradio/app`), which is bundled with Vite.
this.worker = new Worker(new URL("./webworker.js", import.meta.url));
this.postMessageAsync({
type: "init",
data: {
gradioWheelUrl: options.gradioWheelUrl,
gradioClientWheelUrl: options.gradioClientWheelUrl,
requirements: options.requirements
}
}).then(() => {
console.debug("WorkerProxy.constructor(): Initialization is done.");
});
}
public async runPythonAsync(code: string): Promise<void> {
await this.postMessageAsync({
type: "run-python",
data: {
code
}
});
}
// A wrapper for this.worker.postMessage(). Unlike that function, which
// returns void immediately, this function returns a promise, which resolves
// when a ReplyMessage is received from the worker.
// The original implementation is in https://github.com/rstudio/shinylive/blob/v0.1.2/src/pyodide-proxy.ts#L404-L418
private postMessageAsync(msg: InMessage): Promise<unknown> {
return new Promise((resolve, reject) => {
const channel = new MessageChannel();
channel.port1.onmessage = (e) => {
channel.port1.close();
const msg = e.data as ReplyMessage;
if (msg.type === "reply:error") {
reject(msg.error);
return;
}
resolve(msg.data);
};
this.worker.postMessage(msg, [channel.port2]);
});
}
public async httpRequest(request: HttpRequest): Promise<HttpResponse> {
console.debug("WorkerProxy.httpRequest()", request);
const result = await this.postMessageAsync({
type: "http-request",
data: {
request
}
});
const response = (result as { response: HttpResponse }).response;
if (Math.floor(response.status / 100) !== 2) {
let bodyText: string;
let bodyJson: unknown;
try {
bodyText = new TextDecoder().decode(response.body);
} catch (e) {
bodyText = "(failed to decode body)";
}
try {
bodyJson = JSON.parse(bodyText);
} catch (e) {
bodyJson = "(failed to parse body as JSON)";
}
console.error("Wasm HTTP error", {
request,
response,
bodyText,
bodyJson
});
}
return response;
}
public terminate(): void {
this.worker.terminate();
}
}

105
js/wasm/tsconfig.json Normal file
View File

@ -0,0 +1,105 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ESNext" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
"declarationMap": true /* Create sourcemaps for d.ts files. */,
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*"],
"exclude": ["src/webworker/**/*"] // The worker code is bundled by Vite separately. See its config file.
}

View File

@ -0,0 +1,34 @@
import path from "path";
import { defineConfig } from "vite";
/**
* We bundle the worker file before packaging, while other files are only TS-transpiled.
* The consumer of this package, `@gradio/app`, will be bundled with Vite,
* and Vite only supports module-type WebWorkers (`new Worker("...", { type: "module" })`) to handle `import` in the worker file,
* because in the dev mode it doesn't bundle the worker file and just relies on the browser's native support for module-type workers to resolve the imports.
* However, we need to use `importScripts()` in the worker to load Pyodide from the CDN, which is only supported by classic WebWorkers (`new Worker("...")`),
* while we still want to use `import` in the worker to modularize the code.
* So, we bundle the worker file to resolve `import`s here before exporting, preserving `importScripts()` in the bundled file,
* and load the bundled worker file on `@gradio/app` as a classic WebWorker.
*
* Note: We tried the following approaches, but they failed:
* 1. Just TS-transpile the worker file like other files into `worker.js`, and use it like `new Worker("worker.js")`.
* It failed because `tsc` reserves `importScripts()` and also appends `export {};` to the end of the file to specify it as a module (`https://github.com/microsoft/TypeScript/issues/41513`),
* however, `importScripts()` is only supported by classic WebWorkers, and `export {};` is not supported by classic WebWorkers.
* 2. Use ESM import instead of `importScripts()`, which is (experimentally?) supported by Pyodide since v0.20.0 (https://pyodide.org/en/stable/project/changelog.html#javascript-package),
* using `import { loadPyodide } from "https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js";` in the worker file, instead of `importScripts(...)`.
* It was successful in the dev mode, but failed in the prod mode, which has this problem: https://github.com/pyodide/pyodide/issues/2217#issuecomment-1328344562.
*/
export default defineConfig({
build: {
outDir: "dist",
rollupOptions: {
input: path.join(__dirname, "src/webworker/index.ts"),
// Ref: https://github.com/rollup/rollup/issues/2616#issuecomment-1431551704
output: {
entryFileNames: "webworker.js"
}
}
}
});

View File

@ -46,10 +46,10 @@ importers:
version: 20.3.1
'@typescript-eslint/eslint-plugin':
specifier: ^5.60.0
version: 5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3)
version: 5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.0.4)
'@typescript-eslint/parser':
specifier: ^5.60.0
version: 5.60.0(eslint@8.43.0)(typescript@5.1.3)
version: 5.60.0(eslint@8.43.0)(typescript@5.0.4)
autoprefixer:
specifier: ^10.4.4
version: 10.4.4(postcss@8.4.6)
@ -76,7 +76,7 @@ importers:
version: 4.1.5
msw:
specifier: ^1.0.0
version: 1.0.0(typescript@5.1.3)
version: 1.0.0(typescript@5.0.4)
node-html-parser:
specifier: ^6.0.0
version: 6.0.0
@ -133,16 +133,16 @@ importers:
version: 3.6.0(svelte@3.59.1)
svelte-preprocess:
specifier: ^5.0.3
version: 5.0.3(postcss@8.4.6)(svelte@3.59.1)(typescript@5.1.3)
version: 5.0.3(postcss@8.4.6)(svelte@3.59.1)(typescript@5.0.4)
tailwindcss:
specifier: ^3.1.6
version: 3.1.6(postcss@8.4.6)
tinyspy:
specifier: ^2.0.0
version: 2.1.1
version: 2.0.0
typescript:
specifier: ^5.0.0
version: 5.1.3
version: 5.0.4
vite:
specifier: ^4.3.9
version: 4.3.9(@types/node@20.3.1)
@ -301,6 +301,9 @@ importers:
'@gradio/video':
specifier: workspace:^0.0.1
version: link:../video
'@gradio/wasm':
specifier: workspace:^0.0.1
version: link:../wasm
'@playwright/test':
specifier: ^1.35.1
version: 1.35.1
@ -723,6 +726,12 @@ importers:
specifier: workspace:^0.0.1
version: link:../upload
js/wasm:
devDependencies:
pyodide:
specifier: ^0.23.2
version: 0.23.2
packages:
/@adobe/css-tools@4.2.0:
@ -1548,13 +1557,13 @@ packages:
resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==}
dependencies:
'@formatjs/intl-localematcher': 0.2.25
tslib: 2.5.3
tslib: 2.4.0
dev: false
/@formatjs/fast-memoize@1.2.1:
resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==}
dependencies:
tslib: 2.5.3
tslib: 2.4.0
dev: false
/@formatjs/icu-messageformat-parser@2.1.0:
@ -1562,20 +1571,20 @@ packages:
dependencies:
'@formatjs/ecma402-abstract': 1.11.4
'@formatjs/icu-skeleton-parser': 1.3.6
tslib: 2.5.3
tslib: 2.4.0
dev: false
/@formatjs/icu-skeleton-parser@1.3.6:
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
dependencies:
'@formatjs/ecma402-abstract': 1.11.4
tslib: 2.5.3
tslib: 2.4.0
dev: false
/@formatjs/intl-localematcher@0.2.25:
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
dependencies:
tslib: 2.5.3
tslib: 2.4.0
dev: false
/@humanwhocodes/config-array@0.11.10:
@ -2184,7 +2193,7 @@ packages:
'@types/yargs-parser': 21.0.0
dev: false
/@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3):
/@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.0.4):
resolution: {integrity: sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -2196,23 +2205,23 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.5.1
'@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.1.3)
'@typescript-eslint/parser': 5.60.0(eslint@8.43.0)(typescript@5.0.4)
'@typescript-eslint/scope-manager': 5.60.0
'@typescript-eslint/type-utils': 5.60.0(eslint@8.43.0)(typescript@5.1.3)
'@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.1.3)
'@typescript-eslint/type-utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4)
debug: 4.3.4
eslint: 8.43.0
grapheme-splitter: 1.0.4
ignore: 5.2.4
natural-compare-lite: 1.4.0
semver: 7.3.8
tsutils: 3.21.0(typescript@5.1.3)
typescript: 5.1.3
tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4
transitivePeerDependencies:
- supports-color
dev: false
/@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.1.3):
/@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.0.4):
resolution: {integrity: sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -2224,10 +2233,10 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 5.60.0
'@typescript-eslint/types': 5.60.0
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.1.3)
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4)
debug: 4.3.4
eslint: 8.43.0
typescript: 5.1.3
typescript: 5.0.4
transitivePeerDependencies:
- supports-color
dev: false
@ -2240,7 +2249,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.60.0
dev: false
/@typescript-eslint/type-utils@5.60.0(eslint@8.43.0)(typescript@5.1.3):
/@typescript-eslint/type-utils@5.60.0(eslint@8.43.0)(typescript@5.0.4):
resolution: {integrity: sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -2250,12 +2259,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.1.3)
'@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.1.3)
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4)
'@typescript-eslint/utils': 5.60.0(eslint@8.43.0)(typescript@5.0.4)
debug: 4.3.4
eslint: 8.43.0
tsutils: 3.21.0(typescript@5.1.3)
typescript: 5.1.3
tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4
transitivePeerDependencies:
- supports-color
dev: false
@ -2265,7 +2274,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: false
/@typescript-eslint/typescript-estree@5.60.0(typescript@5.1.3):
/@typescript-eslint/typescript-estree@5.60.0(typescript@5.0.4):
resolution: {integrity: sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -2280,13 +2289,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.3.8
tsutils: 3.21.0(typescript@5.1.3)
typescript: 5.1.3
tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4
transitivePeerDependencies:
- supports-color
dev: false
/@typescript-eslint/utils@5.60.0(eslint@8.43.0)(typescript@5.1.3):
/@typescript-eslint/utils@5.60.0(eslint@8.43.0)(typescript@5.0.4):
resolution: {integrity: sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -2297,7 +2306,7 @@ packages:
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 5.60.0
'@typescript-eslint/types': 5.60.0
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.1.3)
'@typescript-eslint/typescript-estree': 5.60.0(typescript@5.0.4)
eslint: 8.43.0
eslint-scope: 5.1.1
semver: 7.3.8
@ -2534,7 +2543,7 @@ packages:
engines: {node: '>=12.20.1'}
dependencies:
'@babel/runtime': 7.21.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/autoprefixer@10.4.4(postcss@8.4.6):
@ -2592,6 +2601,10 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
/base-64@1.0.0:
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
dev: true
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
@ -2646,7 +2659,7 @@ packages:
dependencies:
'@babel/runtime': 7.21.0
fast-unique-numbers: 6.0.21
tslib: 2.5.3
tslib: 2.4.0
worker-factory: 6.0.69
dev: false
@ -2678,7 +2691,6 @@ packages:
requiresBuild: true
dependencies:
node-gyp-build: 4.6.0
dev: false
/busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
@ -2955,7 +2967,7 @@ packages:
'@babel/runtime': 7.21.0
dashify: 2.0.0
indefinite-article: 0.0.2
tslib: 2.5.3
tslib: 2.4.0
dev: false
/concat-map@0.0.1:
@ -2971,7 +2983,7 @@ packages:
js-string-escape: 1.0.1
lodash: 4.17.21
md5-hex: 3.0.1
semver: 7.4.0
semver: 7.3.8
well-known-symbols: 2.0.0
dev: false
@ -3938,7 +3950,7 @@ packages:
engines: {node: '>=12.20.1'}
dependencies:
'@babel/runtime': 7.21.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/fastq@1.13.0:
@ -4131,7 +4143,7 @@ packages:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
@ -4394,7 +4406,7 @@ packages:
'@formatjs/ecma402-abstract': 1.11.4
'@formatjs/fast-memoize': 1.2.1
'@formatjs/icu-messageformat-parser': 2.1.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/is-arguments@1.1.1:
@ -4717,7 +4729,7 @@ packages:
whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0
whatwg-url: 12.0.1
ws: 8.13.0
ws: 8.13.0(bufferutil@4.0.7)
xml-name-validator: 4.0.0
transitivePeerDependencies:
- bufferutil
@ -4950,7 +4962,7 @@ packages:
broker-factory: 3.0.68
fast-unique-numbers: 6.0.21
media-encoder-host-worker: 9.0.70
tslib: 2.5.3
tslib: 2.4.0
dev: false
/media-encoder-host-worker@9.0.70:
@ -4958,7 +4970,7 @@ packages:
dependencies:
'@babel/runtime': 7.21.0
extendable-media-recorder-wav-encoder-broker: 7.0.70
tslib: 2.5.3
tslib: 2.4.0
worker-factory: 6.0.69
dev: false
@ -4968,7 +4980,7 @@ packages:
'@babel/runtime': 7.21.0
media-encoder-host-broker: 7.0.70
media-encoder-host-worker: 9.0.70
tslib: 2.5.3
tslib: 2.4.0
dev: false
/memoizee@0.4.15:
@ -5053,12 +5065,12 @@ packages:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
dev: false
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: false
/minimist-options@4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
@ -5103,7 +5115,7 @@ packages:
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
/msw@1.0.0(typescript@5.1.3):
/msw@1.0.0(typescript@5.0.4):
resolution: {integrity: sha512-8QVa1RAN/Nzbn/tKmtimJ+b2M1QZOMdETQW7/1TmBOZ4w+wJojfxuh1Hj5J4FYdBgZWW/TK4CABUOlOM4OjTOA==}
engines: {node: '>=14'}
hasBin: true
@ -5132,7 +5144,7 @@ packages:
path-to-regexp: 6.2.1
strict-event-emitter: 0.4.6
type-fest: 2.19.0
typescript: 5.1.3
typescript: 5.0.4
yargs: 17.6.2
transitivePeerDependencies:
- encoding
@ -5144,7 +5156,7 @@ packages:
engines: {node: '>=12.20.1'}
dependencies:
'@babel/runtime': 7.21.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/mute-stream@0.0.8:
@ -5193,12 +5205,10 @@ packages:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-gyp-build@4.6.0:
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
hasBin: true
dev: false
/node-html-parser@6.0.0:
resolution: {integrity: sha512-o4vS5Jm7ZdV5WN4/jHmCEVJOpm4exLCeXOcZnNzXi0BGv0AS8FsGwyQ4k0Ujmui1NMQs6qsTy+amjjpr9rmz4Q==}
@ -5848,6 +5858,18 @@ packages:
engines: {node: '>=6'}
dev: false
/pyodide@0.23.2:
resolution: {integrity: sha512-GK4YDZVgzfAXK/7X0IiCI+142k0Ah/HwYTzDHtG8zC47dflWYuPozeFbOngShuL1M9Un5sCmHFqiH3boxJv0pQ==}
dependencies:
base-64: 1.0.0
node-fetch: 2.6.7
ws: 8.13.0(bufferutil@4.0.7)
transitivePeerDependencies:
- bufferutil
- encoding
- utf-8-validate
dev: true
/querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
dev: false
@ -5936,7 +5958,7 @@ packages:
resolution: {integrity: sha512-5QTJKukH8JcQR1f2FqZsQ1QD2aoc6/+tM0WPv8sqEI4THzbiMfH4VuWF3BfdL2F9mRjLo81nFC9OShCj87wMhg==}
dependencies:
'@babel/runtime': 7.21.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/recorder-audio-worklet@5.1.29:
@ -5948,7 +5970,7 @@ packages:
recorder-audio-worklet-processor: 4.2.15
standardized-audio-context: 25.3.32
subscribable-things: 2.1.7
tslib: 2.5.3
tslib: 2.4.0
worker-factory: 6.0.69
dev: false
@ -6074,7 +6096,7 @@ packages:
/rxjs@7.8.0:
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
dependencies:
tslib: 2.5.3
tslib: 2.4.0
dev: false
/sade@1.8.1:
@ -6299,7 +6321,7 @@ packages:
dependencies:
'@babel/runtime': 7.21.0
automation-events: 4.0.21
tslib: 2.5.3
tslib: 2.4.0
dev: false
/std-env@3.3.2:
@ -6428,7 +6450,7 @@ packages:
dependencies:
'@babel/runtime': 7.21.0
rxjs-interop: 2.0.0
tslib: 2.5.3
tslib: 2.4.0
dev: false
/supports-color@5.5.0:
@ -6651,7 +6673,7 @@ packages:
typescript: 4.9.5
dev: false
/svelte-preprocess@5.0.3(postcss@8.4.6)(svelte@3.59.1)(typescript@5.1.3):
/svelte-preprocess@5.0.3(postcss@8.4.6)(svelte@3.59.1)(typescript@5.0.4):
resolution: {integrity: sha512-GrHF1rusdJVbOZOwgPWtpqmaexkydznKzy5qIC2FabgpFyKN57bjMUUUqPRfbBXK5igiEWn1uO/DXsa2vJ5VHA==}
engines: {node: '>= 14.10.0'}
requiresBuild: true
@ -6696,7 +6718,7 @@ packages:
sorcery: 0.11.0
strip-indent: 3.0.0
svelte: 3.59.1
typescript: 5.1.3
typescript: 5.0.4
dev: false
/svelte-range-slider-pips@2.0.2:
@ -6813,6 +6835,11 @@ packages:
engines: {node: '>=14.0.0'}
dev: false
/tinyspy@2.0.0:
resolution: {integrity: sha512-B9wP6IgqmgNTDffygA716cr+PrW51LZc22qFs7+Aur0t73gqf3vNwwlwdcnE1AcqusK6V4R4+5jQ69nIQDiJiw==}
engines: {node: '>=14.0.0'}
dev: false
/tinyspy@2.1.1:
resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
engines: {node: '>=14.0.0'}
@ -6854,7 +6881,6 @@ packages:
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/tr46@4.1.1:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
@ -6887,14 +6913,14 @@ packages:
resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
dev: false
/tsutils@3.21.0(typescript@5.1.3):
/tsutils@3.21.0(typescript@5.0.4):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 5.1.3
typescript: 5.0.4
dev: false
/tty-table@4.2.1:
@ -6978,13 +7004,6 @@ packages:
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
engines: {node: '>=12.20'}
hasBin: true
dev: true
/typescript@5.1.3:
resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==}
engines: {node: '>=14.17'}
hasBin: true
dev: false
/ufo@1.1.1:
resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==}
@ -7068,7 +7087,7 @@ packages:
dependencies:
d3-array: 3.1.1
vega-dataflow: 5.7.4
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7078,7 +7097,7 @@ packages:
dependencies:
vega-format: 1.1.0
vega-loader: 4.5.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7110,7 +7129,7 @@ packages:
d3-interpolate: 3.0.1
vega-dataflow: 5.7.4
vega-scale: 7.2.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7123,7 +7142,7 @@ packages:
resolution: {integrity: sha512-y5+c2frq0tGwJ7vYXzZcfVcIRF/QGfhf2e+bV1Z0iQs+M2lI1II1GPDdmOcMKimpoCVp/D61KUJDIGE1DSmk2w==}
dependencies:
'@types/estree': 0.0.50
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-force@4.1.0:
@ -7131,7 +7150,7 @@ packages:
dependencies:
d3-force: 3.0.0
vega-dataflow: 5.7.4
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7143,7 +7162,7 @@ packages:
d3-format: 3.1.0
d3-time-format: 4.1.0
vega-time: 2.1.0
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-functions@5.13.0:
@ -7159,7 +7178,7 @@ packages:
vega-selections: 5.4.0
vega-statistics: 1.8.0
vega-time: 2.1.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7174,7 +7193,7 @@ packages:
vega-dataflow: 5.7.4
vega-projection: 1.5.0
vega-statistics: 1.8.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7184,7 +7203,7 @@ packages:
dependencies:
d3-hierarchy: 3.1.2
vega-dataflow: 5.7.4
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7199,7 +7218,7 @@ packages:
vega-canvas: 1.2.6
vega-dataflow: 5.7.4
vega-scenegraph: 4.10.1
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7231,7 +7250,7 @@ packages:
node-fetch: 2.6.7
topojson-client: 3.1.0
vega-format: 1.1.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7243,7 +7262,7 @@ packages:
vega-event-selector: 3.0.0
vega-functions: 5.13.0
vega-scale: 7.2.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7261,7 +7280,7 @@ packages:
d3-array: 3.1.1
vega-dataflow: 5.7.4
vega-statistics: 1.8.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7270,7 +7289,7 @@ packages:
resolution: {integrity: sha512-gE+sO2IfxMUpV0RkFeQVnHdmPy3K7LjHakISZgUGsDI/ZFs9y+HhBf8KTGSL5pcZPtQsZh3GBQ0UonqL1mp9PA==}
dependencies:
vega-dataflow: 5.7.4
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7282,7 +7301,7 @@ packages:
d3-interpolate: 3.0.1
d3-scale: 4.0.2
vega-time: 2.1.0
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-scenegraph@4.10.1:
@ -7293,7 +7312,7 @@ packages:
vega-canvas: 1.2.6
vega-loader: 4.5.0
vega-scale: 7.2.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7307,7 +7326,7 @@ packages:
dependencies:
d3-array: 3.1.1
vega-expression: 5.0.0
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-statistics@1.8.0:
@ -7331,7 +7350,7 @@ packages:
dependencies:
d3-array: 3.1.1
d3-time: 3.0.0
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-tooltip@0.32.0:
@ -7347,7 +7366,7 @@ packages:
vega-dataflow: 5.7.4
vega-statistics: 1.8.0
vega-time: 2.1.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7357,7 +7376,7 @@ packages:
dependencies:
vega-event-selector: 3.0.0
vega-expression: 5.0.0
vega-util: 1.17.2
vega-util: 1.17.0
dev: false
/vega-util@1.17.0:
@ -7373,7 +7392,7 @@ packages:
dependencies:
vega-dataflow: 5.7.4
vega-scenegraph: 4.10.1
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7388,7 +7407,7 @@ packages:
vega-functions: 5.13.0
vega-runtime: 6.1.3
vega-scenegraph: 4.10.1
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7398,7 +7417,7 @@ packages:
dependencies:
d3-delaunay: 6.0.2
vega-dataflow: 5.7.4
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7410,7 +7429,7 @@ packages:
vega-dataflow: 5.7.4
vega-scale: 7.2.0
vega-statistics: 1.8.0
vega-util: 1.17.2
vega-util: 1.17.0
transitivePeerDependencies:
- encoding
dev: false
@ -7651,7 +7670,6 @@ packages:
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
@ -7688,7 +7706,6 @@ packages:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
@ -7759,7 +7776,7 @@ packages:
'@babel/runtime': 7.21.0
compilerr: 9.0.21
fast-unique-numbers: 6.0.21
tslib: 2.5.3
tslib: 2.4.0
dev: false
/wrap-ansi@6.2.0:
@ -7783,19 +7800,6 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/ws@8.13.0:
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/ws@8.13.0(bufferutil@4.0.7):
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
engines: {node: '>=10.0.0'}
@ -7809,7 +7813,6 @@ packages:
optional: true
dependencies:
bufferutil: 4.0.7
dev: false
/xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}

View File

@ -1,3 +1,3 @@
packages:
- 'js/*'
- 'client/js'
- 'client/js'