@md-plugins/vite-ssg-plugin 0.1.0-rc.2 → 0.1.0-rc.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/README.md +36 -2
- package/dist/index.d.mts +157 -5
- package/dist/index.d.ts +157 -5
- package/dist/index.mjs +276 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,12 +6,16 @@ This package currently focuses on route inventory and static route output:
|
|
|
6
6
|
|
|
7
7
|
- Normalize static route declarations.
|
|
8
8
|
- Discover Q-Press Markdown routes from a `src/markdown` folder.
|
|
9
|
+
- Flatten static Vue Router route records for non-Markdown pages.
|
|
10
|
+
- Exclude routes from manifests or prerender passes.
|
|
9
11
|
- Generate a route manifest during Vite builds.
|
|
10
12
|
- Expose the same manifest through a virtual module.
|
|
11
13
|
- Emit route-specific HTML files from the built app shell so static hosts can serve deep
|
|
12
14
|
links without relying on a SPA fallback rewrite.
|
|
13
15
|
- Accept a custom per-route renderer when a project is ready to generate fully prerendered
|
|
14
16
|
route HTML.
|
|
17
|
+
- Crawl safe internal links, follow redirects, skip 404s, and write generation reports during
|
|
18
|
+
post-build prerendering when those behaviors are enabled.
|
|
15
19
|
|
|
16
20
|
By default, generated route HTML uses the built `index.html` app shell. That makes the output
|
|
17
21
|
usable on Netlify or other static hosts today. Q-Press projects can use `qpress-ssg` for
|
|
@@ -43,6 +47,25 @@ export default {
|
|
|
43
47
|
}
|
|
44
48
|
```
|
|
45
49
|
|
|
50
|
+
To include static Vue Router routes:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { flattenStaticSsgRouterRoutes, viteSsgPlugin } from '@md-plugins/vite-ssg-plugin'
|
|
54
|
+
import routes from './src/router/routes'
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
plugins: [
|
|
58
|
+
viteSsgPlugin({
|
|
59
|
+
markdown: {
|
|
60
|
+
root: './src/markdown',
|
|
61
|
+
},
|
|
62
|
+
routes: flattenStaticSsgRouterRoutes(routes),
|
|
63
|
+
exclude: ['/drafts/private', /^\/admin/],
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
46
69
|
For Q-Press-style Markdown docs:
|
|
47
70
|
|
|
48
71
|
```ts
|
|
@@ -121,6 +144,10 @@ import { prerenderSsgRoutes } from '@md-plugins/vite-ssg-plugin'
|
|
|
121
144
|
|
|
122
145
|
await prerenderSsgRoutes({
|
|
123
146
|
outDir: 'dist/spa',
|
|
147
|
+
concurrency: 4,
|
|
148
|
+
crawlLinks: true,
|
|
149
|
+
redirects: 'follow',
|
|
150
|
+
notFound: 'skip',
|
|
124
151
|
async renderRoute(route, { appHtml }) {
|
|
125
152
|
const renderedAppHtml = await renderMyAppAt(route.path)
|
|
126
153
|
|
|
@@ -133,12 +160,20 @@ The helper reads `q-press-ssg-routes.json`, renders every route, and writes each
|
|
|
133
160
|
`index.html` file. The renderer can be a Vue SSR renderer, a Quasar SSR adapter, or any
|
|
134
161
|
project-specific static renderer.
|
|
135
162
|
|
|
163
|
+
The output directory is configurable. Q-Press defaults to `dist/spa` because that keeps existing
|
|
164
|
+
static-host deployments simple, but non-Q-Press projects can use another output folder.
|
|
165
|
+
|
|
166
|
+
By default, post-build prerendering writes `q-press-ssg-report.json`. Pass `reportFile: false` to
|
|
167
|
+
disable reports, or provide hooks such as `onRouteRendered`, `onPageGenerated`, and `afterGenerate`
|
|
168
|
+
for custom output.
|
|
169
|
+
|
|
136
170
|
## Vue / Quasar Build-Time Rendering
|
|
137
171
|
|
|
138
172
|
For Q-Press apps, run the generated Q-Press command after a normal SPA build:
|
|
139
173
|
|
|
140
174
|
```bash
|
|
141
175
|
pnpm build:ssg
|
|
176
|
+
pnpm preview:ssg
|
|
142
177
|
```
|
|
143
178
|
|
|
144
179
|
Projects that already have a Quasar SSR bundle can opt into that renderer with
|
|
@@ -181,5 +216,4 @@ import ssgRouteManifest, { ssgRoutes } from 'virtual:md-plugins/ssg-routes'
|
|
|
181
216
|
## Next Steps
|
|
182
217
|
|
|
183
218
|
- Add dynamic-route parameter expansion.
|
|
184
|
-
- Define
|
|
185
|
-
- Define how browser-only examples opt out of prerendering.
|
|
219
|
+
- Define project conventions for browser-only examples and lazy client hydration.
|
package/dist/index.d.mts
CHANGED
|
@@ -27,6 +27,7 @@ interface SsgRouteManifest {
|
|
|
27
27
|
routes: SsgRoute[];
|
|
28
28
|
}
|
|
29
29
|
type SsgRouteSource = SsgRouteInput[] | (() => MaybePromise<SsgRouteInput[]>);
|
|
30
|
+
type SsgRouteExclusion = string | RegExp;
|
|
30
31
|
interface MarkdownSsgRoutesOptions {
|
|
31
32
|
/**
|
|
32
33
|
* Directory containing Markdown pages.
|
|
@@ -83,17 +84,95 @@ interface PrerenderSsgRoutesOptions extends SsgRouteHtmlOptions {
|
|
|
83
84
|
* Manifest to use instead of reading one from disk.
|
|
84
85
|
*/
|
|
85
86
|
manifest?: SsgRouteManifest;
|
|
87
|
+
/**
|
|
88
|
+
* Routes to skip while prerendering. String values are matched after route
|
|
89
|
+
* normalization; RegExp values are tested against normalized route paths.
|
|
90
|
+
*/
|
|
91
|
+
exclude?: SsgRouteExclusion[];
|
|
92
|
+
/**
|
|
93
|
+
* Number of routes to prerender at the same time. Defaults to 1.
|
|
94
|
+
*/
|
|
95
|
+
concurrency?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Milliseconds to wait between prerender batches. Defaults to 0.
|
|
98
|
+
*/
|
|
99
|
+
interval?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Crawl rendered HTML for safe internal links and enqueue missing routes.
|
|
102
|
+
* Defaults to false.
|
|
103
|
+
*/
|
|
104
|
+
crawlLinks?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Redirect behavior for renderer errors with a string `url` property.
|
|
107
|
+
* Defaults to error to preserve strict generic behavior.
|
|
108
|
+
*/
|
|
109
|
+
redirects?: 'error' | 'follow' | 'skip';
|
|
110
|
+
/**
|
|
111
|
+
* Not-found behavior for renderer errors with code/status/statusCode 404.
|
|
112
|
+
* Defaults to error to preserve strict generic behavior.
|
|
113
|
+
*/
|
|
114
|
+
notFound?: 'error' | 'skip';
|
|
115
|
+
/**
|
|
116
|
+
* Optional JSON report file written inside outDir. Defaults to
|
|
117
|
+
* q-press-ssg-report.json. Pass false to disable report output.
|
|
118
|
+
*/
|
|
119
|
+
reportFile?: string | false;
|
|
120
|
+
/**
|
|
121
|
+
* Hook after a route is rendered and transformed, before crawling and writing.
|
|
122
|
+
*/
|
|
123
|
+
onRouteRendered?: SsgRouteRenderedHook;
|
|
124
|
+
/**
|
|
125
|
+
* Hook before a generated page is written. Can adjust html and output path.
|
|
126
|
+
*/
|
|
127
|
+
onPageGenerated?: SsgPageGeneratedHook;
|
|
128
|
+
/**
|
|
129
|
+
* Hook after all routes have completed and the report has been assembled.
|
|
130
|
+
*/
|
|
131
|
+
afterGenerate?: SsgAfterGenerateHook;
|
|
86
132
|
}
|
|
87
133
|
interface PrerenderedSsgRoute {
|
|
88
134
|
path: string;
|
|
89
135
|
htmlFile: string;
|
|
90
136
|
bytes: number;
|
|
137
|
+
milliseconds?: number;
|
|
138
|
+
}
|
|
139
|
+
interface SkippedSsgRoute {
|
|
140
|
+
path: string;
|
|
141
|
+
reason: 'excluded' | 'not-found' | 'redirected' | 'skipped-redirect';
|
|
142
|
+
target?: string;
|
|
143
|
+
}
|
|
144
|
+
interface SsgGenerationWarning {
|
|
145
|
+
path: string;
|
|
146
|
+
message: string;
|
|
147
|
+
}
|
|
148
|
+
interface SsgGenerationReport {
|
|
149
|
+
generatedAt: string;
|
|
150
|
+
outDir: string;
|
|
151
|
+
manifestFile: string;
|
|
152
|
+
routeCount: number;
|
|
153
|
+
generated: PrerenderedSsgRoute[];
|
|
154
|
+
skipped: SkippedSsgRoute[];
|
|
155
|
+
warnings: SsgGenerationWarning[];
|
|
91
156
|
}
|
|
92
157
|
interface PrerenderSsgRoutesResult {
|
|
93
158
|
manifest: SsgRouteManifest;
|
|
94
159
|
outDir: string;
|
|
95
160
|
routes: PrerenderedSsgRoute[];
|
|
161
|
+
skipped: SkippedSsgRoute[];
|
|
162
|
+
warnings: SsgGenerationWarning[];
|
|
163
|
+
report?: SsgGenerationReport;
|
|
96
164
|
}
|
|
165
|
+
interface SsgGeneratedPage {
|
|
166
|
+
route: SsgRoute;
|
|
167
|
+
html: string;
|
|
168
|
+
htmlFile: string;
|
|
169
|
+
filePath: string;
|
|
170
|
+
}
|
|
171
|
+
type SsgRouteRenderedHook = (html: string, route: SsgRoute, context: SsgRouteRenderContext) => MaybePromise<string | void>;
|
|
172
|
+
type SsgPageGeneratedHook = (page: SsgGeneratedPage, context: SsgRouteRenderContext) => MaybePromise<Partial<Pick<SsgGeneratedPage, 'html' | 'htmlFile' | 'filePath'>> | void>;
|
|
173
|
+
type SsgAfterGenerateHook = (result: Omit<PrerenderSsgRoutesResult, 'report'> & {
|
|
174
|
+
report: SsgGenerationReport;
|
|
175
|
+
}) => MaybePromise<void>;
|
|
97
176
|
interface VueSsgRouterAdapter {
|
|
98
177
|
push?: (location: unknown) => MaybePromise<unknown>;
|
|
99
178
|
replace?: (location: unknown) => MaybePromise<unknown>;
|
|
@@ -152,6 +231,10 @@ interface ViteSsgPluginOptions {
|
|
|
152
231
|
* Static route declarations or a function that resolves them.
|
|
153
232
|
*/
|
|
154
233
|
routes?: SsgRouteSource;
|
|
234
|
+
/**
|
|
235
|
+
* Routes to exclude from the generated manifest.
|
|
236
|
+
*/
|
|
237
|
+
exclude?: SsgRouteExclusion[];
|
|
155
238
|
/**
|
|
156
239
|
* Optional Markdown route discovery. This can be combined with explicit routes.
|
|
157
240
|
*/
|
|
@@ -185,28 +268,94 @@ interface ViteSsgPluginOptions {
|
|
|
185
268
|
virtualModuleId?: string;
|
|
186
269
|
}
|
|
187
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Escapes JSON so it can be embedded safely inside an HTML `<script>` tag.
|
|
273
|
+
*/
|
|
188
274
|
declare function escapeJsonForHtml(json: string): string;
|
|
275
|
+
/**
|
|
276
|
+
* Serializes an SSG route into the route payload script consumed during hydration.
|
|
277
|
+
*/
|
|
189
278
|
declare function createSsgRoutePayloadScript(route: SsgRoute): string;
|
|
279
|
+
/**
|
|
280
|
+
* Injects the route payload script into an HTML shell without duplicating it.
|
|
281
|
+
*/
|
|
190
282
|
declare function injectSsgRoutePayload(html: string, route: SsgRoute): string;
|
|
283
|
+
/**
|
|
284
|
+
* Creates the default per-route HTML shell for a static SSG route.
|
|
285
|
+
*/
|
|
191
286
|
declare function createSsgRouteHtml(route: SsgRoute, context: SsgRouteRenderContext, { injectRoutePayload }?: {
|
|
192
287
|
injectRoutePayload?: boolean;
|
|
193
288
|
}): string;
|
|
289
|
+
/**
|
|
290
|
+
* Renders one SSG route, optionally delegating to a framework renderer and HTML transform.
|
|
291
|
+
*/
|
|
194
292
|
declare function renderSsgRouteHtml(route: SsgRoute, context: SsgRouteRenderContext, options?: SsgRouteHtmlOptions): Promise<string>;
|
|
195
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Converts a Markdown file path into the route path generated by Q-Press conventions.
|
|
296
|
+
*/
|
|
196
297
|
declare function markdownFileToRoutePath(markdownFile: string, { landingPage }?: Pick<MarkdownSsgRoutesOptions, 'landingPage'>): string;
|
|
298
|
+
/**
|
|
299
|
+
* Discovers Markdown files and converts them into SSG route inputs.
|
|
300
|
+
*/
|
|
197
301
|
declare function discoverMarkdownSsgRoutes({ root, include, exclude, landingPage, }: MarkdownSsgRoutesOptions): SsgRouteInput[];
|
|
198
302
|
|
|
199
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Prerenders every route in an SSG manifest into static HTML files.
|
|
305
|
+
*
|
|
306
|
+
* The renderer can use the default HTML-shell mode, a custom route renderer, or
|
|
307
|
+
* a framework-specific renderer such as the Vue adapter.
|
|
308
|
+
*/
|
|
309
|
+
declare function prerenderSsgRoutes({ outDir, appHtmlFile, manifestFile, manifest, exclude, concurrency, interval, crawlLinks, redirects, notFound, reportFile, onRouteRendered, onPageGenerated, afterGenerate, renderRoute, transformHtml, injectRoutePayload, }: PrerenderSsgRoutesOptions): Promise<PrerenderSsgRoutesResult>;
|
|
200
310
|
|
|
201
311
|
declare const defaultSsgManifestFile = "q-press-ssg-routes.json";
|
|
312
|
+
declare const defaultSsgReportFile = "q-press-ssg-report.json";
|
|
202
313
|
declare const defaultSsgVirtualModuleId = "virtual:md-plugins/ssg-routes";
|
|
314
|
+
interface SsgRouterRouteLike {
|
|
315
|
+
path?: string;
|
|
316
|
+
children?: SsgRouterRouteLike[];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Normalizes a Vite base value for use in generated SSG manifests and links.
|
|
320
|
+
*/
|
|
203
321
|
declare function normalizeSsgBase(base?: string): string;
|
|
322
|
+
/**
|
|
323
|
+
* Normalizes a route path into an absolute path without query, hash, or trailing slash.
|
|
324
|
+
*/
|
|
204
325
|
declare function normalizeSsgRoutePath(path: string): string;
|
|
326
|
+
/**
|
|
327
|
+
* Checks whether a route path can be emitted as a static HTML file.
|
|
328
|
+
*/
|
|
329
|
+
declare function isStaticSsgRoutePath(path: string): boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Converts a route path to the HTML file emitted for that route.
|
|
332
|
+
*/
|
|
205
333
|
declare function routePathToHtmlFile(routePath: string): string;
|
|
334
|
+
/**
|
|
335
|
+
* Converts a route path into a stable id for manifest entries and diagnostics.
|
|
336
|
+
*/
|
|
206
337
|
declare function routePathToId(routePath: string): string;
|
|
338
|
+
/**
|
|
339
|
+
* Returns whether a normalized route path matches one of the configured exclusions.
|
|
340
|
+
*/
|
|
341
|
+
declare function isSsgRouteExcluded(path: string, exclude?: SsgRouteExclusion[]): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Flattens Vue Router-style route records into static SSG route paths.
|
|
344
|
+
*/
|
|
345
|
+
declare function flattenStaticSsgRouterRoutes(routes: SsgRouterRouteLike[], { base, exclude }?: {
|
|
346
|
+
base?: string;
|
|
347
|
+
exclude?: SsgRouteExclusion[];
|
|
348
|
+
}): string[];
|
|
349
|
+
/**
|
|
350
|
+
* Converts a string or object route input into a complete manifest route entry.
|
|
351
|
+
*/
|
|
207
352
|
declare function normalizeSsgRoute(input: SsgRouteInput): SsgRoute;
|
|
208
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Builds and validates an SSG route manifest from route inputs.
|
|
355
|
+
*/
|
|
356
|
+
declare function createSsgRouteManifest(routeInputs: SsgRouteInput[], { base, exclude }?: {
|
|
209
357
|
base?: string;
|
|
358
|
+
exclude?: SsgRouteExclusion[];
|
|
210
359
|
}): SsgRouteManifest;
|
|
211
360
|
|
|
212
361
|
/**
|
|
@@ -214,11 +363,14 @@ declare function createSsgRouteManifest(routeInputs: SsgRouteInput[], { base }?:
|
|
|
214
363
|
*/
|
|
215
364
|
declare function createVueSsgRouteRenderer(options: VueSsgRouteRendererOptions): SsgRouteRenderer;
|
|
216
365
|
/**
|
|
217
|
-
*
|
|
366
|
+
* Prerenders Vite/Quasar routes with a Vue app factory in one call.
|
|
218
367
|
*/
|
|
219
368
|
declare function prerenderVueSsgRoutes(options: PrerenderVueSsgRoutesOptions): Promise<PrerenderSsgRoutesResult>;
|
|
220
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Creates the Vite plugin that emits SSG route manifests and optional route HTML shells.
|
|
372
|
+
*/
|
|
221
373
|
declare function viteSsgPlugin(options?: ViteSsgPluginOptions): Plugin;
|
|
222
374
|
|
|
223
|
-
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, injectSsgRoutePayload, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|
|
224
|
-
export type { JsonPrimitive, JsonValue, MarkdownSsgRoutesOptions, MaybePromise, PrerenderSsgRoutesOptions, PrerenderSsgRoutesResult, PrerenderVueSsgRoutesOptions, PrerenderedSsgRoute, SsgRoute, SsgRouteHtmlOptions, SsgRouteHtmlTransformer, SsgRouteInput, SsgRouteManifest, SsgRouteMeta, SsgRouteObject, SsgRouteParams, SsgRouteRenderContext, SsgRouteRenderer, SsgRouteSource, ViteSsgPluginOptions, VueSsgAppFactory, VueSsgAppFactoryResult, VueSsgAppHtmlReplacer, VueSsgRenderToString, VueSsgRenderedAppHtmlTransformer, VueSsgRouteLocationResolver, VueSsgRouteRendererOptions, VueSsgRouterAdapter };
|
|
375
|
+
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgReportFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, flattenStaticSsgRouterRoutes, injectSsgRoutePayload, isSsgRouteExcluded, isStaticSsgRoutePath, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|
|
376
|
+
export type { JsonPrimitive, JsonValue, MarkdownSsgRoutesOptions, MaybePromise, PrerenderSsgRoutesOptions, PrerenderSsgRoutesResult, PrerenderVueSsgRoutesOptions, PrerenderedSsgRoute, SkippedSsgRoute, SsgAfterGenerateHook, SsgGeneratedPage, SsgGenerationReport, SsgGenerationWarning, SsgPageGeneratedHook, SsgRoute, SsgRouteExclusion, SsgRouteHtmlOptions, SsgRouteHtmlTransformer, SsgRouteInput, SsgRouteManifest, SsgRouteMeta, SsgRouteObject, SsgRouteParams, SsgRouteRenderContext, SsgRouteRenderedHook, SsgRouteRenderer, SsgRouteSource, SsgRouterRouteLike, ViteSsgPluginOptions, VueSsgAppFactory, VueSsgAppFactoryResult, VueSsgAppHtmlReplacer, VueSsgRenderToString, VueSsgRenderedAppHtmlTransformer, VueSsgRouteLocationResolver, VueSsgRouteRendererOptions, VueSsgRouterAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ interface SsgRouteManifest {
|
|
|
27
27
|
routes: SsgRoute[];
|
|
28
28
|
}
|
|
29
29
|
type SsgRouteSource = SsgRouteInput[] | (() => MaybePromise<SsgRouteInput[]>);
|
|
30
|
+
type SsgRouteExclusion = string | RegExp;
|
|
30
31
|
interface MarkdownSsgRoutesOptions {
|
|
31
32
|
/**
|
|
32
33
|
* Directory containing Markdown pages.
|
|
@@ -83,17 +84,95 @@ interface PrerenderSsgRoutesOptions extends SsgRouteHtmlOptions {
|
|
|
83
84
|
* Manifest to use instead of reading one from disk.
|
|
84
85
|
*/
|
|
85
86
|
manifest?: SsgRouteManifest;
|
|
87
|
+
/**
|
|
88
|
+
* Routes to skip while prerendering. String values are matched after route
|
|
89
|
+
* normalization; RegExp values are tested against normalized route paths.
|
|
90
|
+
*/
|
|
91
|
+
exclude?: SsgRouteExclusion[];
|
|
92
|
+
/**
|
|
93
|
+
* Number of routes to prerender at the same time. Defaults to 1.
|
|
94
|
+
*/
|
|
95
|
+
concurrency?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Milliseconds to wait between prerender batches. Defaults to 0.
|
|
98
|
+
*/
|
|
99
|
+
interval?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Crawl rendered HTML for safe internal links and enqueue missing routes.
|
|
102
|
+
* Defaults to false.
|
|
103
|
+
*/
|
|
104
|
+
crawlLinks?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Redirect behavior for renderer errors with a string `url` property.
|
|
107
|
+
* Defaults to error to preserve strict generic behavior.
|
|
108
|
+
*/
|
|
109
|
+
redirects?: 'error' | 'follow' | 'skip';
|
|
110
|
+
/**
|
|
111
|
+
* Not-found behavior for renderer errors with code/status/statusCode 404.
|
|
112
|
+
* Defaults to error to preserve strict generic behavior.
|
|
113
|
+
*/
|
|
114
|
+
notFound?: 'error' | 'skip';
|
|
115
|
+
/**
|
|
116
|
+
* Optional JSON report file written inside outDir. Defaults to
|
|
117
|
+
* q-press-ssg-report.json. Pass false to disable report output.
|
|
118
|
+
*/
|
|
119
|
+
reportFile?: string | false;
|
|
120
|
+
/**
|
|
121
|
+
* Hook after a route is rendered and transformed, before crawling and writing.
|
|
122
|
+
*/
|
|
123
|
+
onRouteRendered?: SsgRouteRenderedHook;
|
|
124
|
+
/**
|
|
125
|
+
* Hook before a generated page is written. Can adjust html and output path.
|
|
126
|
+
*/
|
|
127
|
+
onPageGenerated?: SsgPageGeneratedHook;
|
|
128
|
+
/**
|
|
129
|
+
* Hook after all routes have completed and the report has been assembled.
|
|
130
|
+
*/
|
|
131
|
+
afterGenerate?: SsgAfterGenerateHook;
|
|
86
132
|
}
|
|
87
133
|
interface PrerenderedSsgRoute {
|
|
88
134
|
path: string;
|
|
89
135
|
htmlFile: string;
|
|
90
136
|
bytes: number;
|
|
137
|
+
milliseconds?: number;
|
|
138
|
+
}
|
|
139
|
+
interface SkippedSsgRoute {
|
|
140
|
+
path: string;
|
|
141
|
+
reason: 'excluded' | 'not-found' | 'redirected' | 'skipped-redirect';
|
|
142
|
+
target?: string;
|
|
143
|
+
}
|
|
144
|
+
interface SsgGenerationWarning {
|
|
145
|
+
path: string;
|
|
146
|
+
message: string;
|
|
147
|
+
}
|
|
148
|
+
interface SsgGenerationReport {
|
|
149
|
+
generatedAt: string;
|
|
150
|
+
outDir: string;
|
|
151
|
+
manifestFile: string;
|
|
152
|
+
routeCount: number;
|
|
153
|
+
generated: PrerenderedSsgRoute[];
|
|
154
|
+
skipped: SkippedSsgRoute[];
|
|
155
|
+
warnings: SsgGenerationWarning[];
|
|
91
156
|
}
|
|
92
157
|
interface PrerenderSsgRoutesResult {
|
|
93
158
|
manifest: SsgRouteManifest;
|
|
94
159
|
outDir: string;
|
|
95
160
|
routes: PrerenderedSsgRoute[];
|
|
161
|
+
skipped: SkippedSsgRoute[];
|
|
162
|
+
warnings: SsgGenerationWarning[];
|
|
163
|
+
report?: SsgGenerationReport;
|
|
96
164
|
}
|
|
165
|
+
interface SsgGeneratedPage {
|
|
166
|
+
route: SsgRoute;
|
|
167
|
+
html: string;
|
|
168
|
+
htmlFile: string;
|
|
169
|
+
filePath: string;
|
|
170
|
+
}
|
|
171
|
+
type SsgRouteRenderedHook = (html: string, route: SsgRoute, context: SsgRouteRenderContext) => MaybePromise<string | void>;
|
|
172
|
+
type SsgPageGeneratedHook = (page: SsgGeneratedPage, context: SsgRouteRenderContext) => MaybePromise<Partial<Pick<SsgGeneratedPage, 'html' | 'htmlFile' | 'filePath'>> | void>;
|
|
173
|
+
type SsgAfterGenerateHook = (result: Omit<PrerenderSsgRoutesResult, 'report'> & {
|
|
174
|
+
report: SsgGenerationReport;
|
|
175
|
+
}) => MaybePromise<void>;
|
|
97
176
|
interface VueSsgRouterAdapter {
|
|
98
177
|
push?: (location: unknown) => MaybePromise<unknown>;
|
|
99
178
|
replace?: (location: unknown) => MaybePromise<unknown>;
|
|
@@ -152,6 +231,10 @@ interface ViteSsgPluginOptions {
|
|
|
152
231
|
* Static route declarations or a function that resolves them.
|
|
153
232
|
*/
|
|
154
233
|
routes?: SsgRouteSource;
|
|
234
|
+
/**
|
|
235
|
+
* Routes to exclude from the generated manifest.
|
|
236
|
+
*/
|
|
237
|
+
exclude?: SsgRouteExclusion[];
|
|
155
238
|
/**
|
|
156
239
|
* Optional Markdown route discovery. This can be combined with explicit routes.
|
|
157
240
|
*/
|
|
@@ -185,28 +268,94 @@ interface ViteSsgPluginOptions {
|
|
|
185
268
|
virtualModuleId?: string;
|
|
186
269
|
}
|
|
187
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Escapes JSON so it can be embedded safely inside an HTML `<script>` tag.
|
|
273
|
+
*/
|
|
188
274
|
declare function escapeJsonForHtml(json: string): string;
|
|
275
|
+
/**
|
|
276
|
+
* Serializes an SSG route into the route payload script consumed during hydration.
|
|
277
|
+
*/
|
|
189
278
|
declare function createSsgRoutePayloadScript(route: SsgRoute): string;
|
|
279
|
+
/**
|
|
280
|
+
* Injects the route payload script into an HTML shell without duplicating it.
|
|
281
|
+
*/
|
|
190
282
|
declare function injectSsgRoutePayload(html: string, route: SsgRoute): string;
|
|
283
|
+
/**
|
|
284
|
+
* Creates the default per-route HTML shell for a static SSG route.
|
|
285
|
+
*/
|
|
191
286
|
declare function createSsgRouteHtml(route: SsgRoute, context: SsgRouteRenderContext, { injectRoutePayload }?: {
|
|
192
287
|
injectRoutePayload?: boolean;
|
|
193
288
|
}): string;
|
|
289
|
+
/**
|
|
290
|
+
* Renders one SSG route, optionally delegating to a framework renderer and HTML transform.
|
|
291
|
+
*/
|
|
194
292
|
declare function renderSsgRouteHtml(route: SsgRoute, context: SsgRouteRenderContext, options?: SsgRouteHtmlOptions): Promise<string>;
|
|
195
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Converts a Markdown file path into the route path generated by Q-Press conventions.
|
|
296
|
+
*/
|
|
196
297
|
declare function markdownFileToRoutePath(markdownFile: string, { landingPage }?: Pick<MarkdownSsgRoutesOptions, 'landingPage'>): string;
|
|
298
|
+
/**
|
|
299
|
+
* Discovers Markdown files and converts them into SSG route inputs.
|
|
300
|
+
*/
|
|
197
301
|
declare function discoverMarkdownSsgRoutes({ root, include, exclude, landingPage, }: MarkdownSsgRoutesOptions): SsgRouteInput[];
|
|
198
302
|
|
|
199
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Prerenders every route in an SSG manifest into static HTML files.
|
|
305
|
+
*
|
|
306
|
+
* The renderer can use the default HTML-shell mode, a custom route renderer, or
|
|
307
|
+
* a framework-specific renderer such as the Vue adapter.
|
|
308
|
+
*/
|
|
309
|
+
declare function prerenderSsgRoutes({ outDir, appHtmlFile, manifestFile, manifest, exclude, concurrency, interval, crawlLinks, redirects, notFound, reportFile, onRouteRendered, onPageGenerated, afterGenerate, renderRoute, transformHtml, injectRoutePayload, }: PrerenderSsgRoutesOptions): Promise<PrerenderSsgRoutesResult>;
|
|
200
310
|
|
|
201
311
|
declare const defaultSsgManifestFile = "q-press-ssg-routes.json";
|
|
312
|
+
declare const defaultSsgReportFile = "q-press-ssg-report.json";
|
|
202
313
|
declare const defaultSsgVirtualModuleId = "virtual:md-plugins/ssg-routes";
|
|
314
|
+
interface SsgRouterRouteLike {
|
|
315
|
+
path?: string;
|
|
316
|
+
children?: SsgRouterRouteLike[];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Normalizes a Vite base value for use in generated SSG manifests and links.
|
|
320
|
+
*/
|
|
203
321
|
declare function normalizeSsgBase(base?: string): string;
|
|
322
|
+
/**
|
|
323
|
+
* Normalizes a route path into an absolute path without query, hash, or trailing slash.
|
|
324
|
+
*/
|
|
204
325
|
declare function normalizeSsgRoutePath(path: string): string;
|
|
326
|
+
/**
|
|
327
|
+
* Checks whether a route path can be emitted as a static HTML file.
|
|
328
|
+
*/
|
|
329
|
+
declare function isStaticSsgRoutePath(path: string): boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Converts a route path to the HTML file emitted for that route.
|
|
332
|
+
*/
|
|
205
333
|
declare function routePathToHtmlFile(routePath: string): string;
|
|
334
|
+
/**
|
|
335
|
+
* Converts a route path into a stable id for manifest entries and diagnostics.
|
|
336
|
+
*/
|
|
206
337
|
declare function routePathToId(routePath: string): string;
|
|
338
|
+
/**
|
|
339
|
+
* Returns whether a normalized route path matches one of the configured exclusions.
|
|
340
|
+
*/
|
|
341
|
+
declare function isSsgRouteExcluded(path: string, exclude?: SsgRouteExclusion[]): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Flattens Vue Router-style route records into static SSG route paths.
|
|
344
|
+
*/
|
|
345
|
+
declare function flattenStaticSsgRouterRoutes(routes: SsgRouterRouteLike[], { base, exclude }?: {
|
|
346
|
+
base?: string;
|
|
347
|
+
exclude?: SsgRouteExclusion[];
|
|
348
|
+
}): string[];
|
|
349
|
+
/**
|
|
350
|
+
* Converts a string or object route input into a complete manifest route entry.
|
|
351
|
+
*/
|
|
207
352
|
declare function normalizeSsgRoute(input: SsgRouteInput): SsgRoute;
|
|
208
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Builds and validates an SSG route manifest from route inputs.
|
|
355
|
+
*/
|
|
356
|
+
declare function createSsgRouteManifest(routeInputs: SsgRouteInput[], { base, exclude }?: {
|
|
209
357
|
base?: string;
|
|
358
|
+
exclude?: SsgRouteExclusion[];
|
|
210
359
|
}): SsgRouteManifest;
|
|
211
360
|
|
|
212
361
|
/**
|
|
@@ -214,11 +363,14 @@ declare function createSsgRouteManifest(routeInputs: SsgRouteInput[], { base }?:
|
|
|
214
363
|
*/
|
|
215
364
|
declare function createVueSsgRouteRenderer(options: VueSsgRouteRendererOptions): SsgRouteRenderer;
|
|
216
365
|
/**
|
|
217
|
-
*
|
|
366
|
+
* Prerenders Vite/Quasar routes with a Vue app factory in one call.
|
|
218
367
|
*/
|
|
219
368
|
declare function prerenderVueSsgRoutes(options: PrerenderVueSsgRoutesOptions): Promise<PrerenderSsgRoutesResult>;
|
|
220
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Creates the Vite plugin that emits SSG route manifests and optional route HTML shells.
|
|
372
|
+
*/
|
|
221
373
|
declare function viteSsgPlugin(options?: ViteSsgPluginOptions): Plugin;
|
|
222
374
|
|
|
223
|
-
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, injectSsgRoutePayload, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|
|
224
|
-
export type { JsonPrimitive, JsonValue, MarkdownSsgRoutesOptions, MaybePromise, PrerenderSsgRoutesOptions, PrerenderSsgRoutesResult, PrerenderVueSsgRoutesOptions, PrerenderedSsgRoute, SsgRoute, SsgRouteHtmlOptions, SsgRouteHtmlTransformer, SsgRouteInput, SsgRouteManifest, SsgRouteMeta, SsgRouteObject, SsgRouteParams, SsgRouteRenderContext, SsgRouteRenderer, SsgRouteSource, ViteSsgPluginOptions, VueSsgAppFactory, VueSsgAppFactoryResult, VueSsgAppHtmlReplacer, VueSsgRenderToString, VueSsgRenderedAppHtmlTransformer, VueSsgRouteLocationResolver, VueSsgRouteRendererOptions, VueSsgRouterAdapter };
|
|
375
|
+
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgReportFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, flattenStaticSsgRouterRoutes, injectSsgRoutePayload, isSsgRouteExcluded, isStaticSsgRoutePath, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|
|
376
|
+
export type { JsonPrimitive, JsonValue, MarkdownSsgRoutesOptions, MaybePromise, PrerenderSsgRoutesOptions, PrerenderSsgRoutesResult, PrerenderVueSsgRoutesOptions, PrerenderedSsgRoute, SkippedSsgRoute, SsgAfterGenerateHook, SsgGeneratedPage, SsgGenerationReport, SsgGenerationWarning, SsgPageGeneratedHook, SsgRoute, SsgRouteExclusion, SsgRouteHtmlOptions, SsgRouteHtmlTransformer, SsgRouteInput, SsgRouteManifest, SsgRouteMeta, SsgRouteObject, SsgRouteParams, SsgRouteRenderContext, SsgRouteRenderedHook, SsgRouteRenderer, SsgRouteSource, SsgRouterRouteLike, ViteSsgPluginOptions, VueSsgAppFactory, VueSsgAppFactoryResult, VueSsgAppHtmlReplacer, VueSsgRenderToString, VueSsgRenderedAppHtmlTransformer, VueSsgRouteLocationResolver, VueSsgRouteRendererOptions, VueSsgRouterAdapter };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { globSync } from 'tinyglobby';
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
|
-
import { readFile,
|
|
4
|
-
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
|
|
4
|
+
import { resolve, join, dirname, posix } from 'node:path';
|
|
5
5
|
|
|
6
6
|
function escapeJsonForHtml(json) {
|
|
7
7
|
return json.replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
@@ -77,6 +77,7 @@ function discoverMarkdownSsgRoutes({
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const defaultSsgManifestFile = "q-press-ssg-routes.json";
|
|
80
|
+
const defaultSsgReportFile = "q-press-ssg-report.json";
|
|
80
81
|
const defaultSsgVirtualModuleId = "virtual:md-plugins/ssg-routes";
|
|
81
82
|
function normalizeSsgBase(base = "/") {
|
|
82
83
|
const trimmed = base.trim();
|
|
@@ -107,6 +108,10 @@ function normalizeSsgRoutePath(path) {
|
|
|
107
108
|
}
|
|
108
109
|
return compacted.replace(/\/+$/, "");
|
|
109
110
|
}
|
|
111
|
+
function isStaticSsgRoutePath(path) {
|
|
112
|
+
const normalized = normalizeSsgRoutePath(path);
|
|
113
|
+
return normalized.startsWith("/") && !normalized.startsWith("//") && !normalized.includes(":") && !normalized.includes("*") && !/\.[a-z0-9]+$/i.test(normalized);
|
|
114
|
+
}
|
|
110
115
|
function routePathToHtmlFile(routePath) {
|
|
111
116
|
const normalized = normalizeSsgRoutePath(routePath);
|
|
112
117
|
if (normalized === "/") {
|
|
@@ -121,6 +126,37 @@ function routePathToId(routePath) {
|
|
|
121
126
|
}
|
|
122
127
|
return normalized.slice(1).replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
123
128
|
}
|
|
129
|
+
function isSsgRouteExcluded(path, exclude = []) {
|
|
130
|
+
const normalized = normalizeSsgRoutePath(path);
|
|
131
|
+
return exclude.some(
|
|
132
|
+
(pattern) => typeof pattern === "string" ? normalizeSsgRoutePath(pattern) === normalized : pattern.test(normalized)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function joinRoutePaths(parentPath, childPath) {
|
|
136
|
+
if (!childPath || childPath === "/") {
|
|
137
|
+
return parentPath || "/";
|
|
138
|
+
}
|
|
139
|
+
if (childPath.startsWith("/")) {
|
|
140
|
+
return childPath;
|
|
141
|
+
}
|
|
142
|
+
return `${parentPath.replace(/\/+$/, "")}/${childPath}` || "/";
|
|
143
|
+
}
|
|
144
|
+
function flattenStaticSsgRouterRoutes(routes, { base = "", exclude = [] } = {}) {
|
|
145
|
+
const routePaths = [];
|
|
146
|
+
function visit(route, parentPath) {
|
|
147
|
+
const path = joinRoutePaths(parentPath, route.path ?? "");
|
|
148
|
+
if (isStaticSsgRoutePath(path) && !isSsgRouteExcluded(path, exclude)) {
|
|
149
|
+
routePaths.push(path);
|
|
150
|
+
}
|
|
151
|
+
for (const child of route.children ?? []) {
|
|
152
|
+
visit(child, path);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const route of routes) {
|
|
156
|
+
visit(route, base);
|
|
157
|
+
}
|
|
158
|
+
return Array.from(new Set(routePaths.map(normalizeSsgRoutePath)));
|
|
159
|
+
}
|
|
124
160
|
function normalizeSsgRoute(input) {
|
|
125
161
|
const route = typeof input === "string" ? {
|
|
126
162
|
path: input,
|
|
@@ -139,8 +175,8 @@ function normalizeSsgRoute(input) {
|
|
|
139
175
|
...Object.hasOwn(route, "data") ? { data: route.data } : {}
|
|
140
176
|
};
|
|
141
177
|
}
|
|
142
|
-
function createSsgRouteManifest(routeInputs, { base = "/" } = {}) {
|
|
143
|
-
const routes = routeInputs.map(normalizeSsgRoute);
|
|
178
|
+
function createSsgRouteManifest(routeInputs, { base = "/", exclude = [] } = {}) {
|
|
179
|
+
const routes = routeInputs.map(normalizeSsgRoute).filter((route) => !isSsgRouteExcluded(route.path, exclude));
|
|
144
180
|
const routePaths = /* @__PURE__ */ new Set();
|
|
145
181
|
for (const route of routes) {
|
|
146
182
|
if (routePaths.has(route.path)) {
|
|
@@ -192,51 +228,263 @@ async function injectMissingCssAssets(appHtml, outDir, base) {
|
|
|
192
228
|
`;
|
|
193
229
|
return appHtml.includes("</head>") ? appHtml.replace("</head>", `${content}</head>`) : `${content}${appHtml}`;
|
|
194
230
|
}
|
|
231
|
+
function clampConcurrency(concurrency) {
|
|
232
|
+
return Math.max(1, Math.floor(concurrency ?? 1));
|
|
233
|
+
}
|
|
234
|
+
function clampInterval(interval) {
|
|
235
|
+
return Math.max(0, Math.floor(interval ?? 0));
|
|
236
|
+
}
|
|
237
|
+
function wait(milliseconds) {
|
|
238
|
+
return new Promise((resolvePromise) => {
|
|
239
|
+
setTimeout(resolvePromise, milliseconds);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function isRecord(value) {
|
|
243
|
+
return typeof value === "object" && value !== null;
|
|
244
|
+
}
|
|
245
|
+
function getRedirectTarget(error) {
|
|
246
|
+
if (!isRecord(error)) {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
return typeof error.url === "string" ? error.url : void 0;
|
|
250
|
+
}
|
|
251
|
+
function isNotFoundError(error) {
|
|
252
|
+
if (!isRecord(error)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return error.code === 404 || error.status === 404 || error.statusCode === 404;
|
|
256
|
+
}
|
|
257
|
+
function extractAnchorHrefs(html) {
|
|
258
|
+
const hrefs = [];
|
|
259
|
+
const anchorHrefRE = /<a\b[^>]*\bhref\s*=\s*(["'])(.*?)\1/gi;
|
|
260
|
+
let match;
|
|
261
|
+
while (match = anchorHrefRE.exec(html)) {
|
|
262
|
+
hrefs.push(match[2]);
|
|
263
|
+
}
|
|
264
|
+
return hrefs;
|
|
265
|
+
}
|
|
266
|
+
function isExternalHref(href) {
|
|
267
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//");
|
|
268
|
+
}
|
|
269
|
+
function stripBaseFromPath(path, base) {
|
|
270
|
+
if (base === "/" || base === "./" || base.startsWith("http://") || base.startsWith("https://")) {
|
|
271
|
+
return path;
|
|
272
|
+
}
|
|
273
|
+
const normalizedBase = normalizeSsgRoutePath(base);
|
|
274
|
+
if (path === normalizedBase) {
|
|
275
|
+
return "/";
|
|
276
|
+
}
|
|
277
|
+
return path.startsWith(`${normalizedBase}/`) ? path.slice(normalizedBase.length) : path;
|
|
278
|
+
}
|
|
279
|
+
function resolveHrefPath(hrefPath, fromRoutePath) {
|
|
280
|
+
if (hrefPath.startsWith("/")) {
|
|
281
|
+
return hrefPath;
|
|
282
|
+
}
|
|
283
|
+
const routeBase = fromRoutePath === "/" ? "/" : posix.dirname(fromRoutePath);
|
|
284
|
+
return posix.normalize(posix.join(routeBase, hrefPath));
|
|
285
|
+
}
|
|
286
|
+
function hrefToSsgRoutePath(href, base, fromRoutePath = "/") {
|
|
287
|
+
const trimmed = href.trim();
|
|
288
|
+
if (!trimmed || trimmed.startsWith("#") || isExternalHref(trimmed)) {
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
const withoutHash = trimmed.split("#")[0] ?? "";
|
|
292
|
+
const withoutQuery = withoutHash.split("?")[0] ?? "";
|
|
293
|
+
if (!withoutQuery || withoutQuery.startsWith(".")) {
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const routePath = stripBaseFromPath(resolveHrefPath(withoutQuery, fromRoutePath), base);
|
|
298
|
+
return isStaticSsgRoutePath(routePath) ? normalizeSsgRoutePath(routePath) : void 0;
|
|
299
|
+
} catch {
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function extractSsgRouteLinks(html, base, fromRoutePath) {
|
|
304
|
+
return Array.from(
|
|
305
|
+
new Set(
|
|
306
|
+
extractAnchorHrefs(html).map((href) => hrefToSsgRoutePath(href, base, fromRoutePath)).filter((path) => path !== void 0)
|
|
307
|
+
)
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
function createReport({
|
|
311
|
+
generated,
|
|
312
|
+
manifest,
|
|
313
|
+
manifestFile,
|
|
314
|
+
outDir,
|
|
315
|
+
skipped,
|
|
316
|
+
warnings
|
|
317
|
+
}) {
|
|
318
|
+
return {
|
|
319
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
320
|
+
outDir,
|
|
321
|
+
manifestFile,
|
|
322
|
+
routeCount: manifest.routes.length,
|
|
323
|
+
generated,
|
|
324
|
+
skipped,
|
|
325
|
+
warnings
|
|
326
|
+
};
|
|
327
|
+
}
|
|
195
328
|
async function prerenderSsgRoutes({
|
|
196
329
|
outDir,
|
|
197
330
|
appHtmlFile = "index.html",
|
|
198
331
|
manifestFile = defaultSsgManifestFile,
|
|
199
332
|
manifest,
|
|
333
|
+
exclude,
|
|
334
|
+
concurrency,
|
|
335
|
+
interval,
|
|
336
|
+
crawlLinks = false,
|
|
337
|
+
redirects = "error",
|
|
338
|
+
notFound = "error",
|
|
339
|
+
reportFile = defaultSsgReportFile,
|
|
340
|
+
onRouteRendered,
|
|
341
|
+
onPageGenerated,
|
|
342
|
+
afterGenerate,
|
|
200
343
|
renderRoute,
|
|
201
344
|
transformHtml,
|
|
202
345
|
injectRoutePayload
|
|
203
346
|
}) {
|
|
204
347
|
const resolvedOutDir = resolve(outDir);
|
|
205
|
-
const
|
|
348
|
+
const rawManifest = manifest ?? await readSsgRouteManifest(resolvedOutDir, manifestFile);
|
|
349
|
+
const resolvedManifest = createSsgRouteManifest(rawManifest.routes, {
|
|
350
|
+
base: rawManifest.base,
|
|
351
|
+
exclude
|
|
352
|
+
});
|
|
206
353
|
const appHtml = await injectMissingCssAssets(
|
|
207
354
|
await readFile(join(resolvedOutDir, appHtmlFile), "utf8"),
|
|
208
355
|
resolvedOutDir,
|
|
209
356
|
resolvedManifest.base
|
|
210
357
|
);
|
|
211
358
|
const routes = [];
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
359
|
+
const skipped = rawManifest.routes.filter((route) => isSsgRouteExcluded(route.path, exclude ?? [])).map((route) => ({
|
|
360
|
+
path: route.path,
|
|
361
|
+
reason: "excluded"
|
|
362
|
+
}));
|
|
363
|
+
const warnings = [];
|
|
364
|
+
const queue = [...resolvedManifest.routes];
|
|
365
|
+
const enqueuedPaths = new Set(queue.map((route) => route.path));
|
|
366
|
+
const maxConcurrency = clampConcurrency(concurrency);
|
|
367
|
+
const batchInterval = clampInterval(interval);
|
|
368
|
+
function enqueueRoute(routeInput) {
|
|
369
|
+
const route = normalizeSsgRoute(routeInput);
|
|
370
|
+
if (isSsgRouteExcluded(route.path, exclude ?? []) || enqueuedPaths.has(route.path)) {
|
|
371
|
+
return void 0;
|
|
372
|
+
}
|
|
373
|
+
enqueuedPaths.add(route.path);
|
|
374
|
+
resolvedManifest.routes.push(route);
|
|
375
|
+
queue.push(route);
|
|
376
|
+
return route;
|
|
377
|
+
}
|
|
378
|
+
async function renderOne(route) {
|
|
379
|
+
const start = performance.now();
|
|
380
|
+
const routeIndex = resolvedManifest.routes.findIndex((entry) => entry.path === route.path);
|
|
381
|
+
const context = {
|
|
382
|
+
appHtml,
|
|
383
|
+
manifest: resolvedManifest,
|
|
384
|
+
routeIndex
|
|
385
|
+
};
|
|
386
|
+
try {
|
|
387
|
+
let html = await renderSsgRouteHtml(route, context, {
|
|
221
388
|
renderRoute,
|
|
222
389
|
transformHtml,
|
|
223
390
|
injectRoutePayload
|
|
391
|
+
});
|
|
392
|
+
const renderedHtml = await onRouteRendered?.(html, route, context);
|
|
393
|
+
if (typeof renderedHtml === "string") {
|
|
394
|
+
html = renderedHtml;
|
|
224
395
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
396
|
+
if (crawlLinks) {
|
|
397
|
+
for (const linkedRoute of extractSsgRouteLinks(html, resolvedManifest.base, route.path)) {
|
|
398
|
+
enqueueRoute(linkedRoute);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const htmlPath = join(resolvedOutDir, route.htmlFile);
|
|
402
|
+
const page = {
|
|
403
|
+
route,
|
|
404
|
+
html,
|
|
405
|
+
htmlFile: route.htmlFile,
|
|
406
|
+
filePath: htmlPath
|
|
407
|
+
};
|
|
408
|
+
const pageUpdate = await onPageGenerated?.(page, context);
|
|
409
|
+
const finalHtml = pageUpdate?.html ?? page.html;
|
|
410
|
+
const finalHtmlFile = pageUpdate?.htmlFile ?? page.htmlFile;
|
|
411
|
+
const finalFilePath = pageUpdate?.filePath ?? join(resolvedOutDir, finalHtmlFile);
|
|
412
|
+
await mkdir(dirname(finalFilePath), { recursive: true });
|
|
413
|
+
await writeFile(finalFilePath, finalHtml);
|
|
414
|
+
routes.push({
|
|
415
|
+
path: route.path,
|
|
416
|
+
htmlFile: finalHtmlFile,
|
|
417
|
+
bytes: Buffer.byteLength(finalHtml),
|
|
418
|
+
milliseconds: Math.round(performance.now() - start)
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
const redirectTarget = getRedirectTarget(error);
|
|
422
|
+
if (redirectTarget !== void 0) {
|
|
423
|
+
if (redirects === "follow") {
|
|
424
|
+
const target = hrefToSsgRoutePath(redirectTarget, resolvedManifest.base, route.path);
|
|
425
|
+
if (target) {
|
|
426
|
+
enqueueRoute(target);
|
|
427
|
+
}
|
|
428
|
+
skipped.push({
|
|
429
|
+
path: route.path,
|
|
430
|
+
reason: "redirected",
|
|
431
|
+
target: target ?? redirectTarget
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (redirects === "skip") {
|
|
436
|
+
skipped.push({
|
|
437
|
+
path: route.path,
|
|
438
|
+
reason: "skipped-redirect",
|
|
439
|
+
target: redirectTarget
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (isNotFoundError(error) && notFound === "skip") {
|
|
445
|
+
skipped.push({
|
|
446
|
+
path: route.path,
|
|
447
|
+
reason: "not-found"
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
234
453
|
}
|
|
235
|
-
|
|
454
|
+
while (queue.length > 0) {
|
|
455
|
+
const batch = queue.splice(0, maxConcurrency);
|
|
456
|
+
await Promise.all(batch.map((route) => renderOne(route)));
|
|
457
|
+
if (queue.length > 0 && batchInterval > 0) {
|
|
458
|
+
await wait(batchInterval);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
await writeFile(
|
|
462
|
+
join(resolvedOutDir, manifestFile),
|
|
463
|
+
`${JSON.stringify(resolvedManifest, null, 2)}
|
|
464
|
+
`
|
|
465
|
+
);
|
|
466
|
+
const report = createReport({
|
|
467
|
+
generated: routes,
|
|
236
468
|
manifest: resolvedManifest,
|
|
469
|
+
manifestFile,
|
|
237
470
|
outDir: resolvedOutDir,
|
|
238
|
-
|
|
471
|
+
skipped,
|
|
472
|
+
warnings
|
|
473
|
+
});
|
|
474
|
+
if (reportFile !== false) {
|
|
475
|
+
await writeFile(join(resolvedOutDir, reportFile), `${JSON.stringify(report, null, 2)}
|
|
476
|
+
`);
|
|
477
|
+
}
|
|
478
|
+
const result = {
|
|
479
|
+
manifest: resolvedManifest,
|
|
480
|
+
outDir: resolvedOutDir,
|
|
481
|
+
routes,
|
|
482
|
+
skipped,
|
|
483
|
+
warnings,
|
|
484
|
+
report
|
|
239
485
|
};
|
|
486
|
+
await afterGenerate?.(result);
|
|
487
|
+
return result;
|
|
240
488
|
}
|
|
241
489
|
|
|
242
490
|
const vueServerRendererPackage = "@vue/server-renderer";
|
|
@@ -363,7 +611,8 @@ function viteSsgPlugin(options = {}) {
|
|
|
363
611
|
async function refreshManifest() {
|
|
364
612
|
const routes = await resolveRouteInputs(options);
|
|
365
613
|
manifest = createSsgRouteManifest(routes, {
|
|
366
|
-
base: options.base ?? config?.base ?? "/"
|
|
614
|
+
base: options.base ?? config?.base ?? "/",
|
|
615
|
+
exclude: options.exclude
|
|
367
616
|
});
|
|
368
617
|
return manifest;
|
|
369
618
|
}
|
|
@@ -440,4 +689,4 @@ function viteSsgPlugin(options = {}) {
|
|
|
440
689
|
};
|
|
441
690
|
}
|
|
442
691
|
|
|
443
|
-
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, injectSsgRoutePayload, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|
|
692
|
+
export { createSsgRouteHtml, createSsgRouteManifest, createSsgRoutePayloadScript, createVueSsgRouteRenderer, defaultSsgManifestFile, defaultSsgReportFile, defaultSsgVirtualModuleId, discoverMarkdownSsgRoutes, escapeJsonForHtml, flattenStaticSsgRouterRoutes, injectSsgRoutePayload, isSsgRouteExcluded, isStaticSsgRoutePath, markdownFileToRoutePath, normalizeSsgBase, normalizeSsgRoute, normalizeSsgRoutePath, prerenderSsgRoutes, prerenderVueSsgRoutes, renderSsgRouteHtml, routePathToHtmlFile, routePathToId, viteSsgPlugin };
|