From 8d0d4e0a8ebe2425aef24a6f21b88598684b0965 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Thu, 6 Jul 2023 19:38:15 +0900 Subject: [PATCH] Prebuild lite css (#4788) * Customize scripts/generate_theme.py to reuse for the CSS generation for @gradio/lite * Build theme.css at the build time of Gradio-lite and update the frontend app to load it at the initialization phase * Add changeset --------- Co-authored-by: pngwn --- .changeset/happy-plums-sing.md | 5 +++++ .github/workflows/ui.yml | 5 ++--- js/app/.gitignore | 1 + js/app/lite.html | 2 +- js/app/package.json | 5 +++-- js/app/src/lite/css.ts | 18 +++++++++++++++++- js/app/src/lite/index.ts | 4 +++- scripts/generate_theme.py | 14 ++++++++++---- 8 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 .changeset/happy-plums-sing.md create mode 100644 js/app/.gitignore diff --git a/.changeset/happy-plums-sing.md b/.changeset/happy-plums-sing.md new file mode 100644 index 0000000000..e535f089e4 --- /dev/null +++ b/.changeset/happy-plums-sing.md @@ -0,0 +1,5 @@ +--- +"@gradio/lite": patch +--- + +Generate a prebuilt themed CSS file at build time diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 10c7438e9e..9fcab12266 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -74,14 +74,14 @@ jobs: - name: install gradio run: python -m pip install gradio==$(cat gradio/version.txt) - name: generate theme.css - run: python scripts/generate_theme.py + run: python scripts/generate_theme.py --outfile js/storybook/theme.css - name: install dependencies uses: "./.github/actions/install-all-deps" with: always-install-pnpm: true skip_build: 'true' - name: build storybook - run: pnpm build-storybook --quiet + run: pnpm build-storybook --quiet - name: publish to chromatic id: publish-chromatic uses: chromaui/action@v1 @@ -99,4 +99,3 @@ jobs: GITHUB_TOKEN: ${{ secrets.COMMENT_TOKEN }} comment_tag: chromatic-build pr_number: ${{ env.pr_number }} - diff --git a/js/app/.gitignore b/js/app/.gitignore new file mode 100644 index 0000000000..6f8fe001ac --- /dev/null +++ b/js/app/.gitignore @@ -0,0 +1 @@ +/src/lite/theme.css diff --git a/js/app/lite.html b/js/app/lite.html index e9eea654dc..f0a74338f3 100644 --- a/js/app/lite.html +++ b/js/app/lite.html @@ -31,7 +31,7 @@ def upload_file(files): file_paths = [file.name for file in files] return file_paths -with gr.Blocks() as demo: +with gr.Blocks(theme=gr.themes.Soft()) as demo: name = gr.Textbox(label="Name") output = gr.Textbox(label="Output Box") greet_btn = gr.Button("Greet") diff --git a/js/app/package.json b/js/app/package.json index 47464415cb..f55bcaed37 100644 --- a/js/app/package.json +++ b/js/app/package.json @@ -11,10 +11,11 @@ "build:cdn": "vite build --mode production:cdn --emptyOutDir", "build:website": "vite build --mode production:website --emptyOutDir", "build:local": "vite build --mode production:local --emptyOutDir", + "cssbuild": "python ../../scripts/generate_theme.py --outfile ./src/lite/theme.css", "pybuild:gradio": "cd ../../ && rm -rf gradio/templates/frontend && python -m build", "pybuild:gradio-client": "cd ../../client/python && python -m build", "pybuild": "run-p pybuild:*", - "build:lite": "pnpm pybuild && pnpm --filter @gradio/client build && pnpm --filter @gradio/wasm build && vite build --mode production:lite --emptyOutDir", + "build:lite": "pnpm pybuild && pnpm cssbuild && pnpm --filter @gradio/client build && pnpm --filter @gradio/wasm build && 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", @@ -59,4 +60,4 @@ "msw": { "workerDirectory": "public" } -} \ No newline at end of file +} diff --git a/js/app/src/lite/css.ts b/js/app/src/lite/css.ts index 38b43aaa13..f5949df53a 100644 --- a/js/app/src/lite/css.ts +++ b/js/app/src/lite/css.ts @@ -2,11 +2,21 @@ import type { WorkerProxy } from "@gradio/wasm"; import { is_self_origin } from "./url"; import { mount_css as default_mount_css } from "../css"; +// In the Wasm mode, we use a prebuilt CSS file `/static/css/theme.css` to apply the styles in the initialization phase, +// because it will take a long time for `theme.css` to be available after opening the app and loading the Pyodide and the server code. +// The prebuild CSS will be replaced with the dynamic `theme.css` when it is available and `wasm_proxied_mount_css()` is called with its URL. +const PREBUILT_CSS_URL = new URL("./theme.css", import.meta.url).href; +const DYNAMIC_THEME_CSS_URL_PATH = "/theme.css"; + +export async function mount_prebuilt_css(target: HTMLElement): Promise { + return default_mount_css(PREBUILT_CSS_URL, target); +} + export async function wasm_proxied_mount_css( worker_proxy: WorkerProxy, url_string: string, target: HTMLElement -) { +): Promise { const request = new Request(url_string); // Resolve a relative URL. const url = new URL(request.url); @@ -28,6 +38,12 @@ export async function wasm_proxied_mount_css( ); if (existing_link) return; + if (url.pathname === DYNAMIC_THEME_CSS_URL_PATH) { + console.debug("Unmount the prebuilt theme.css before mounting the dynamic theme.css"); + const existing_prebuilt_css = document.querySelector(`link[href='${PREBUILT_CSS_URL}']`); + existing_prebuilt_css?.remove(); + } + const style = document.createElement("style"); style.setAttribute("data-wasm-path", url_string); style.textContent = css; diff --git a/js/app/src/lite/index.ts b/js/app/src/lite/index.ts index e3aba23292..3ba872ed98 100644 --- a/js/app/src/lite/index.ts +++ b/js/app/src/lite/index.ts @@ -2,7 +2,7 @@ import "@gradio/theme"; import { WorkerProxy, type WorkerProxyOptions } from "@gradio/wasm"; import { api_factory } from "@gradio/client"; import { wasm_proxied_fetch } from "./fetch"; -import { wasm_proxied_mount_css } from "./css"; +import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css"; import type { mount_css } from "../css"; import Index from "../Index.svelte"; import type { ThemeMode } from "../components/types"; @@ -73,6 +73,8 @@ export function create(options: Options): GradioAppController { // 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); + mount_prebuilt_css(document.head); + const overridden_fetch: typeof fetch = (input, init?) => { return wasm_proxied_fetch(worker_proxy, input, init); }; diff --git a/scripts/generate_theme.py b/scripts/generate_theme.py index 8fd905e926..6481714eaa 100644 --- a/scripts/generate_theme.py +++ b/scripts/generate_theme.py @@ -1,6 +1,12 @@ -from gradio.themes.default import Default +import argparse +from gradio import themes -css = Default()._get_theme_css() +parser = argparse.ArgumentParser(description='Generate themed CSS which is normally served from the /theme.css endpoint of a Gradio server.') +parser.add_argument('--outfile', type=argparse.FileType('w', encoding='latin-1'), default="-") +parser.add_argument('--theme', choices=["default", "glass", "monochrome", "soft"], default="default") +args = parser.parse_args() -with open("js/storybook/theme.css", "w") as file1: - file1.write(css) +ThemeClass = getattr(themes, args.theme.capitalize()) +css = ThemeClass()._get_theme_css() + +args.outfile.write(css)