Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Embed and CDN handling and fix a couple of related bugs #6261

Merged
merged 7 commits into from Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/purple-areas-see.md
@@ -0,0 +1,7 @@
---
"@gradio/app": minor
"@gradio/preview": minor
"gradio": minor
---

feat:Improve Embed and CDN handling and fix a couple of related bugs
5 changes: 1 addition & 4 deletions .config/basevite.config.ts
Expand Up @@ -27,10 +27,7 @@ const version = JSON.parse(readFileSync(version_path, { encoding: "utf-8" }))

//@ts-ignore
export default defineConfig(({ mode }) => {
const production =
mode === "production:cdn" ||
mode === "production:local" ||
mode === "production:website";
const production = mode === "production";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont need all of these modes anymore.


return {
server: {
Expand Down
5 changes: 1 addition & 4 deletions build_pypi.sh
Expand Up @@ -9,12 +9,9 @@ new_version=$(python -c "import json; f = open('$FILE', 'r'); data = json.load(f
GRADIO_VERSION=$new_version

rm -rf gradio/templates/frontend
rm -rf gradio/templates/cdn
pnpm i --frozen-lockfile --ignore-scripts
GRADIO_VERSION=$new_version pnpm build
GRADIO_VERSION=$new_version pnpm build:cdn
aws s3 cp gradio/templates/cdn "s3://gradio/${new_version}/" --recursive --region us-west-2
cp gradio/templates/cdn/index.html gradio/templates/frontend/share.html
aws s3 cp gradio/templates/frontend "s3://gradio/${new_version}/" --recursive --region us-west-2
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we use the same build for everything now, we only need to build once and we can upload the files to the server from the templates/frontend dir. It contains everything we need.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice


rm -rf dist/*
rm -rf build/*
Expand Down
107 changes: 39 additions & 68 deletions js/app/build_plugins.ts
Expand Up @@ -17,70 +17,47 @@ export function inject_ejs(): Plugin {
};
}

interface PatchDynamicImportOptionms {
mode: "cdn" | "local";
gradio_version: string;
cdn_url: string;
}

export function patch_dynamic_import({
mode,
gradio_version,
cdn_url
}: PatchDynamicImportOptionms): Plugin {
return {
name: "patch-dynamic-import",
enforce: "post",
writeBundle(config, bundle) {
// const import_re = /import\(((?:'|")[\.\/a-zA-Z0-9]*(?:'|"))\)/g;
// const import_meta = `${"import"}.${"meta"}.${"url"}`;
// for (const file in bundle) {
// const chunk = bundle[file];
// if (chunk.type === "chunk") {
// if (chunk.code.indexOf("import(") > -1) {
// const fix_fn = `const VERSION_RE = new RegExp("${gradio_version}\/", "g");function import_fix(mod, base) {const url = new URL(mod, base); return import(\`${cdn_url}\${url.pathname?.startsWith('/') ? url.pathname.substring(1).replace(VERSION_RE, "") : url.pathname.replace(VERSION_RE, "")}\`);}`;
// chunk.code =
// fix_fn +
// chunk.code.replace(import_re, `import_fix($1, ${import_meta})`);
// if (!config.dir) break;
// const output_location = join(config.dir, chunk.fileName);
// writeFileSync(output_location, chunk.code);
// }
// }
// }
}
};
}

export function generate_cdn_entry({
enable,
cdn_url
version,
cdn_base
}: {
enable: boolean;
cdn_url: string;
version: string;
cdn_base: string;
}): Plugin {
return {
name: "generate-cdn-entry",
enforce: "post",
writeBundle(config, bundle) {
if (!enable) return;

if (
!config.dir ||
!bundle["index.html"] ||
bundle["index.html"].type !== "asset"
)
return;

const tree = parse(bundle["index.html"].source as string);
const script =
Array.from(tree.querySelectorAll("script[type=module]")).find(
(node) => node.attributes.src?.startsWith(cdn_url)
)?.attributes.src || "";
const source = bundle["index.html"].source as string;
const tree = parse(source);

const script = Array.from(
tree.querySelectorAll("script[type=module]")
).find((node) => node.attributes.src?.includes("assets"));

const output_location = join(config.dir, "gradio.js");

writeFileSync(output_location, make_entry(script));
writeFileSync(output_location, make_entry(script?.attributes.src || ""));

if (!script) return;

const transformed_html =
(bundle["index.html"].source as string).substring(0, script?.range[0]) +
`<script type="module" crossorigin src="${cdn_base}/${version}/gradio.js"></script>` +
(bundle["index.html"].source as string).substring(
script?.range[1],
source.length
);

const share_html_location = join(config.dir, "share.html");
writeFileSync(share_html_location, transformed_html);
Comment on lines +51 to +60
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the share.html rather than copying it over like we used to, we created it directly from the index.html. What we are doing here is prepending the cdn_base to the scripts src. This is the only time we need to do this.

}
};
}
Expand All @@ -93,6 +70,7 @@ export function generate_dev_entry({ enable }: { enable: boolean }): Plugin {
name: "generate-dev-entry",
transform(code, id) {
if (!enable) return;

const new_code = code.replace(RE_SVELTE_IMPORT, (str, $1, $2) => {
return `const ${$1.replace(
" as ",
Expand All @@ -109,18 +87,7 @@ export function generate_dev_entry({ enable }: { enable: boolean }): Plugin {
}

function make_entry(script: string): string {
const make_script = `
function make_script(src) {
const script = document.createElement('script');
script.type = 'module';
script.setAttribute("crossorigin", "");
script.src = src;
document.head.appendChild(script);
}`;

return `
${make_script}
make_script("${script}");
return `import("${script}");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno why i chose the hard route previsouly.

The problem we always had was that anything that is requested from an html tag will always treat its own location as the base url from which to resolve files. With import it will use the files own location.

This gradio.js file will be request like: https://aws-etc.com/4.0.0/gradio.js so when we request./assets/index-asdasdasd.js it will be considered relative to that gradio.js url.

When we add a script to the page the 'file' in that case is the HTML file, which isn't where the cdn assets live.

`;
}

Expand Down Expand Up @@ -174,14 +141,6 @@ export function handle_ce_css(): Plugin {
}
);

const transformed_html =
(bundle["index.html"].source as string).substring(0, style!.range[0]) +
(bundle["index.html"].source as string).substring(
style!.range[1],
bundle["index.html"].source.length
);
const html_location = join(config.dir, "index.html");

writeFileSync(
file_to_insert.filename,
file_to_insert.source
Expand All @@ -192,7 +151,19 @@ export function handle_ce_css(): Plugin {
)
);

writeFileSync(html_location, transformed_html);
const share_html_location = join(config.dir, "share.html");
const share_html = readFileSync(share_html_location, "utf8");
const share_tree = parse(share_html);
const node = Array.from(
share_tree.querySelectorAll("link[rel=stylesheet]")
).find((node) => /.*\/index(.*?)\.css/.test(node.attributes.href));

if (!node) return;
const transformed_html =
share_html.substring(0, node.range[0]) +
share_html.substring(node.range[1], share_html.length);

writeFileSync(share_html_location, transformed_html);
Comment on lines +154 to +166
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are just stripping out the css file for the share.html, to prevent network errors. The CSS is loaded by the JS later anyway, so its no big deal.

}
};
}
Expand Down
5 changes: 1 addition & 4 deletions js/app/package.json
Expand Up @@ -8,10 +8,7 @@
"dev:lite": "run-p dev:lite:*",
"dev:lite:self": "vite --port 9876 --mode development:lite",
"dev:lite:worker": "pnpm --filter @gradio/wasm dev",
"build:cdn": "vite build --mode production:cdn --emptyOutDir",
"build:website": "vite build --mode production:website --emptyOutDir",
"build:local": "vite build --mode production:local --emptyOutDir",
"build:cc": "cross-env NODE_ENV=development vite build --mode dev:custom --emptyOutDir",
"build": "vite build --mode production --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",
Expand Down
8 changes: 5 additions & 3 deletions js/app/src/css.ts
@@ -1,16 +1,18 @@
export function mount_css(url: string, target: HTMLElement): Promise<void> {
const existing_link = document.querySelector(`link[href='${url}']`);
const base = new URL(import.meta.url).origin;
const _url = new URL(url, base).href;
const existing_link = document.querySelector(`link[href='${_url}']`);

if (existing_link) return Promise.resolve();

const link = document.createElement("link");
link.rel = "stylesheet";
link.href = url;
link.href = _url;

return new Promise((res, rej) => {
link.addEventListener("load", () => res());
link.addEventListener("error", () => {
console.error(`Unable to preload CSS for ${url}`);
console.error(`Unable to preload CSS for ${_url}`);
res();
});
target.appendChild(link);
Expand Down
16 changes: 13 additions & 3 deletions js/app/src/main.ts
Expand Up @@ -21,6 +21,15 @@ let FONTS: string | [];
FONTS = "__FONTS_CSS__";

let IndexComponent: typeof Index;
let _res: (value?: unknown) => void;
let pending = new Promise((res) => {
_res = res;
});
async function get_index(): Promise<void> {
IndexComponent = (await import("./Index.svelte")).default;
_res();
}

Comment on lines +24 to +32
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to the build changed but fixes a race condition I noticed.

function create_custom_element(): void {
const o = {
SvelteComponent: svelte.SvelteComponent
Expand Down Expand Up @@ -72,7 +81,7 @@ function create_custom_element(): void {
}

async connectedCallback(): Promise<void> {
IndexComponent = (await import("./Index.svelte")).default;
await get_index();
this.loading = true;

if (this.app) {
Expand Down Expand Up @@ -136,11 +145,12 @@ function create_custom_element(): void {
return ["src", "space", "host"];
}

attributeChangedCallback(
async attributeChangedCallback(
name: string,
old_val: string,
new_val: string
): void {
): Promise<void> {
await pending;
if (
(name === "host" || name === "space" || name === "src") &&
new_val !== old_val
Expand Down
32 changes: 9 additions & 23 deletions js/app/vite.config.ts
Expand Up @@ -28,40 +28,30 @@ const client_version_raw = JSON.parse(

import {
inject_ejs,
patch_dynamic_import,
generate_cdn_entry,
generate_dev_entry,
handle_ce_css,
inject_component_loader,
resolve_svelte
} from "./build_plugins";

const GRADIO_VERSION = version || "asd_stub_asd";
const TEST_CDN = !!process.env.TEST_CDN;
const CDN = TEST_CDN
? "http://localhost:4321/"
: `https://gradio.s3-us-west-2.amazonaws.com/${version_raw}/`;
const GRADIO_VERSION = version_raw || "asd_stub_asd";
const CDN_BASE = "https://gradio.s3-us-west-2.amazonaws.com";
const TEST_MODE = process.env.TEST_MODE || "jsdom";

//@ts-ignore
export default defineConfig(({ mode }) => {
console.log(mode);
const targets = {
"production:cdn": "../../gradio/templates/cdn",
"production:local": "../../gradio/templates/frontend",
production: "../../gradio/templates/frontend",
"dev:custom": "../../gradio/templates/frontend"
};
const CDN_URL = mode === "production:cdn" ? CDN : "/";
const production =
mode === "production:cdn" ||
mode === "production:local" ||
mode === "production:website" ||
mode === "production:lite";
const is_cdn = mode === "production:cdn" || mode === "production:website";
const production = mode === "production" || mode === "production:lite";
const is_lite = mode.endsWith(":lite");

return {
base: is_cdn ? CDN_URL : "./",
base: "./",

server: {
port: 9876,
open: is_lite ? "/lite.html" : "/"
Expand All @@ -70,7 +60,8 @@ export default defineConfig(({ mode }) => {
build: {
sourcemap: true,
target: "esnext",
minify: production,
// minify: production,
minify: false,
outDir: is_lite ? resolve(__dirname, "../lite/dist") : targets[mode],
// To build Gradio-lite as a library, we can't use the library mode
// like `lib: is_lite && {}`
Expand Down Expand Up @@ -161,12 +152,7 @@ export default defineConfig(({ mode }) => {
}),
generate_dev_entry({ enable: mode !== "development" && mode !== "test" }),
inject_ejs(),
patch_dynamic_import({
mode: is_cdn ? "cdn" : "local",
gradio_version: GRADIO_VERSION,
cdn_url: CDN_URL
}),
generate_cdn_entry({ enable: is_cdn, cdn_url: CDN_URL }),
generate_cdn_entry({ version: GRADIO_VERSION, cdn_base: CDN_BASE }),
handle_ce_css(),
inject_component_loader()
],
Expand Down
30 changes: 9 additions & 21 deletions js/preview/rollup.config.js
Expand Up @@ -18,7 +18,6 @@ const vite_client = require.resolve("vite/dist/client/client.mjs");
const hmr = require.resolve("svelte-hmr");

const output_svelte_dir = "../../gradio/templates/frontend/assets/svelte";
const output_svelte_dir_cdn = "../../gradio/templates/cdn/assets/svelte";

const onwarn = (warning, warn) => {
if (warning.plugin === "typescript") return;
Expand Down Expand Up @@ -153,32 +152,21 @@ export default [
},
{
input: "src/svelte-submodules.ts",
output: [
{
file: join(output_svelte_dir, "svelte-submodules.js"),
format: "esm"
},
{
file: join(output_svelte_dir_cdn, "svelte-submodules.js"),
format: "esm"
}
],
output: {
file: join(output_svelte_dir, "svelte-submodules.js"),
format: "esm"
},

onwarn,
plugins
},

{
input: "src/svelte-internal.ts",
output: [
{
file: join(output_svelte_dir, "svelte.js"),
format: "esm"
},
{
file: join(output_svelte_dir_cdn, "svelte.js"),
format: "esm"
}
],
output: {
file: join(output_svelte_dir, "svelte.js"),
format: "esm"
},
onwarn,
plugins
},
Expand Down