diff --git a/apps/web/src/parts/no-wasm.ts b/apps/web/src/parts/no-wasm.ts new file mode 100644 index 0000000..9b4b96a --- /dev/null +++ b/apps/web/src/parts/no-wasm.ts @@ -0,0 +1,12 @@ +/** + * Stands in for Shiki's Oniguruma engine, a 600 KB wasm, through the alias in vite.config.ts. + * Shiki's full bundle names it for its shorthand highlighter, which the app never uses, so it was + * built but never loaded. highlight.ts highlights through @streamdown/code, which uses Shiki's + * JavaScript engine instead. Loading the stand-in fails, so anything that did reach for the engine + * would say why rather than highlight nothing. + */ +throw new Error( + "Shiki's Oniguruma engine is left out of the build. Highlight through highlight.ts.", +) + +export {} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 458f64f..ccf4f58 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -50,9 +50,18 @@ export default defineConfig({ babel({ presets: [reactCompilerPreset()] }), precompress(), ], - // Streamdown's HTML plugin, which markdown.tsx never uses, stays out of the build. + // Streamdown's HTML plugin and Shiki's wasm engine, which the app never uses, stay out of the build. resolve: { - alias: { 'rehype-raw': fileURLToPath(new URL('src/parts/no-raw.ts', import.meta.url)) }, + alias: [ + { + find: 'rehype-raw', + replacement: fileURLToPath(new URL('src/parts/no-raw.ts', import.meta.url)), + }, + { + find: /^shiki\/wasm$/, + replacement: fileURLToPath(new URL('src/parts/no-wasm.ts', import.meta.url)), + }, + ], }, optimizeDeps: { // There is no index.html to scan, so scan every route up front. Otherwise a dependency found diff --git a/specs/web-ui.md b/specs/web-ui.md index ee11392..9b6c4bd 100644 --- a/specs/web-ui.md +++ b/specs/web-ui.md @@ -33,7 +33,7 @@ The server process runs Hono, and Hono runs Start, so the server and plugins sta - A route's file loads with the app's entry, and only its `component` is split into a chunk of its own, so whatever a route names directly, such as its pending or error page or its loader's queries, stays light. The shared page's skeleton and error page live apart from the page, which draws messages and so brings the Markdown renderer. - The chat routes' head preloads the store's worker and SQLite's wasm, their largest downloads, so they download alongside the app's code rather than once the worker runs, and preconnects to the avatar's image CDN. Their loader starts the models, attachment types, and profile queries without waiting for them, so they don't wait for the page's code. - The conversation and the search results, which bring the Markdown renderers, load with `React.lazy`. The chat route's loader starts the conversation's download, and the page for a new chat starts it once it shows, so it's there when the first message is sent. Opening the search starts the results'. - - Streamdown imports `rehype-raw` for its defaults, which `markdown.tsx` leaves out, so `vite.config.ts` aliases it to an empty stand-in, keeping its HTML parser out of the build. + - Streamdown imports `rehype-raw` for its defaults, which `markdown.tsx` leaves out, so `vite.config.ts` aliases it to an empty stand-in, keeping its HTML parser out of the build. Shiki's full bundle names its Oniguruma engine, a 600 KB wasm, for a shorthand highlighter the app never uses, since `@streamdown/code` highlights with Shiki's JavaScript engine, so `vite.config.ts` aliases `shiki/wasm` to a stand-in that fails to load, keeping the wasm out of the build. - Pointing at or focusing a link preloads its route, with the query cache, not the router's, deciding what is stale. - **Development.** The same server entry creates Vite in middleware mode on the server's HTTP server, so HMR shares its port. Each request goes through Vite's middlewares first, which serve modules and call on for everything else. Pages go to Start's server entry, which the server imports through Vite's SSR runner on each request, so web edits apply without a restart. `pnpm dev` runs one process on one port, with no proxy. - **Request context.** Hono passes Start a context holding the configured app name and description, `PUBLIC_URL`, and a `fetch` that calls the Hono app in process, resolving paths against the page request and sending its cookie. During server rendering, the typed client in `api.ts` calls the API through that `fetch`, found with Start's `getGlobalStartContext`, and in the browser it uses the browser's `fetch`. The root route reads the app name and description from the context, and in the browser from the rendered page.