|
import { join } from "path"; |
|
import * as fs from "fs"; |
|
import { createServer, createLogger } from "vite"; |
|
import { plugins, make_gradio_plugin } from "./plugins"; |
|
import { examine_module } from "./index"; |
|
import type { PreprocessorGroup } from "svelte/compiler"; |
|
|
|
const vite_messages_to_ignore = [ |
|
"Default and named imports from CSS files are deprecated.", |
|
"The above dynamic import cannot be analyzed by Vite." |
|
]; |
|
|
|
const logger = createLogger(); |
|
const originalWarning = logger.warn; |
|
logger.warn = (msg, options) => { |
|
if (vite_messages_to_ignore.some((m) => msg.includes(m))) return; |
|
|
|
originalWarning(msg, options); |
|
}; |
|
|
|
interface ServerOptions { |
|
component_dir: string; |
|
root_dir: string; |
|
frontend_port: number; |
|
backend_port: number; |
|
host: string; |
|
python_path: string; |
|
} |
|
|
|
export async function create_server({ |
|
component_dir, |
|
root_dir, |
|
frontend_port, |
|
backend_port, |
|
host, |
|
python_path |
|
}: ServerOptions): Promise<void> { |
|
process.env.gradio_mode = "dev"; |
|
const [imports, config] = await generate_imports( |
|
component_dir, |
|
root_dir, |
|
python_path |
|
); |
|
|
|
const svelte_dir = join(root_dir, "assets", "svelte"); |
|
|
|
try { |
|
const server = await createServer({ |
|
customLogger: logger, |
|
mode: "development", |
|
configFile: false, |
|
root: root_dir, |
|
server: { |
|
port: frontend_port, |
|
host: host, |
|
fs: { |
|
allow: [root_dir, component_dir] |
|
} |
|
}, |
|
resolve: { |
|
conditions: ["gradio"] |
|
}, |
|
build: { |
|
target: config.build.target |
|
}, |
|
optimizeDeps: config.optimizeDeps, |
|
plugins: [ |
|
...plugins(config), |
|
make_gradio_plugin({ |
|
mode: "dev", |
|
backend_port, |
|
svelte_dir, |
|
imports |
|
}) |
|
] |
|
}); |
|
|
|
await server.listen(); |
|
|
|
console.info( |
|
`[orange3]Frontend Server[/] (Go here): ${server.resolvedUrls?.local}` |
|
); |
|
} catch (e) { |
|
console.error(e); |
|
} |
|
} |
|
|
|
function find_frontend_folders(start_path: string): string[] { |
|
if (!fs.existsSync(start_path)) { |
|
console.warn("No directory found at:", start_path); |
|
return []; |
|
} |
|
|
|
if (fs.existsSync(join(start_path, "pyproject.toml"))) return [start_path]; |
|
|
|
const results: string[] = []; |
|
const dir = fs.readdirSync(start_path); |
|
dir.forEach((dir) => { |
|
const filepath = join(start_path, dir); |
|
if (fs.existsSync(filepath)) { |
|
if (fs.existsSync(join(filepath, "pyproject.toml"))) |
|
results.push(filepath); |
|
} |
|
}); |
|
|
|
return results; |
|
} |
|
|
|
function to_posix(_path: string): string { |
|
const isExtendedLengthPath = /^\\\\\?\\/.test(_path); |
|
const hasNonAscii = /[^\u0000-\u0080]+/.test(_path); |
|
|
|
if (isExtendedLengthPath || hasNonAscii) { |
|
return _path; |
|
} |
|
|
|
return _path.replace(/\\/g, "/"); |
|
} |
|
|
|
export interface ComponentConfig { |
|
plugins: any[]; |
|
svelte: { |
|
preprocess: PreprocessorGroup[]; |
|
extensions?: string[]; |
|
}; |
|
build: { |
|
target: string | string[]; |
|
}; |
|
optimizeDeps: object; |
|
} |
|
|
|
async function generate_imports( |
|
component_dir: string, |
|
root: string, |
|
python_path: string |
|
): Promise<[string, ComponentConfig]> { |
|
const components = find_frontend_folders(component_dir); |
|
|
|
const component_entries = components.flatMap((component) => { |
|
return examine_module(component, root, python_path, "dev"); |
|
}); |
|
if (component_entries.length === 0) { |
|
console.info( |
|
`No custom components were found in ${component_dir}. It is likely that dev mode does not work properly. Please pass the --gradio-path and --python-path CLI arguments so that gradio uses the right executables.` |
|
); |
|
} |
|
|
|
let component_config: ComponentConfig = { |
|
plugins: [], |
|
svelte: { |
|
preprocess: [] |
|
}, |
|
build: { |
|
target: [] |
|
}, |
|
optimizeDeps: {} |
|
}; |
|
|
|
await Promise.all( |
|
component_entries.map(async (component) => { |
|
if ( |
|
component.frontend_dir && |
|
fs.existsSync(join(component.frontend_dir, "gradio.config.js")) |
|
) { |
|
const m = await import( |
|
join("file://" + component.frontend_dir, "gradio.config.js") |
|
); |
|
|
|
component_config.plugins = m.default.plugins || []; |
|
component_config.svelte.preprocess = m.default.svelte?.preprocess || []; |
|
component_config.build.target = m.default.build?.target || "modules"; |
|
component_config.optimizeDeps = m.default.optimizeDeps || {}; |
|
} else { |
|
} |
|
}) |
|
); |
|
|
|
const imports = component_entries.reduce((acc, component) => { |
|
const pkg = JSON.parse( |
|
fs.readFileSync(join(component.frontend_dir, "package.json"), "utf-8") |
|
); |
|
|
|
const exports: Record<string, any | undefined> = { |
|
component: pkg.exports["."], |
|
example: pkg.exports["./example"] |
|
}; |
|
|
|
if (!exports.component) |
|
throw new Error( |
|
"Could not find component entry point. Please check the exports field of your package.json." |
|
); |
|
|
|
const example = exports.example |
|
? `example: () => import("/@fs/${to_posix( |
|
join(component.frontend_dir, exports.example.gradio) |
|
)}"),\n` |
|
: ""; |
|
return `${acc}"${component.component_class_id}": { |
|
${example} |
|
component: () => import("/@fs/${to_posix( |
|
join(component.frontend_dir, exports.component.gradio) |
|
)}") |
|
},\n`; |
|
}, ""); |
|
|
|
return [`{${imports}}`, component_config]; |
|
} |
|
|