@jasonshimmy/vite-plugin-cer-app 0.20.3 → 0.20.4
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/CHANGELOG.md +4 -0
- package/commits.txt +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +5 -0
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +0 -11
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/content/index.d.ts +5 -4
- package/dist/plugin/content/index.d.ts.map +1 -1
- package/dist/plugin/content/index.js +9 -11
- package/dist/plugin/content/index.js.map +1 -1
- package/dist/plugin/dev-server.d.ts.map +1 -1
- package/dist/plugin/dev-server.js +40 -2
- package/dist/plugin/dev-server.js.map +1 -1
- package/dist/plugin/dts-generator.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +9 -1
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +7 -0
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/entry-server-template.d.ts +1 -1
- package/dist/runtime/entry-server-template.d.ts.map +1 -1
- package/dist/runtime/entry-server-template.js +8 -2
- package/dist/runtime/entry-server-template.js.map +1 -1
- package/e2e/cypress/e2e/preview-hardening.cy.ts +42 -33
- package/e2e/cypress/e2e/use-page-data.cy.ts +122 -0
- package/e2e/kitchen-sink/app/pages/blog/[slug].ts +4 -0
- package/e2e/kitchen-sink/app/pages/blog/index.ts +5 -0
- package/package.json +5 -2
- package/src/__tests__/plugin/build-ssg.test.ts +2 -2
- package/src/__tests__/plugin/content/loader.test.ts +19 -27
- package/src/__tests__/plugin/entry-server-template.test.ts +4 -1
- package/src/cli/commands/dev.ts +5 -0
- package/src/plugin/build-ssg.ts +0 -12
- package/src/plugin/content/index.ts +8 -11
- package/src/plugin/dev-server.ts +37 -2
- package/src/plugin/dts-generator.ts +7 -1
- package/src/plugin/index.ts +7 -0
- package/src/runtime/entry-server-template.ts +8 -2
|
@@ -24,14 +24,14 @@ export function resolveContentDir(root: string, contentConfig?: CerContentConfig
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Loads all content files from `contentDir`, parses them concurrently, and
|
|
27
|
-
* returns the full `ContentItem[]`. Excludes drafts
|
|
28
|
-
*
|
|
29
|
-
* which is significantly faster than sequential `readFileSync` at
|
|
27
|
+
* returns the full `ContentItem[]`. Excludes drafts unless `includeDrafts: true`
|
|
28
|
+
* is set in the content config. Uses async I/O + `Promise.all` for concurrent
|
|
29
|
+
* disk reads, which is significantly faster than sequential `readFileSync` at
|
|
30
|
+
* 10k+ pages.
|
|
30
31
|
*/
|
|
31
32
|
export async function loadContentStore(
|
|
32
33
|
contentDir: string,
|
|
33
34
|
isDraft: boolean,
|
|
34
|
-
isProduction: boolean,
|
|
35
35
|
): Promise<ContentItem[]> {
|
|
36
36
|
if (!existsSync(contentDir)) return []
|
|
37
37
|
|
|
@@ -41,8 +41,8 @@ export async function loadContentStore(
|
|
|
41
41
|
files.map(async (file) => {
|
|
42
42
|
try {
|
|
43
43
|
const item = await parseContentFileAsync(file, contentDir)
|
|
44
|
-
// Skip drafts
|
|
45
|
-
if (
|
|
44
|
+
// Skip drafts unless the user explicitly opted in via drafts: true
|
|
45
|
+
if (!isDraft && item.draft === true) return null
|
|
46
46
|
return item
|
|
47
47
|
} catch (err) {
|
|
48
48
|
// Warn and skip unparseable / invalid files so one bad file does not
|
|
@@ -175,8 +175,7 @@ export function cerContent(
|
|
|
175
175
|
},
|
|
176
176
|
|
|
177
177
|
async buildStart() {
|
|
178
|
-
const
|
|
179
|
-
const items = await loadContentStore(_resolvedContentDir, includeDrafts, isProduction)
|
|
178
|
+
const items = await loadContentStore(_resolvedContentDir, includeDrafts)
|
|
180
179
|
const g = globalThis as Record<string, unknown>
|
|
181
180
|
g[CONTENT_STORE_KEY] = items
|
|
182
181
|
},
|
|
@@ -225,9 +224,7 @@ export function cerContent(
|
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
async function refreshStore(contentDir: string, includeDrafts: boolean): Promise<void> {
|
|
228
|
-
|
|
229
|
-
// remain visible, matching the initial buildStart behaviour in dev mode.
|
|
230
|
-
const items = await loadContentStore(contentDir, includeDrafts, false)
|
|
227
|
+
const items = await loadContentStore(contentDir, includeDrafts)
|
|
231
228
|
const g = globalThis as Record<string, unknown>
|
|
232
229
|
g[CONTENT_STORE_KEY] = items
|
|
233
230
|
// Invalidate the dev middleware caches so the next request rebuilds manifest
|
package/src/plugin/dev-server.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ViteDevServer } from 'vite'
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
3
5
|
import { join } from 'pathe'
|
|
4
6
|
import { getGeneratedDir } from './generated-dir.js'
|
|
5
7
|
|
|
@@ -246,7 +248,7 @@ export function configureCerDevServer(
|
|
|
246
248
|
const acceptsHtml =
|
|
247
249
|
(req.headers['accept'] ?? '').includes('text/html') ||
|
|
248
250
|
url === '/' ||
|
|
249
|
-
(!url.includes('.') && !url.startsWith('/api/'))
|
|
251
|
+
(!url.includes('.') && !url.startsWith('/api/') && !url.startsWith('/@'))
|
|
250
252
|
|
|
251
253
|
if (acceptsHtml) {
|
|
252
254
|
// Check per-route render mode — skip SSR for 'spa' routes.
|
|
@@ -257,6 +259,20 @@ export function configureCerDevServer(
|
|
|
257
259
|
for (const route of pageRoutes) {
|
|
258
260
|
if (_matchDevRoute(route.path, urlPathOnly)) {
|
|
259
261
|
if (route.meta?.render === 'spa') {
|
|
262
|
+
// In SSR/SSG dev mode, a route with render:'spa' should be served as
|
|
263
|
+
// the SPA shell (no server rendering). Serve .cer/index.html so the
|
|
264
|
+
// client bundle boots and handles the route client-side.
|
|
265
|
+
const _userHtml = resolve(config.root, 'index.html')
|
|
266
|
+
const _cerHtml = join(getGeneratedDir(config.root), 'index.html')
|
|
267
|
+
const _spaSrcPath = existsSync(_userHtml) ? _userHtml : _cerHtml
|
|
268
|
+
if (existsSync(_spaSrcPath)) {
|
|
269
|
+
const rawHtml = readFileSync(_spaSrcPath, 'utf-8')
|
|
270
|
+
const transformed = await server.transformIndexHtml(url, rawHtml)
|
|
271
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
272
|
+
res.statusCode = 200
|
|
273
|
+
res.end(transformed)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
260
276
|
next()
|
|
261
277
|
return
|
|
262
278
|
}
|
|
@@ -276,7 +292,26 @@ export function configureCerDevServer(
|
|
|
276
292
|
ssrEntry.handler ?? ssrEntry.default?.handler
|
|
277
293
|
|
|
278
294
|
if (typeof handler === 'function') {
|
|
279
|
-
|
|
295
|
+
// In dev mode _clientTemplate inside entry-server.ts is null because
|
|
296
|
+
// the dist/client/index.html path doesn't exist. Set the global that
|
|
297
|
+
// the handler reads per-request so the SSR response includes the
|
|
298
|
+
// Vite client scripts (/@vite/client, HMR, module imports for app.ts).
|
|
299
|
+
const _userIndexPath = resolve(config.root, 'index.html')
|
|
300
|
+
const _genIndexPath = join(getGeneratedDir(config.root), 'index.html')
|
|
301
|
+
const _rawHtml = existsSync(_userIndexPath)
|
|
302
|
+
? readFileSync(_userIndexPath, 'utf-8')
|
|
303
|
+
: existsSync(_genIndexPath)
|
|
304
|
+
? readFileSync(_genIndexPath, 'utf-8')
|
|
305
|
+
: null
|
|
306
|
+
if (_rawHtml) {
|
|
307
|
+
;(globalThis as Record<string, unknown>).__CER_CLIENT_TEMPLATE__ =
|
|
308
|
+
await server.transformIndexHtml(url, _rawHtml)
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
await handler(req, res)
|
|
312
|
+
} finally {
|
|
313
|
+
;(globalThis as Record<string, unknown>).__CER_CLIENT_TEMPLATE__ = undefined
|
|
314
|
+
}
|
|
280
315
|
return
|
|
281
316
|
}
|
|
282
317
|
|
|
@@ -40,7 +40,13 @@ export function writeTsconfigPaths(root: string, srcDir: string): void {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const content = JSON.stringify(tsconfig, null, 2) + '\n'
|
|
43
|
-
|
|
43
|
+
const tsconfigPath = join(cerDir, 'tsconfig.json')
|
|
44
|
+
// Skip write when content is unchanged to avoid triggering Vite's tsconfig
|
|
45
|
+
// watcher, which would cause an unnecessary server restart on every dev start.
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(tsconfigPath) && readFileSync(tsconfigPath, 'utf-8') === content) return
|
|
48
|
+
} catch { /* proceed to write */ }
|
|
49
|
+
writeFileSync(tsconfigPath, content, 'utf-8')
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
const RUNTIME_GLOBALS = [
|
package/src/plugin/index.ts
CHANGED
|
@@ -365,6 +365,13 @@ export function cerApp(userConfig: CerAppConfig = {}): Plugin[] {
|
|
|
365
365
|
if (!existsSync(userHtml)) {
|
|
366
366
|
const cerHtmlPath = join(getGeneratedDir(config.root), 'index.html')
|
|
367
367
|
server.middlewares.use(async (req, res, next) => {
|
|
368
|
+
// In SSR/SSG mode, HTML requests must fall through to configureCerDevServer
|
|
369
|
+
// so the SSR handler can run loaders and inject __CER_DATA__ into the response.
|
|
370
|
+
// Only SPA mode (no server rendering) should serve the raw SPA shell here.
|
|
371
|
+
if (config.mode === 'ssr' || config.mode === 'ssg') {
|
|
372
|
+
next()
|
|
373
|
+
return
|
|
374
|
+
}
|
|
368
375
|
const url = (req as { url?: string }).url ?? '/'
|
|
369
376
|
const isHtmlRequest =
|
|
370
377
|
url === '/' ||
|
|
@@ -394,8 +394,14 @@ export const handler = async (req, res) => {
|
|
|
394
394
|
// (loader data script, useHead() tags, JIT styles). No polyfill in body yet.
|
|
395
395
|
const ssrHtml = \`<!DOCTYPE html><html><head>\${headContent}</head><body>\${firstChunk}</body></html>\`
|
|
396
396
|
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
// In dev mode the module-level _clientTemplate is null (only the
|
|
398
|
+
// production dist/client/index.html path is searched at init time).
|
|
399
|
+
// The dev server sets (globalThis).__CER_CLIENT_TEMPLATE__ per-request
|
|
400
|
+
// after running server.transformIndexHtml so the Vite client scripts
|
|
401
|
+
// (/@vite/client, HMR) are included in every SSR response.
|
|
402
|
+
const _resolvedClientTemplate = (globalThis).__CER_CLIENT_TEMPLATE__ ?? _clientTemplate
|
|
403
|
+
const merged = _resolvedClientTemplate
|
|
404
|
+
? _mergeWithClientTemplate(ssrHtml, _resolvedClientTemplate)
|
|
399
405
|
: ssrHtml
|
|
400
406
|
|
|
401
407
|
// Split at </body> so async swap scripts and the DSD polyfill can be streamed
|