@omg-dev/vite-plugin 0.4.41 → 0.4.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/build.ts +54 -6
- package/src/prerender.test.ts +34 -0
- package/src/prerender.ts +145 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omg-dev/vite-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"build": "vp pack src/index.ts --no-fail-on-warn"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@omg-dev/server": "0.4.
|
|
19
|
-
"@omg-dev/schema": "0.4.
|
|
18
|
+
"@omg-dev/server": "0.4.43",
|
|
19
|
+
"@omg-dev/schema": "0.4.43",
|
|
20
20
|
"ws": "^8.18.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
package/src/build.ts
CHANGED
|
@@ -41,6 +41,9 @@ import { prerenderApp } from "./prerender.ts"
|
|
|
41
41
|
const root = process.cwd()
|
|
42
42
|
const vibesDir = path.join(root, ".vibes")
|
|
43
43
|
const distDir = path.join(root, "dist")
|
|
44
|
+
// TanStack Start's server build output. Its presence after the client build is
|
|
45
|
+
// what puts this build in SSR mode (see step 4).
|
|
46
|
+
const startServerEntry = path.join(distDir, "server", "server.js")
|
|
44
47
|
|
|
45
48
|
// The build is not a runtime. Step 4b (prerender) spins up a nested Vite
|
|
46
49
|
// dev server to SSR the app's root view, which — via the vibes vite-plugin's
|
|
@@ -189,6 +192,9 @@ async function main() {
|
|
|
189
192
|
fs.writeFileSync(path.join(vibesDir, "workflows.generated.ts"), wfLines.join("\n"))
|
|
190
193
|
|
|
191
194
|
// ── 3. write server.entry.ts ──────────────────────────────────────────────
|
|
195
|
+
// Deferred until after the client build (step 4) because the entry's shape
|
|
196
|
+
// depends on whether that build emitted an SPA or a Start SSR handler.
|
|
197
|
+
const writeServerEntry = (ssr: boolean) => {
|
|
192
198
|
log("3/5 generating .vibes/server.entry.ts")
|
|
193
199
|
const entry = [
|
|
194
200
|
"// AUTO-GENERATED by @omg-dev/vite-plugin. Do not edit.",
|
|
@@ -201,6 +207,10 @@ async function main() {
|
|
|
201
207
|
'import { routes } from "./routes.generated.ts"',
|
|
202
208
|
'import { triggers } from "./triggers.generated.ts"',
|
|
203
209
|
'import { workflows } from "./workflows.generated.ts"',
|
|
210
|
+
// Start's server build. bun build inlines it (and react, h3, seroval, …)
|
|
211
|
+
// into server.mjs, which is what lets the runtime keep its no-node_modules
|
|
212
|
+
// contract — the raw dist/server/server.js externalizes all of them.
|
|
213
|
+
...(ssr ? ['import startServer from "../dist/server/server.js"'] : []),
|
|
204
214
|
"",
|
|
205
215
|
"const PORT = Number(process.env.PORT) || 3000",
|
|
206
216
|
"",
|
|
@@ -212,7 +222,17 @@ async function main() {
|
|
|
212
222
|
" routes,",
|
|
213
223
|
" triggers,",
|
|
214
224
|
" workflows,",
|
|
215
|
-
|
|
225
|
+
// Under SSR the browser assets live in dist/client and Start owns every
|
|
226
|
+
// HTML document, so serveStatic must NOT SPA-fallback — an unmatched path
|
|
227
|
+
// has to reach Start's fetch handler below to get a real 404 page or a
|
|
228
|
+
// server-rendered route.
|
|
229
|
+
...(ssr
|
|
230
|
+
? [
|
|
231
|
+
" staticDir: \"dist/client\",",
|
|
232
|
+
" staticFallthrough: true,",
|
|
233
|
+
" notFound: (req) => startServer.fetch(req),",
|
|
234
|
+
]
|
|
235
|
+
: [" staticDir: \"dist\","]),
|
|
216
236
|
" // Migrations run at runtime (idempotent — diffs sqlite_master) so",
|
|
217
237
|
" // user data in .vibes/data.db persists across redeploys. The build",
|
|
218
238
|
" // artifact intentionally does NOT ship a seeded DB.",
|
|
@@ -290,6 +310,8 @@ async function main() {
|
|
|
290
310
|
" } },",
|
|
291
311
|
" )",
|
|
292
312
|
" }",
|
|
313
|
+
// Under SSR, unmatched requests reach Start via the `notFound` seam passed
|
|
314
|
+
// to createVibesServer above — so this stays one call in both modes.
|
|
293
315
|
" return vibes.fetch(req)",
|
|
294
316
|
" },",
|
|
295
317
|
" websocket: {",
|
|
@@ -323,12 +345,25 @@ async function main() {
|
|
|
323
345
|
"",
|
|
324
346
|
].join("\n")
|
|
325
347
|
fs.writeFileSync(path.join(vibesDir, "server.entry.ts"), entry)
|
|
348
|
+
}
|
|
326
349
|
|
|
327
350
|
// ── 4. vite build (client) ────────────────────────────────────────────────
|
|
328
351
|
log("4/5 vite build (client)")
|
|
329
352
|
fs.rmSync(distDir, { recursive: true, force: true })
|
|
330
353
|
run("bunx", ["--bun", "vp", "build"])
|
|
331
|
-
|
|
354
|
+
|
|
355
|
+
// SPA vs SSR is decided by what the build actually emitted, not by config
|
|
356
|
+
// parsing: TanStack Start emits dist/client/ + dist/server/server.js (a
|
|
357
|
+
// module whose default export is `{ fetch }`), where an SPA emits
|
|
358
|
+
// dist/index.html. Reading the output keeps this honest if Start changes
|
|
359
|
+
// its plugin API but keeps its output contract.
|
|
360
|
+
const ssr = fs.existsSync(startServerEntry)
|
|
361
|
+
if (ssr) {
|
|
362
|
+
if (!fs.existsSync(path.join(distDir, "client"))) {
|
|
363
|
+
die("SSR build produced dist/server/server.js but no dist/client/")
|
|
364
|
+
}
|
|
365
|
+
log("mode: SSR (TanStack Start) — dist/client + dist/server/server.js")
|
|
366
|
+
} else if (!fs.existsSync(path.join(distDir, "index.html"))) {
|
|
332
367
|
die("vite build did not produce dist/index.html")
|
|
333
368
|
}
|
|
334
369
|
|
|
@@ -337,8 +372,17 @@ async function main() {
|
|
|
337
372
|
// Strictly additive and FAIL-SOFT (see prerender.ts): any failure leaves the
|
|
338
373
|
// client build untouched, so it can never break a deploy. The client mounts
|
|
339
374
|
// the full SPA over whatever HTML lands in #root.
|
|
340
|
-
|
|
341
|
-
|
|
375
|
+
//
|
|
376
|
+
// Skipped under SSR: Start renders the document per request, so there is no
|
|
377
|
+
// #root shell to bake into and nothing to gain.
|
|
378
|
+
if (ssr) {
|
|
379
|
+
log("4b/5 prerender skipped (SSR renders every request)")
|
|
380
|
+
} else {
|
|
381
|
+
log("4b/5 prerender (SSG, fail-soft)")
|
|
382
|
+
await prerenderApp({ root, distDir, vibesDir, log })
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
writeServerEntry(ssr)
|
|
342
386
|
|
|
343
387
|
// ── 5. bun build (server) ─────────────────────────────────────────────────
|
|
344
388
|
log("5/5 bun build server")
|
|
@@ -382,7 +426,9 @@ async function main() {
|
|
|
382
426
|
// causes data loss.
|
|
383
427
|
// Workflows force a runtime VM: a static deploy has no server for the
|
|
384
428
|
// engine to invoke, so workflow-only apps are NOT static.
|
|
385
|
-
|
|
429
|
+
// An SSR app is never static: object storage has no server to render the
|
|
430
|
+
// document, so serving dist/ directly would hand visitors a blank page.
|
|
431
|
+
const isStatic = !ssr && !hasDb && routes.length === 0 && workflows.length === 0
|
|
386
432
|
|
|
387
433
|
// Serialize the app's @omg-dev/billing declaration (if any) into the manifest.
|
|
388
434
|
// The orchestrator reads manifest.catalog at deploy time and persists it to
|
|
@@ -401,7 +447,9 @@ async function main() {
|
|
|
401
447
|
static: isStatic,
|
|
402
448
|
functions: routes.length,
|
|
403
449
|
artifacts: [
|
|
404
|
-
|
|
450
|
+
// SSR ships only dist/client — dist/server was inlined into server.mjs
|
|
451
|
+
// by the bun build below, so shipping it again is dead weight.
|
|
452
|
+
{ src: ssr ? "dist/client" : "dist", kind: "dir" },
|
|
405
453
|
{ src: ".vibes/server.mjs", kind: "file" },
|
|
406
454
|
],
|
|
407
455
|
entry: ".vibes/server.mjs",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { isStaticPath, routeHtmlPath } from "./prerender.ts"
|
|
3
|
+
|
|
4
|
+
describe("isStaticPath", () => {
|
|
5
|
+
test("keeps routes that resolve to exactly one URL", () => {
|
|
6
|
+
expect(isStaticPath("/")).toBe(true)
|
|
7
|
+
expect(isStaticPath("/pricing")).toBe(true)
|
|
8
|
+
expect(isStaticPath("/docs/getting-started")).toBe(true)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
// A param route has no single URL to bake, so baking one would pin every
|
|
12
|
+
// /posts/* request to whichever id happened to render at build time.
|
|
13
|
+
test("drops param and splat routes", () => {
|
|
14
|
+
expect(isStaticPath("/posts/$postId")).toBe(false)
|
|
15
|
+
expect(isStaticPath("/files/$")).toBe(false)
|
|
16
|
+
expect(isStaticPath("/docs/*")).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("drops anything that is not an absolute path", () => {
|
|
20
|
+
expect(isStaticPath("pricing")).toBe(false)
|
|
21
|
+
expect(isStaticPath("")).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe("routeHtmlPath", () => {
|
|
26
|
+
// Directory-index form is load-bearing: it is what serveStatic in
|
|
27
|
+
// @omg-dev/server and the static-deploy path in proxy/deploy.go resolve.
|
|
28
|
+
// "pricing.html" would 404 into the SPA fallback and silently undo the bake.
|
|
29
|
+
test("maps / to the shell and every other route to a directory index", () => {
|
|
30
|
+
expect(routeHtmlPath("/app/dist", "/")).toBe("/app/dist/index.html")
|
|
31
|
+
expect(routeHtmlPath("/app/dist", "/pricing")).toBe("/app/dist/pricing/index.html")
|
|
32
|
+
expect(routeHtmlPath("/app/dist", "/docs/intro")).toBe("/app/dist/docs/intro/index.html")
|
|
33
|
+
})
|
|
34
|
+
})
|
package/src/prerender.ts
CHANGED
|
@@ -1,26 +1,47 @@
|
|
|
1
1
|
// Build-time prerender (SSG) for user apps.
|
|
2
2
|
//
|
|
3
|
-
// Renders the app
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Renders the app to static HTML at build time so crawlers + first paint see
|
|
4
|
+
// real content instead of an empty SPA shell. The client (src/main.tsx,
|
|
5
|
+
// createRoot) mounts the full app over whatever HTML is in #root — so behavior
|
|
6
|
+
// is identical whether or not the prerender ran; it only changes the initial
|
|
7
|
+
// bytes.
|
|
8
|
+
//
|
|
9
|
+
// Two modes, picked automatically:
|
|
10
|
+
//
|
|
11
|
+
// router — src/routeTree.gen.ts exists (TanStack Router, the default
|
|
12
|
+
// template since the router landed). Every STATIC route is
|
|
13
|
+
// rendered at its own path and written to dist/<path>/index.html,
|
|
14
|
+
// with "/" going to dist/index.html. This is why it matters: the
|
|
15
|
+
// static serving layers fall back to index.html for unknown paths,
|
|
16
|
+
// so a home-only bake would serve homepage markup for /pricing —
|
|
17
|
+
// wrong first paint, wrong SEO. Parameterized routes ($id, splats)
|
|
18
|
+
// are skipped; they keep the SPA fallback.
|
|
19
|
+
// app — no route tree: render <App/> from src/App.tsx into
|
|
20
|
+
// dist/index.html. This is the pre-router behavior and every app
|
|
21
|
+
// created before the router still takes it, unchanged.
|
|
8
22
|
//
|
|
9
23
|
// FAIL-SOFT IS THE CONTRACT. This runs against arbitrary agent-generated code,
|
|
10
24
|
// so anything can go wrong (a module-scope `window` reference, a render-time
|
|
11
25
|
// throw, a missing App). Every failure path leaves dist/index.html exactly as
|
|
12
26
|
// the client build produced it and returns normally — a prerender failure can
|
|
13
|
-
// never fail a deploy.
|
|
27
|
+
// never fail a deploy. In router mode that guarantee is per-route: one route
|
|
28
|
+
// that throws costs you that route's static HTML, nothing else. The worst case
|
|
29
|
+
// is "no prerender", never a broken page.
|
|
14
30
|
import fs from "node:fs"
|
|
15
31
|
import path from "node:path"
|
|
16
32
|
|
|
17
33
|
const ROOT_MARKER = '<div id="root"></div>'
|
|
18
34
|
|
|
35
|
+
// Upper bound on how many routes we'll render in one build. A pathological
|
|
36
|
+
// route tree shouldn't be able to stall a deploy; past this we bake the ones
|
|
37
|
+
// we got and leave the rest on the SPA fallback.
|
|
38
|
+
const MAX_ROUTES = 50
|
|
39
|
+
|
|
19
40
|
export type PrerenderResult =
|
|
20
|
-
| { status: "baked"; bytes: number }
|
|
41
|
+
| { status: "baked"; bytes: number; routes?: number }
|
|
21
42
|
| { status: "skipped"; reason: string }
|
|
22
43
|
|
|
23
|
-
// Renders
|
|
44
|
+
// Renders the app to static HTML under distDir. Never throws.
|
|
24
45
|
export async function prerenderApp(opts: {
|
|
25
46
|
root: string
|
|
26
47
|
distDir: string
|
|
@@ -32,8 +53,10 @@ export async function prerenderApp(opts: {
|
|
|
32
53
|
const htmlPath = path.join(distDir, "index.html")
|
|
33
54
|
|
|
34
55
|
try {
|
|
56
|
+
const routeTreePath = path.join(root, "src", "routeTree.gen.ts")
|
|
57
|
+
const routerMode = fs.existsSync(routeTreePath)
|
|
35
58
|
const appPath = path.join(root, "src", "App.tsx")
|
|
36
|
-
if (!fs.existsSync(appPath)) {
|
|
59
|
+
if (!routerMode && !fs.existsSync(appPath)) {
|
|
37
60
|
return done(log, { status: "skipped", reason: "src/App.tsx not found" })
|
|
38
61
|
}
|
|
39
62
|
if (!fs.existsSync(htmlPath)) {
|
|
@@ -45,24 +68,11 @@ export async function prerenderApp(opts: {
|
|
|
45
68
|
}
|
|
46
69
|
|
|
47
70
|
// Build-generated SSR entry — lives in .vibes/ so we never add a file to the
|
|
48
|
-
// user's src tree, and works for apps that predate this feature.
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// matches the client's first render.
|
|
71
|
+
// user's src tree, and works for apps that predate this feature. Renders in
|
|
72
|
+
// the same StrictMode wrapper the client uses (src/main.tsx), so the baked
|
|
73
|
+
// markup matches the client's first render.
|
|
52
74
|
const entryPath = path.join(vibesDir, "prerender.entry.tsx")
|
|
53
|
-
fs.writeFileSync(
|
|
54
|
-
entryPath,
|
|
55
|
-
[
|
|
56
|
-
"// AUTO-GENERATED by @omg-dev/vite-plugin. Do not edit.",
|
|
57
|
-
'import { StrictMode } from "react"',
|
|
58
|
-
'import { renderToString } from "react-dom/server"',
|
|
59
|
-
'import App from "../src/App.tsx"',
|
|
60
|
-
"export function render() {",
|
|
61
|
-
" return renderToString(<StrictMode><App /></StrictMode>)",
|
|
62
|
-
"}",
|
|
63
|
-
"",
|
|
64
|
-
].join("\n"),
|
|
65
|
-
)
|
|
75
|
+
fs.writeFileSync(entryPath, routerMode ? routerEntry() : appEntry())
|
|
66
76
|
|
|
67
77
|
// Use the app's own Vite config (react plugin, @/ alias, @omg-dev/vite-plugin)
|
|
68
78
|
// so SSR module resolution matches the client build. middlewareMode = no
|
|
@@ -77,15 +87,51 @@ export async function prerenderApp(opts: {
|
|
|
77
87
|
})
|
|
78
88
|
try {
|
|
79
89
|
const mod = (await vite.ssrLoadModule("/.vibes/prerender.entry.tsx")) as {
|
|
80
|
-
render
|
|
90
|
+
render?: () => string
|
|
91
|
+
listPaths?: () => string[]
|
|
92
|
+
renderPath?: (p: string) => Promise<string>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!routerMode) {
|
|
96
|
+
const html = mod.render?.()
|
|
97
|
+
if (!html || !html.trim()) {
|
|
98
|
+
return done(log, { status: "skipped", reason: "empty render output" })
|
|
99
|
+
}
|
|
100
|
+
writeShell(htmlPath, shell, html)
|
|
101
|
+
return done(log, { status: "baked", bytes: html.length })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const all = mod.listPaths?.() ?? []
|
|
105
|
+
const paths = all.filter(isStaticPath).slice(0, MAX_ROUTES)
|
|
106
|
+
if (paths.length === 0) {
|
|
107
|
+
return done(log, { status: "skipped", reason: "no static routes in the route tree" })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let baked = 0
|
|
111
|
+
let bytes = 0
|
|
112
|
+
for (const p of paths) {
|
|
113
|
+
// Per-route fail-soft: a route that throws (loader hitting the network,
|
|
114
|
+
// a browser-only global at render time) simply doesn't get static HTML.
|
|
115
|
+
try {
|
|
116
|
+
const html = await mod.renderPath?.(p)
|
|
117
|
+
if (!html || !html.trim()) {
|
|
118
|
+
log(`prerender: – ${p} skipped (empty render output)`)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
writeShell(routeHtmlPath(distDir, p), shell, html)
|
|
122
|
+
baked++
|
|
123
|
+
bytes += html.length
|
|
124
|
+
} catch (err) {
|
|
125
|
+
log(`prerender: – ${p} skipped (${String(err).split("\n")[0]})`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (baked === 0) {
|
|
129
|
+
return done(log, { status: "skipped", reason: "every route failed to render" })
|
|
81
130
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return done(log, { status: "skipped", reason: "empty render output" })
|
|
131
|
+
if (all.length > paths.length) {
|
|
132
|
+
log(`prerender: ${all.length - paths.length} dynamic/overflow route(s) left on the SPA fallback`)
|
|
85
133
|
}
|
|
86
|
-
|
|
87
|
-
fs.writeFileSync(htmlPath, out)
|
|
88
|
-
return done(log, { status: "baked", bytes: html.length })
|
|
134
|
+
return done(log, { status: "baked", bytes, routes: baked })
|
|
89
135
|
} finally {
|
|
90
136
|
await vite.close()
|
|
91
137
|
fs.rmSync(entryPath, { force: true })
|
|
@@ -96,9 +142,74 @@ export async function prerenderApp(opts: {
|
|
|
96
142
|
}
|
|
97
143
|
}
|
|
98
144
|
|
|
145
|
+
// "/" → dist/index.html, "/pricing" → dist/pricing/index.html. Directory-index
|
|
146
|
+
// form (not "pricing.html") because that is what the static servers resolve —
|
|
147
|
+
// see serveStatic in @omg-dev/server and the static-deploy path in
|
|
148
|
+
// apps/infra/internal/proxy/deploy.go.
|
|
149
|
+
export function routeHtmlPath(distDir: string, routePath: string): string {
|
|
150
|
+
if (routePath === "/") return path.join(distDir, "index.html")
|
|
151
|
+
return path.join(distDir, routePath.replace(/^\/+/, ""), "index.html")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function writeShell(outPath: string, shell: string, html: string): void {
|
|
155
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
|
156
|
+
fs.writeFileSync(outPath, shell.replace(ROOT_MARKER, `<div id="root">${html}</div>`))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Only routes that resolve to exactly one URL can be baked to a file. Anything
|
|
160
|
+
// with a param ($id), a splat, or a pathless/layout marker is left alone.
|
|
161
|
+
export function isStaticPath(p: string): boolean {
|
|
162
|
+
if (!p.startsWith("/")) return false
|
|
163
|
+
if (p.includes("$") || p.includes("*")) return false
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function appEntry(): string {
|
|
168
|
+
return [
|
|
169
|
+
"// AUTO-GENERATED by @omg-dev/vite-plugin. Do not edit.",
|
|
170
|
+
'import { StrictMode } from "react"',
|
|
171
|
+
'import { renderToString } from "react-dom/server"',
|
|
172
|
+
'import App from "../src/App.tsx"',
|
|
173
|
+
"export function render() {",
|
|
174
|
+
" return renderToString(<StrictMode><App /></StrictMode>)",
|
|
175
|
+
"}",
|
|
176
|
+
"",
|
|
177
|
+
].join("\n")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function routerEntry(): string {
|
|
181
|
+
return [
|
|
182
|
+
"// AUTO-GENERATED by @omg-dev/vite-plugin. Do not edit.",
|
|
183
|
+
'import { StrictMode } from "react"',
|
|
184
|
+
'import { renderToString } from "react-dom/server"',
|
|
185
|
+
'import { RouterProvider, createRouter, createMemoryHistory } from "@tanstack/react-router"',
|
|
186
|
+
'import { routeTree } from "../src/routeTree.gen"',
|
|
187
|
+
"",
|
|
188
|
+
"// Every URL the route tree can resolve, params included — the caller",
|
|
189
|
+
"// filters down to the static ones.",
|
|
190
|
+
"export function listPaths() {",
|
|
191
|
+
" const router = createRouter({ routeTree })",
|
|
192
|
+
" return Object.keys(router.routesByPath ?? {})",
|
|
193
|
+
"}",
|
|
194
|
+
"",
|
|
195
|
+
"// A fresh router per path: routers carry resolved match state, so reusing",
|
|
196
|
+
"// one across paths would bleed the previous route's markup into the next.",
|
|
197
|
+
"export async function renderPath(pathname) {",
|
|
198
|
+
" const router = createRouter({",
|
|
199
|
+
" routeTree,",
|
|
200
|
+
" history: createMemoryHistory({ initialEntries: [pathname] }),",
|
|
201
|
+
" })",
|
|
202
|
+
" await router.load()",
|
|
203
|
+
" return renderToString(<StrictMode><RouterProvider router={router} /></StrictMode>)",
|
|
204
|
+
"}",
|
|
205
|
+
"",
|
|
206
|
+
].join("\n")
|
|
207
|
+
}
|
|
208
|
+
|
|
99
209
|
function done(log: (m: string) => void, r: PrerenderResult): PrerenderResult {
|
|
100
210
|
if (r.status === "baked") {
|
|
101
|
-
|
|
211
|
+
const where = r.routes ? `${r.routes} route(s)` : "dist/index.html"
|
|
212
|
+
log(`prerender: ✓ baked app root into ${where} (+${r.bytes}B static HTML)`)
|
|
102
213
|
} else {
|
|
103
214
|
log(`prerender: SPA shell kept (no prerender) — ${r.reason}`)
|
|
104
215
|
}
|