snappy-prerender 2.6.0-rc.3 → 2.6.0
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 +140 -0
- package/README.md +42 -21
- package/package.json +8 -5
- package/src/cli.js +95 -9
- package/src/core/artifacts.js +110 -0
- package/src/core/blocking.js +53 -1
- package/src/core/browser.js +109 -39
- package/src/core/build-origin.js +19 -0
- package/src/core/config.js +128 -2
- package/src/core/css-url.js +34 -0
- package/src/core/destination.js +28 -3
- package/src/core/init.js +44 -1
- package/src/core/inline-css.js +34 -13
- package/src/core/metadata-origin.js +11 -2
- package/src/core/normalize.js +52 -2
- package/src/core/output.js +32 -9
- package/src/core/page-content.js +13 -0
- package/src/core/preload-manifest.js +61 -7
- package/src/core/prune.js +65 -0
- package/src/core/render.js +64 -10
- package/src/core/resources.js +1 -0
- package/src/core/routes.js +9 -0
- package/src/core/run.js +177 -304
- package/src/core/server.js +30 -4
- package/src/core/sitemap.js +49 -17
- package/src/core/url-policy.js +32 -0
- package/src/core/verify.js +14 -4
- package/src/core/write.js +278 -0
- package/src/head/index.js +1 -1
- package/src/head/tags.js +19 -26
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
6
|
+
uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Earlier history predates
|
|
7
|
+
the release tags; see the Git history for those changes.
|
|
8
|
+
|
|
9
|
+
## [2.6.0] - 2026-10-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `cleanDestination` empties the destination before copying the build output, so assets
|
|
14
|
+
removed from a build do not linger.
|
|
15
|
+
- `pruneStaleRoutes` removes pages recorded by the previous run's `.snappy-manifest.json`
|
|
16
|
+
when they are no longer produced, without touching assets or user files.
|
|
17
|
+
- New build gates: `failOnEmptyPage` (on by default), `failOnConsoleError`,
|
|
18
|
+
`failOnFailedRequest`, `failOnDuplicateContent`, `failOnUndetected`,
|
|
19
|
+
`failOnVerificationError` and the optional `failOnBuildOriginMetadata`.
|
|
20
|
+
- `browserDownloadVersion` pins the Chrome for Testing build id; a cached build is now
|
|
21
|
+
used without contacting the version feed, so offline rebuilds work.
|
|
22
|
+
- `stripBuildOriginMetadata` removes canonical and social tags that point at the
|
|
23
|
+
prerender server instead of shipping loopback URLs.
|
|
24
|
+
- Reports now expose `consoleErrors`, `failedRequests`, `emptyPages`, `duplicatePages`
|
|
25
|
+
and `pruned`; page files carry `noindex`, and verification entries carry `status` and
|
|
26
|
+
`empty`.
|
|
27
|
+
- CLI flags for the new gates and for `quietPeriod`, `scrollStepDelay`,
|
|
28
|
+
`serveCmdTimeout`, `shutdownTimeout`, `maxCachedBytes`, `viewport`, `readyFlag`,
|
|
29
|
+
`readySelector`, `headless`, `cleanDestination` and `metadata.base`.
|
|
30
|
+
- A Node 22/24 test workflow and a dedicated integration suite for the audit fixes.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Breaking:** the minimum supported Node version is now 22.12, matching
|
|
35
|
+
`@puppeteer/browsers`.
|
|
36
|
+
- **Breaking:** an explicit `browser` choice no longer falls back to another engine.
|
|
37
|
+
`chromium` uses a Playwright-managed Chromium and asks you to install it when missing;
|
|
38
|
+
`auto` is the only choice that walks machine, Playwright cache, cached Chrome for
|
|
39
|
+
Testing, then download.
|
|
40
|
+
- **Breaking:** `include` is a true output allowlist. A wildcard-only list still seeds the
|
|
41
|
+
root for crawling, but the seed itself is not written. Passing `include: ['/']` is
|
|
42
|
+
treated as the default and leaves crawling enabled.
|
|
43
|
+
- **Breaking:** canonical, Open Graph and sitemap URLs share one page-path policy:
|
|
44
|
+
directory output keeps its trailing slash, flat output stays extensionless, and an
|
|
45
|
+
explicit `trailingSlash` wins.
|
|
46
|
+
- **Breaking:** sitemap entries no longer carry `lastmod` (a build date claimed every page
|
|
47
|
+
changed), noindex pages are excluded, and sitemaps above 50,000 URLs are split behind a
|
|
48
|
+
sitemap index.
|
|
49
|
+
- **Breaking:** `metadata.siteUrl` must be an origin without a path; sub-path deployments
|
|
50
|
+
belong in `base`. A `serveCmd` URL with a path is rejected for the same reason.
|
|
51
|
+
- **Breaking:** `removeStyleTags` combined with `inlineCss: 'inline'` or `true` is
|
|
52
|
+
rejected instead of silently removing the CSS that was just inlined.
|
|
53
|
+
- Empty output now fails a route by default; 404 pages are emitted with a robots
|
|
54
|
+
`noindex` tag.
|
|
55
|
+
- Third-party blocking now covers WebSockets and accepts hostnames with ports, wildcard
|
|
56
|
+
suffixes and leading-dot suffixes. Service workers are blocked during rendering and
|
|
57
|
+
verification.
|
|
58
|
+
- Verification uses a real browser user agent and the same reduced-motion context as the
|
|
59
|
+
render pass; mode counts are always logged and can fail the build with
|
|
60
|
+
`failOnUndetected`.
|
|
61
|
+
- Inlined stylesheets rebase relative `url()` values against their source stylesheet;
|
|
62
|
+
sheets using `@import` stay linked to preserve the cascade.
|
|
63
|
+
- Preload manifests are derived from the final HTML, so inline CSS, tag surgery and failed
|
|
64
|
+
writes no longer leave stale hints. The not-found route is omitted.
|
|
65
|
+
- Screenshots use the same unchanged-file check as HTML and dry runs report the image file
|
|
66
|
+
instead of an HTML one.
|
|
67
|
+
- `init` reads Vite's resolved `outDir` and `base`, and Create React App's `homepage`, so
|
|
68
|
+
the wizard no longer guesses `dist` and `/` for custom projects. During a prerelease it
|
|
69
|
+
prints the `@next` install tag.
|
|
70
|
+
- Unknown config keys and options that have no effect in the current combination now
|
|
71
|
+
produce warnings instead of being ignored silently.
|
|
72
|
+
- Rendered output is split across smaller modules; `run.js` is back under the 600-line
|
|
73
|
+
limit.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- `setHead` called an undefined helper and threw `ReferenceError`.
|
|
78
|
+
- HTTP 4xx/5xx navigations, empty pages and zero-route runs could be written and reported
|
|
79
|
+
as successful; they now fail with route-level errors.
|
|
80
|
+
- The static server served `index.html` for dead extensionless links and failed dotted
|
|
81
|
+
routes; HTML navigations now fall back deliberately and duplicate output is detected.
|
|
82
|
+
- Canonical and `og:url` doubled a non-root `base` path.
|
|
83
|
+
- `verifyChangedOnly` could skip a changed page when a shared state script was unchanged.
|
|
84
|
+
- `asyncScriptTags` marked the injected state script `async`, allowing it to run after the
|
|
85
|
+
application bundle.
|
|
86
|
+
- `removeAttributes` with a global or sticky RegExp skipped every other match.
|
|
87
|
+
- Output collisions that differ only by case are detected on case-insensitive
|
|
88
|
+
filesystems.
|
|
89
|
+
- A destination inside the source directory recursed until the path length limit.
|
|
90
|
+
- Inlined CSS lost relative font and image URLs.
|
|
91
|
+
- Build-server metadata shipped loopback canonical and social URLs.
|
|
92
|
+
- Preload manifests described resources the final page no longer loaded.
|
|
93
|
+
- Sitemap and canonical URLs disagreed on trailing slashes and flat output.
|
|
94
|
+
- Allowed-host matching ignored ports and wildcards, and WebSocket requests bypassed
|
|
95
|
+
blocking.
|
|
96
|
+
- Browser downloads needed the network even when a cached browser was available.
|
|
97
|
+
- The CLI advertised `--preconnect-third-party` without defining it, and several other
|
|
98
|
+
documented flags had no CLI equivalent.
|
|
99
|
+
|
|
100
|
+
## [2.6.0-rc.3] - 2026-10-03
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- Computed config files: `snappy.config.js` may export an async function that returns
|
|
105
|
+
options, so routes can come from a CMS, sitemap or file listing.
|
|
106
|
+
- Sitemap generation and the `sitemap` option.
|
|
107
|
+
- Stable output: unchanged pages, state scripts and artifacts are left untouched.
|
|
108
|
+
- React 16.8 and 17 fixtures and integration coverage.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
|
|
112
|
+
- Documentation links to the npm package and notes the release-candidate channel.
|
|
113
|
+
|
|
114
|
+
## [2.6.0-rc.2] - 2026-09-26
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
|
|
118
|
+
- `snappy-prerender init` setup wizard, with flags for non-interactive use.
|
|
119
|
+
- CSP-friendly output: `csp: 'off' | 'strict'`, `externalScripts` and configurable
|
|
120
|
+
`criticalCssPreload`.
|
|
121
|
+
|
|
122
|
+
## [2.6.0-rc.1] - 2026-09-26
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- The `snappy-prerender/head` entry point: `Head`, `setHead`, `clearHead`, metadata
|
|
127
|
+
defaults, canonical, Open Graph and Twitter tags, and client-side reconciliation.
|
|
128
|
+
- Head-layer audit fixes for tag ownership, restoration and repeated runs.
|
|
129
|
+
|
|
130
|
+
## [2.5.0] - 2026-09-25
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
|
|
134
|
+
- react-snap feature parity for v2: `inlineCss`, `minifyHtml`, `minifyCss`,
|
|
135
|
+
`preloadImages`, `cacheAjaxRequests`, `snapSaveState`, `preloadManifest`,
|
|
136
|
+
`removeBlobs`, `removeStyleTags`, `removeScriptTags`, `asyncScriptTags`,
|
|
137
|
+
`destination`, `saveAs`, `browserArgs` and `ignoreHTTPSErrors`.
|
|
138
|
+
- Hydration verification that reports `hydrated`, `re-rendered`, `undetected` and
|
|
139
|
+
`unknown`, plus per-route React mismatch messages.
|
|
140
|
+
- Third-party requests allowed by default, with opt-in blocking and host allowlists.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/snappy-prerender)
|
|
8
8
|
[](https://github.com/sayakmukherjee080/snappy-prerender/blob/main/LICENSE)
|
|
9
9
|
|
|
10
|
-
Package page: [npmjs.com/package/snappy-prerender](https://www.npmjs.com/package/snappy-prerender).
|
|
10
|
+
Package page: [npmjs.com/package/snappy-prerender](https://www.npmjs.com/package/snappy-prerender). Install with `npm install --save-dev snappy-prerender`. Release notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
11
11
|
|
|
12
12
|
- **No SSR entry, no framework migration, no app changes.** If it builds to a `dist` directory, it can be prerendered.
|
|
13
13
|
- **React 16.8, 17, 18 and 19, Vite 6/7/8, or any static build.**
|
|
@@ -74,9 +74,10 @@ import { prerender } from 'snappy-prerender/core';
|
|
|
74
74
|
|
|
75
75
|
const report = await prerender({ sourceDir: 'dist', include: ['/'] });
|
|
76
76
|
if (!report.ok) {
|
|
77
|
-
for (const failure of report.verification
|
|
77
|
+
for (const failure of report.verification?.routes.filter((entry) => !entry.ok) ?? []) {
|
|
78
78
|
console.error(failure.route, failure.hydrationErrors);
|
|
79
79
|
}
|
|
80
|
+
for (const failure of report.errors) console.error(failure.route, failure.message);
|
|
80
81
|
}
|
|
81
82
|
```
|
|
82
83
|
|
|
@@ -154,7 +155,7 @@ Rules worth knowing:
|
|
|
154
155
|
- When no Head wants a tag any more, a generated one is removed and one the app wrote is restored to the value it had before the head layer touched it. That original is recorded into the page during prerendering, so a route change brings the template's fallback metadata back.
|
|
155
156
|
- The component renders nothing, so it cannot affect hydration of the page content.
|
|
156
157
|
- The title template applies unless the title already ends with the template's fixed part, or is the site name itself, so a title composed by hand is never suffixed twice.
|
|
157
|
-
- Absolute URLs are required for `og:url`, `og:image` and canonical.
|
|
158
|
+
- Absolute URLs are required for `og:url`, `og:image` and canonical. `metadata.siteUrl` must be an origin (no path); put a sub-path deployment in `base`. Without `siteUrl`, URLs that fall back to the build server are removed from the output by default (`stripBuildOriginMetadata: true`) and the run warns. The `trailingSlash` convention applies to page URLs only; asset URLs are left alone. Canonical, social and sitemap URLs share one page-path policy: directory output keeps its slash, flat output stays extensionless, and an explicit `trailingSlash` wins.
|
|
158
159
|
- `metadata.base` overrides the base path used for absolute URLs and defaults to the `base` option.
|
|
159
160
|
- On React 19, React hoists `<title>` and `<meta>` rendered in JSX and leaves an existing template tag alone, so a page can end up with two titles. Use React's native metadata or this component, not both; the run warns when the output carries more than one `<title>`.
|
|
160
161
|
- Every metadata value has a CLI flag: `--metadata-site-url`, `--metadata-site-name`, `--metadata-title-template`, `--metadata-default-image`, `--metadata-trailing-slash`.
|
|
@@ -175,6 +176,8 @@ After writing the output, every route is loaded again with the real client bundl
|
|
|
175
176
|
| `undetected` | No React root was found on the container, so the mode could not be judged. |
|
|
176
177
|
| `unknown` | The prerendered node was never seen, for example a container outside the usual ids. |
|
|
177
178
|
|
|
179
|
+
A route whose verification navigation returns HTTP 4xx/5xx, or whose container renders no content, is reported as failed with its `status` or `empty` flag. `undetected` and `unknown` are reported as mode counts; set `failOnUndetected: true` to make any such route fail the build. A verification pass that cannot run is listed in `verification.errors`; set `failOnVerificationError: true` to fail on those separately from hydration errors.
|
|
180
|
+
|
|
178
181
|
`re-rendered` matters because an app calling `createRoot` instead of `hydrateRoot` throws the prerendered DOM away on boot — and because `createRoot` cannot raise a hydration error, nothing else would ever tell you. It is reported per route as `verification.routes[].mode` and summarised in `verification.modes`, with a warning in the log. Set `failOnRerender: true` to make it fail the build as well.
|
|
179
182
|
|
|
180
183
|
**Page errors.** Errors the browser logs while verifying are reported too: uncaught exceptions as warnings, console errors at debug level. Errors thrown during rendering itself are counted in the summary and reported per route, and `failOnPageError: true` turns them into a build failure; by default an app that throws still ships what it managed to render, because the static output is what crawlers read.
|
|
@@ -243,7 +246,7 @@ snappy({ preconnectThirdParty: true, preloadImages: true, preloadManifest: true
|
|
|
243
246
|
|
|
244
247
|
- `preconnectThirdParty` (default `true`) records every third-party origin the page tried to reach — including ones blocked by `blockThirdParty` — and adds `<link rel="preconnect">` for each.
|
|
245
248
|
- `preloadImages` adds `<link rel="preload" as="image">` for same-origin images the page loaded.
|
|
246
|
-
- `preloadManifest` writes `preload-manifest.json` into the output directory: for each route, a `Link` header value listing the scripts and stylesheets
|
|
249
|
+
- `preloadManifest` writes `preload-manifest.json` into the output directory: for each route, a `Link` header value listing the scripts and stylesheets the final HTML still references, filtered by `ignoreForPreload` (default `['service-worker.js']`). Inline CSS and tag surgery are already reflected, the not-found route is omitted, and hints and route keys carry the base path, so serve these as Early Hints or `Link` response headers as they are.
|
|
247
250
|
|
|
248
251
|
react-snap called this `http2PushManifest`. Browsers removed HTTP/2 push (Chrome in v106), so the manifest is now a header list rather than a push list.
|
|
249
252
|
|
|
@@ -253,7 +256,7 @@ react-snap called this `http2PushManifest`. Browsers removed HTTP/2 push (Chrome
|
|
|
253
256
|
snappy({ sitemap: true, metadata: { siteUrl: 'https://example.com' } });
|
|
254
257
|
```
|
|
255
258
|
|
|
256
|
-
`snappy` already knows every route it rendered, so it can write the sitemap for you. `sitemap.xml` is generated from the
|
|
259
|
+
`snappy` already knows every route it rendered, so it can write the sitemap for you. `sitemap.xml` is generated from the routes that were written — using the same page path policy as canonical URLs, with the base path applied, `noindex` pages and the not-found route left out — and split behind a sitemap index when it exceeds 50,000 URLs. Entries carry no `<lastmod>`, because a build date would claim every page changed on every build.
|
|
257
260
|
|
|
258
261
|
When the output directory already has a `robots.txt`, a `Sitemap:` line is appended to it if it does not point anywhere yet; an existing directive is left alone. Without a `robots.txt`, the run logs the exact line to add instead of creating a file you did not ask for.
|
|
259
262
|
|
|
@@ -429,14 +432,15 @@ The function is awaited once per run, and whatever it returns must be a plain op
|
|
|
429
432
|
| --- | --- | --- |
|
|
430
433
|
| `sourceDir` | `dist` | Built output directory |
|
|
431
434
|
| `destination` | `null` | Copy the output here and write results here instead of in place |
|
|
435
|
+
| `cleanDestination` | `false` | Empty the destination before copying, so removed assets do not linger |
|
|
432
436
|
| `base` | `/` | Public base path |
|
|
433
|
-
| `include` | `['/']` | Seed routes and allowlist. Wildcards (`/blog/*`) filter discovered routes, only concrete paths are seeded
|
|
437
|
+
| `include` | `['/']` | Seed routes and output allowlist. Wildcards (`/blog/*`) filter discovered routes, only concrete paths are seeded, and a seed that does not match the list is used for crawling but not written. Passing `['/']` is treated as the default: crawl everything linked from the root |
|
|
434
438
|
| `exclude` | `[]` | Routes to skip, strings or RegExp |
|
|
435
439
|
| `crawl` | `true` | Follow same-origin links from seeds |
|
|
436
440
|
| `maxDepth` | `null` | Crawl depth limit, `null` is unlimited, `0` renders seeds only |
|
|
437
441
|
| `maxRoutes` | `null` | Stop after this many routes; hitting the cap marks the run incomplete |
|
|
438
442
|
| `concurrency` | derived | Parallel pages, derived from CPU count, capped at 8 |
|
|
439
|
-
| `timeout` | `30000` |
|
|
443
|
+
| `timeout` | `30000` | Navigation, ready and network-settle timeout in ms; each wait gets this budget |
|
|
440
444
|
| `quietPeriod` | `500` | Network must be idle this long before capture |
|
|
441
445
|
| `readyFlag` | `__prerenderReady` | Window flag awaited when defined |
|
|
442
446
|
| `readySelector` | `null` | Selector awaited when set |
|
|
@@ -447,6 +451,7 @@ The function is awaited once per run, and whatever it returns must be a plain op
|
|
|
447
451
|
| `browser` | `auto` | `auto`, `chrome`, `msedge`, `chromium`, or a path to an executable |
|
|
448
452
|
| `browserDownload` | `true` | Allow the Chrome for Testing fallback download |
|
|
449
453
|
| `browserDownloadHash` | `null` | SHA-256 digest the fallback download must match, verified before use |
|
|
454
|
+
| `browserDownloadVersion` | `null` | Chrome for Testing build id to use for the cache and download instead of resolving `stable` |
|
|
450
455
|
| `browserArgs` | `[]` | Extra browser launch arguments |
|
|
451
456
|
| `headless` | `true` | Run the browser headless |
|
|
452
457
|
| `ignoreHTTPSErrors` | `false` | Ignore TLS errors while rendering |
|
|
@@ -464,31 +469,40 @@ The function is awaited once per run, and whatever it returns must be a plain op
|
|
|
464
469
|
| `minifyCss` | `false` | Minify CSS with clean-css, also applied to inlined CSS |
|
|
465
470
|
| `preconnectThirdParty` | `true` | Add preconnect hints for every third-party origin the page contacts |
|
|
466
471
|
| `preloadImages` | `false` | Add preload hints for same-origin images |
|
|
467
|
-
| `preloadManifest` | `false` | Write `preload-manifest.json` with Link header hints |
|
|
472
|
+
| `preloadManifest` | `false` | Write `preload-manifest.json` with Link header hints, derived from the final HTML |
|
|
468
473
|
| `ignoreForPreload` | `['service-worker.js']` | File names excluded from the manifest |
|
|
469
|
-
| `sitemap` | `false` | Write `sitemap.xml` from the
|
|
474
|
+
| `sitemap` | `false` | Write `sitemap.xml` (or a sitemap index above 50,000 URLs) from the routes that do not declare noindex, and point an existing `robots.txt` at it; needs `metadata.siteUrl` |
|
|
470
475
|
| `cacheAjaxRequests` | `false` | Expose captured JSON responses as `window.snapStore` |
|
|
471
|
-
| `maxCachedBytes` | `5242880` | Largest JSON response cached per
|
|
476
|
+
| `maxCachedBytes` | `5242880` | Largest JSON response cached per request, measured on the serialised body |
|
|
472
477
|
| `removeBlobs` | `true` | Drop stylesheet links pointing at dead `blob:` URLs |
|
|
473
478
|
| `removeAttributes` | `[]` | Attribute names or RegExps to drop from the output, for libraries that stamp per-load ids |
|
|
474
|
-
| `
|
|
479
|
+
| `stripBuildOriginMetadata` | `true` | Remove canonical and social tags whose URL points at the prerender server |
|
|
480
|
+
| `failOnBuildOriginMetadata` | `false` | Fail when metadata still points at the prerender server after stripping |
|
|
481
|
+
| `pruneStaleRoutes` | `false` | Delete pages listed by the previous run's manifest that this run no longer writes |
|
|
482
|
+
| `removeStyleTags` | `false` | Strip every style tag from the output; cannot be combined with `inlineCss` `'inline'` |
|
|
475
483
|
| `removeScriptTags` | `false` | Strip every script tag from the output |
|
|
476
484
|
| `asyncScriptTags` | `false` | Mark external scripts async |
|
|
477
485
|
| `externalScripts` | `false` | Write injected state scripts as content-hashed same-origin files instead of inline; set by `csp: 'strict'` |
|
|
478
486
|
| `flatOutput` | `false` | Write `about.html` instead of `about/index.html` |
|
|
479
|
-
| `notFoundRoute` | `/404` | Route emitted as `404.html
|
|
480
|
-
| `saveAs` | `html` | `html`, `png` or `jpeg
|
|
481
|
-
| `metadata` | `null` | Defaults for the head component: `siteUrl
|
|
487
|
+
| `notFoundRoute` | `/404` | Route emitted as `404.html` with a robots noindex tag; a notice is logged when it is never prerendered |
|
|
488
|
+
| `saveAs` | `html` | `html`, `png` or `jpeg`; identical screenshots are left untouched |
|
|
489
|
+
| `metadata` | `null` | Defaults for the head component: `siteUrl` (origin only), `siteName`, `titleTemplate`, `defaultImage`, `trailingSlash`, `base` |
|
|
482
490
|
| `verify` | `true` | Run the hydration verification pass |
|
|
483
491
|
| `verifyChangedOnly` | `false` | Skip verification for routes whose HTML file did not change since the last run |
|
|
484
492
|
| `failOnHydrationError` | `false` | Fail the run on hydration errors; off by default, where they are reported instead |
|
|
485
493
|
| `failOnRerender` | `false` | Fail the run when a route re-renders instead of hydrating |
|
|
486
494
|
| `failOnPageError` | `false` | Fail the run when a page throws while rendering; off by default, where page errors are reported |
|
|
495
|
+
| `failOnEmptyPage` | `true` | Fail when a route renders no app content |
|
|
496
|
+
| `failOnConsoleError` | `false` | Fail when the browser logs a console error while rendering |
|
|
497
|
+
| `failOnFailedRequest` | `false` | Fail when a same-origin request fails while rendering |
|
|
498
|
+
| `failOnDuplicateContent` | `false` | Fail when two routes produce byte-identical HTML |
|
|
499
|
+
| `failOnUndetected` | `false` | Fail when verification cannot classify any route as hydrated or re-rendered |
|
|
500
|
+
| `failOnVerificationError` | `false` | Fail when the verification pass cannot run on a route |
|
|
487
501
|
| `failOnError` | `true` | Fail the run on route render errors |
|
|
488
502
|
| `dryRun` | `false` | Render and report without writing |
|
|
489
503
|
| `logLevel` | `info` | `silent`, `error`, `warn`, `info`, `debug` |
|
|
490
504
|
| `serveCmd` | `null` | Command that starts the app server instead of static serving |
|
|
491
|
-
| `url` | `null` | App
|
|
505
|
+
| `url` | `null` | App origin, required with `serveCmd`; put a sub-path in `base` instead |
|
|
492
506
|
| `serveCmdTimeout` | `30000` | How long to wait for `serveCmd` to answer |
|
|
493
507
|
| `shutdownTimeout` | `5000` | Grace period before force-killing `serveCmd` |
|
|
494
508
|
|
|
@@ -501,13 +515,14 @@ import { prerender } from 'snappy-prerender/core';
|
|
|
501
515
|
|
|
502
516
|
const report = await prerender({ sourceDir: 'dist', include: ['/'] });
|
|
503
517
|
if (!report.ok) {
|
|
504
|
-
for (const failure of report.verification
|
|
518
|
+
for (const failure of report.verification?.routes.filter((entry) => !entry.ok) ?? []) {
|
|
505
519
|
console.error(failure.route, failure.hydrationErrors);
|
|
506
520
|
}
|
|
521
|
+
for (const failure of report.errors) console.error(failure.route, failure.message);
|
|
507
522
|
}
|
|
508
523
|
```
|
|
509
524
|
|
|
510
|
-
The report exposes `routes`, `files` (with per-route `status`), `errors`, `pageErrors` (uncaught browser errors seen while rendering), `duplicateTitles` (routes shipping more than one title), `verification` (per-route `mode` plus a `modes` summary), `preloadManifest`, `truncated`, and `ok`.
|
|
525
|
+
The report exposes `routes`, `files` (with per-route `status` and `noindex`), `errors`, `pageErrors` (uncaught browser errors seen while rendering), `consoleErrors`, `failedRequests`, `emptyPages`, `duplicatePages`, `duplicateTitles` (routes shipping more than one title), `verification` (nullable; per-route `mode`, `status` and `empty` plus a `modes` summary), `preloadManifest`, `sitemap`, `pruned`, `truncated`, and `ok`.
|
|
511
526
|
|
|
512
527
|
`snappy-prerender` exports `DEFAULTS`, `prerender` and `resolveConfig`; the default export is the Vite plugin.
|
|
513
528
|
|
|
@@ -531,16 +546,22 @@ Dropped: `sourceMaps` (declared but never referenced in react-snap's code), `fix
|
|
|
531
546
|
- Crawling is unbounded unless `maxRoutes` is set; hitting the cap marks the run as failed so incomplete output is never shipped silently. Which routes are cut is concurrency-dependent on a large site — narrow `include`/`exclude` instead when you need a deterministic set.
|
|
532
547
|
- **React 19 native metadata.** React hoists `<title>` and `<meta>` rendered in JSX and leaves an existing template title in place, so using both systems produces two titles in the output. Use one of them; the run warns when a page ships more than one `<title>`.
|
|
533
548
|
- If two routes would write the same file — such as `/` and `/index` with `flatOutput` — neither is written and the run fails with a collision error.
|
|
534
|
-
- Screenshots are viewport-width full-page captures, so they reflect the configured `viewport`, not a device matrix.
|
|
535
|
-
- Output files from routes that no longer exist are left in place
|
|
549
|
+
- Screenshots are viewport-width full-page captures, so they reflect the configured `viewport`, not a device matrix. Identical screenshots are left untouched.
|
|
550
|
+
- **Output files from routes that no longer exist** are left in place unless `pruneStaleRoutes` is enabled, which deletes only files listed in the previous run's `.snappy-manifest.json`.
|
|
551
|
+
- **Dead extensionless links** are indistinguishable from SPA routes to the static server, so a missing page can render the shell or the app's catch-all 404. Duplicate output is reported; set `failOnDuplicateContent` to enforce it, and use an app-level 404 marker where possible.
|
|
552
|
+
- **Metadata pointing at the build server** is removed by default (`stripBuildOriginMetadata: true`) and reported. Set `metadata.siteUrl` so canonical and social URLs survive.
|
|
553
|
+
- **`inlineCss`** rebases relative font and image URLs against each stylesheet. A sheet that uses `@import` is left as a link because concatenating it would change the cascade.
|
|
554
|
+
- **`verifyChangedOnly` compares the generated HTML only.** If a bundle keeps the same filename while its contents change, that route's HTML is unchanged and its verification is skipped; content-hashed asset names (Vite and CRA defaults) make the HTML change as well.
|
|
555
|
+
- **Destination directories are merged unless `cleanDestination` is on.** That option empties the destination first; `pruneStaleRoutes` separately removes pages the run itself generated.
|
|
556
|
+
- **`removeStyleTags`** cannot be combined with `inlineCss: 'inline'` or `true`; the config is rejected instead of silently shipping pages without CSS.
|
|
536
557
|
|
|
537
558
|
## Requirements
|
|
538
559
|
|
|
539
|
-
- Node.js >=
|
|
560
|
+
- Node.js >= 22.12
|
|
540
561
|
- Chrome or Edge installed (detected automatically), or allow the one-time Chrome for Testing download fallback
|
|
541
562
|
- `beasties` only if you use `inlineCss: 'critical'` (optional peer dependency)
|
|
542
563
|
|
|
543
|
-
Browser resolution order: installed Chrome, installed Edge, a Playwright-managed browser, then a Chrome for Testing download from Google's version-pinned HTTPS bucket into `SNAPPY_BROWSER_CACHE_DIR` (defaults to the platform cache directory).
|
|
564
|
+
Browser resolution order: installed Chrome, installed Edge, a Playwright-managed browser, a Chrome for Testing build already in the tool cache, then a Chrome for Testing download from Google's version-pinned HTTPS bucket into `SNAPPY_BROWSER_CACHE_DIR` (defaults to the platform cache directory). An explicit `browser` choice never falls back to another engine. Set `browserDownloadVersion` to pin the build id and `browserDownloadHash` to pin the archive digest; without a hash the download is trusted on the strength of HTTPS and the pinned URL. Set `browserDownload: false` to forbid downloads entirely.
|
|
544
565
|
|
|
545
566
|
## Contributing
|
|
546
567
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snappy-prerender",
|
|
3
|
-
"version": "2.6.0
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Drop-in prerendering for existing single-page apps. Renders your built app in a real browser, writes static HTML per route, reports React hydration mismatches, and optimises the output with critical CSS, link hints and cached AJAX state.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"playwright"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=22.12.0"
|
|
30
30
|
},
|
|
31
31
|
"main": "./src/index.js",
|
|
32
32
|
"exports": {
|
|
@@ -36,14 +36,17 @@
|
|
|
36
36
|
"./package.json": "./package.json"
|
|
37
37
|
},
|
|
38
38
|
"bin": {
|
|
39
|
-
"snappy-prerender": "
|
|
39
|
+
"snappy-prerender": "src/cli.js"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"src",
|
|
43
|
+
"CHANGELOG.md",
|
|
43
44
|
"LICENSE",
|
|
44
45
|
"README.md"
|
|
45
46
|
],
|
|
46
|
-
"sideEffects":
|
|
47
|
+
"sideEffects": [
|
|
48
|
+
"./src/head/manager.js"
|
|
49
|
+
],
|
|
47
50
|
"scripts": {
|
|
48
51
|
"test": "node scripts/run-tests.mjs tests/unit",
|
|
49
52
|
"test:integration": "node scripts/run-tests.mjs tests/integration",
|
|
@@ -65,7 +68,7 @@
|
|
|
65
68
|
"beasties": "0.4.1",
|
|
66
69
|
"react": "19.2.0",
|
|
67
70
|
"react-dom": "19.2.0",
|
|
68
|
-
"vite": "7.3.
|
|
71
|
+
"vite": "7.3.7"
|
|
69
72
|
},
|
|
70
73
|
"workspaces": [
|
|
71
74
|
"tests/fixtures/react16-app",
|
package/src/cli.js
CHANGED
|
@@ -30,17 +30,33 @@ Options:
|
|
|
30
30
|
--max-depth <n> Crawl depth limit, 0 renders roots only (default: unlimited)
|
|
31
31
|
--max-routes <n> Stop after this many routes and fail the run (default: unlimited)
|
|
32
32
|
--concurrency <n> Parallel browser pages (default: derived from CPU count)
|
|
33
|
-
--timeout <ms> Per-route timeout (default: 30000)
|
|
33
|
+
--timeout <ms> Per-route navigation/settle timeout (default: 30000)
|
|
34
|
+
--quiet-period <ms> Network must be idle this long before capture (default: 500)
|
|
35
|
+
--scroll-step-delay <ms> Delay between scroll steps (default: 100)
|
|
36
|
+
--viewport <WxH> Browser viewport (default: 1280x720)
|
|
37
|
+
--ready-selector <sel> Selector awaited when set
|
|
38
|
+
--ready-flag <name> Window flag awaited when defined (default: __prerenderReady)
|
|
39
|
+
--no-headless Run the browser visibly
|
|
34
40
|
--wait-for <selector> Wait for a selector on every route before capture
|
|
35
41
|
--browser <choice> auto | chrome | msedge | chromium | path (default: auto)
|
|
36
42
|
--no-browser-download Refuse the Chrome for Testing fallback download
|
|
37
43
|
--browser-download-hash <sha256> Pin the fallback download to a SHA-256 digest
|
|
44
|
+
--browser-download-version <build> Pin the Chrome for Testing build id
|
|
38
45
|
--storage-state <file> Playwright storage state for authenticated routes
|
|
39
46
|
--no-verify Skip the hydration verification pass
|
|
40
47
|
--verify-changed-only Skip verification for routes whose HTML did not change
|
|
41
48
|
--fail-on-hydration-error Fail the build when hydration errors are found
|
|
42
49
|
--fail-on-rerender Fail when a route re-renders instead of hydrating
|
|
43
50
|
--fail-on-page-error Fail when a page throws while it is being rendered
|
|
51
|
+
--no-fail-on-error Report route errors without failing the run
|
|
52
|
+
--fail-on-empty-page Fail when a route renders no app content (default: on)
|
|
53
|
+
--no-fail-on-empty-page Allow an empty page and report it instead
|
|
54
|
+
--fail-on-console-error Fail when the browser logs a console error while rendering
|
|
55
|
+
--fail-on-failed-request Fail when a same-origin request fails while rendering
|
|
56
|
+
--fail-on-duplicate-content Fail when two routes produce identical HTML
|
|
57
|
+
--fail-on-build-origin-metadata Fail when metadata still points at the prerender server
|
|
58
|
+
--fail-on-undetected Fail when verification cannot find a framework root
|
|
59
|
+
--fail-on-verification-error Fail when the verification pass cannot run
|
|
44
60
|
--block-third-party Block third-party requests while rendering
|
|
45
61
|
--no-block-third-party Allow third-party requests (the default)
|
|
46
62
|
--allowed-hosts <hosts> Third-party hosts to allow while blocking the rest, comma separated
|
|
@@ -51,6 +67,7 @@ Options:
|
|
|
51
67
|
--flat Write about.html instead of about/index.html
|
|
52
68
|
--not-found <route> Route emitted as 404.html (default: /404)
|
|
53
69
|
--destination <dir> Write output to another directory (default: in place)
|
|
70
|
+
--clean-destination Empty the destination before copying the build output
|
|
54
71
|
--save-as <format> html | png | jpeg (default: html)
|
|
55
72
|
--user-agent <value> User agent used while rendering (default: SnappyPrerender)
|
|
56
73
|
--metadata-site-url <url> Public site URL for canonical and og:url
|
|
@@ -58,6 +75,7 @@ Options:
|
|
|
58
75
|
--metadata-title-template <t> Title template, for example '%s | Example'
|
|
59
76
|
--metadata-default-image <url> Share image for routes without one
|
|
60
77
|
--metadata-trailing-slash <mode> preserve | always | never
|
|
78
|
+
--metadata-base <path> Base path used for absolute metadata URLs
|
|
61
79
|
--inline-css <strategy> inline | critical (critical needs the beasties package)
|
|
62
80
|
--critical-css-preload <mode> Swap for deferred CSS: none | body | media | swap | swap-high | swap-low | js | js-lazy
|
|
63
81
|
--minify-html Minify the generated HTML
|
|
@@ -69,6 +87,7 @@ Options:
|
|
|
69
87
|
--sitemap Write sitemap.xml from the rendered routes (needs metadata.siteUrl)
|
|
70
88
|
--ignore-for-preload <names> File names excluded from the manifest, comma separated
|
|
71
89
|
--cache-ajax-requests Expose captured JSON responses as window.snapStore
|
|
90
|
+
--max-cached-bytes <n> Largest JSON response cached per request (default: 5242880)
|
|
72
91
|
--remove-scripts Strip every script tag from the output
|
|
73
92
|
--remove-styles Strip every style tag from the output
|
|
74
93
|
--async-scripts Mark external scripts async
|
|
@@ -76,10 +95,14 @@ Options:
|
|
|
76
95
|
--csp <mode> off | strict: strict makes the output work under script-src 'self'
|
|
77
96
|
--no-remove-blobs Keep blob stylesheet links instead of dropping them
|
|
78
97
|
--remove-attributes <names> Drop these attributes from the output, comma separated
|
|
98
|
+
--no-strip-build-origin-metadata Keep tags that point at the prerender server
|
|
99
|
+
--prune-stale-routes Delete pages listed by the previous run's manifest
|
|
79
100
|
--ignore-https-errors Ignore TLS errors while rendering
|
|
80
101
|
--browser-args <args> Extra browser launch arguments, comma separated
|
|
81
102
|
--url <url> App URL to connect to when using --serve-cmd
|
|
82
103
|
--serve-cmd <command> Command that starts the app server instead of static serving
|
|
104
|
+
--serve-cmd-timeout <ms> How long to wait for the command server (default: 30000)
|
|
105
|
+
--shutdown-timeout <ms> Grace period before force-killing the server (default: 5000)
|
|
83
106
|
--dry-run Render and report without writing files
|
|
84
107
|
--log-level <level> silent | error | warn | info | debug (default: info)
|
|
85
108
|
--yes, -y Accept every default, for a shell without a terminal
|
|
@@ -105,16 +128,34 @@ async function main() {
|
|
|
105
128
|
'max-routes': { type: 'string' },
|
|
106
129
|
concurrency: { type: 'string' },
|
|
107
130
|
timeout: { type: 'string' },
|
|
131
|
+
'quiet-period': { type: 'string' },
|
|
132
|
+
'scroll-step-delay': { type: 'string' },
|
|
133
|
+
'max-cached-bytes': { type: 'string' },
|
|
134
|
+
'serve-cmd-timeout': { type: 'string' },
|
|
135
|
+
'shutdown-timeout': { type: 'string' },
|
|
136
|
+
viewport: { type: 'string' },
|
|
137
|
+
'ready-flag': { type: 'string' },
|
|
138
|
+
'ready-selector': { type: 'string' },
|
|
139
|
+
headless: { type: 'boolean' },
|
|
108
140
|
'wait-for': { type: 'string' },
|
|
109
141
|
browser: { type: 'string' },
|
|
110
142
|
'no-browser-download': { type: 'boolean' },
|
|
111
143
|
'browser-download-hash': { type: 'string' },
|
|
144
|
+
'browser-download-version': { type: 'string' },
|
|
112
145
|
'storage-state': { type: 'string' },
|
|
113
146
|
'no-verify': { type: 'boolean' },
|
|
114
147
|
'verify-changed-only': { type: 'boolean' },
|
|
115
148
|
'fail-on-hydration-error': { type: 'boolean' },
|
|
149
|
+
'fail-on-error': { type: 'boolean' },
|
|
116
150
|
'fail-on-rerender': { type: 'boolean' },
|
|
117
151
|
'fail-on-page-error': { type: 'boolean' },
|
|
152
|
+
'fail-on-empty-page': { type: 'boolean' },
|
|
153
|
+
'fail-on-console-error': { type: 'boolean' },
|
|
154
|
+
'fail-on-failed-request': { type: 'boolean' },
|
|
155
|
+
'fail-on-duplicate-content': { type: 'boolean' },
|
|
156
|
+
'fail-on-build-origin-metadata': { type: 'boolean' },
|
|
157
|
+
'fail-on-undetected': { type: 'boolean' },
|
|
158
|
+
'fail-on-verification-error': { type: 'boolean' },
|
|
118
159
|
'no-block-third-party': { type: 'boolean' },
|
|
119
160
|
'block-third-party': { type: 'boolean' },
|
|
120
161
|
'allowed-hosts': { type: 'string', multiple: true },
|
|
@@ -125,6 +166,7 @@ async function main() {
|
|
|
125
166
|
flat: { type: 'boolean' },
|
|
126
167
|
'not-found': { type: 'string' },
|
|
127
168
|
destination: { type: 'string' },
|
|
169
|
+
'clean-destination': { type: 'boolean' },
|
|
128
170
|
'save-as': { type: 'string' },
|
|
129
171
|
'user-agent': { type: 'string' },
|
|
130
172
|
'metadata-site-url': { type: 'string' },
|
|
@@ -132,12 +174,14 @@ async function main() {
|
|
|
132
174
|
'metadata-title-template': { type: 'string' },
|
|
133
175
|
'metadata-default-image': { type: 'string' },
|
|
134
176
|
'metadata-trailing-slash': { type: 'string' },
|
|
177
|
+
'metadata-base': { type: 'string' },
|
|
135
178
|
'inline-css': { type: 'string' },
|
|
136
179
|
'critical-css-preload': { type: 'string' },
|
|
137
180
|
csp: { type: 'string' },
|
|
138
181
|
'minify-html': { type: 'boolean' },
|
|
139
182
|
'minify-css': { type: 'boolean' },
|
|
140
183
|
'no-preconnect-third-party': { type: 'boolean' },
|
|
184
|
+
'preconnect-third-party': { type: 'boolean' },
|
|
141
185
|
'preload-images': { type: 'boolean' },
|
|
142
186
|
'preload-manifest': { type: 'boolean' },
|
|
143
187
|
sitemap: { type: 'boolean' },
|
|
@@ -149,6 +193,8 @@ async function main() {
|
|
|
149
193
|
'external-scripts': { type: 'boolean' },
|
|
150
194
|
'no-remove-blobs': { type: 'boolean' },
|
|
151
195
|
'remove-attributes': { type: 'string', multiple: true },
|
|
196
|
+
'no-strip-build-origin-metadata': { type: 'boolean' },
|
|
197
|
+
'prune-stale-routes': { type: 'boolean' },
|
|
152
198
|
'ignore-https-errors': { type: 'boolean' },
|
|
153
199
|
'browser-args': { type: 'string', multiple: true },
|
|
154
200
|
url: { type: 'string' },
|
|
@@ -205,18 +251,52 @@ function cliOptions({ values, positionals }) {
|
|
|
205
251
|
if (values['max-routes'] !== undefined) options.maxRoutes = Number(values['max-routes']);
|
|
206
252
|
if (values.concurrency !== undefined) options.concurrency = Number(values.concurrency);
|
|
207
253
|
if (values.timeout !== undefined) options.timeout = Number(values.timeout);
|
|
254
|
+
if (values['quiet-period'] !== undefined) options.quietPeriod = Number(values['quiet-period']);
|
|
255
|
+
if (values['scroll-step-delay'] !== undefined) {
|
|
256
|
+
options.scrollStepDelay = Number(values['scroll-step-delay']);
|
|
257
|
+
}
|
|
258
|
+
if (values['max-cached-bytes'] !== undefined) {
|
|
259
|
+
options.maxCachedBytes = Number(values['max-cached-bytes']);
|
|
260
|
+
}
|
|
261
|
+
if (values['serve-cmd-timeout'] !== undefined) {
|
|
262
|
+
options.serveCmdTimeout = Number(values['serve-cmd-timeout']);
|
|
263
|
+
}
|
|
264
|
+
if (values['shutdown-timeout'] !== undefined) {
|
|
265
|
+
options.shutdownTimeout = Number(values['shutdown-timeout']);
|
|
266
|
+
}
|
|
267
|
+
if (values.viewport !== undefined) {
|
|
268
|
+
const match = /^(\d+)x(\d+)$/i.exec(values.viewport);
|
|
269
|
+
if (!match) throw new Error('viewport must look like 1280x720');
|
|
270
|
+
options.viewport = { width: Number(match[1]), height: Number(match[2]) };
|
|
271
|
+
}
|
|
272
|
+
if (values['ready-selector'] !== undefined) options.readySelector = values['ready-selector'];
|
|
273
|
+
if (values['ready-flag'] !== undefined) options.readyFlag = values['ready-flag'];
|
|
274
|
+
if (values.headless !== undefined) options.headless = values.headless;
|
|
208
275
|
if (values['wait-for'] !== undefined) options.waitFor = values['wait-for'];
|
|
209
276
|
if (values.browser !== undefined) options.browser = values.browser;
|
|
210
277
|
if (values['no-browser-download']) options.browserDownload = false;
|
|
211
278
|
if (values['browser-download-hash'] !== undefined) {
|
|
212
279
|
options.browserDownloadHash = values['browser-download-hash'];
|
|
213
280
|
}
|
|
281
|
+
if (values['browser-download-version'] !== undefined) {
|
|
282
|
+
options.browserDownloadVersion = values['browser-download-version'];
|
|
283
|
+
}
|
|
214
284
|
if (values['storage-state'] !== undefined) options.storageState = values['storage-state'];
|
|
215
285
|
if (values['no-verify']) options.verify = false;
|
|
216
286
|
if (values['verify-changed-only']) options.verifyChangedOnly = true;
|
|
217
287
|
if (values['fail-on-hydration-error']) options.failOnHydrationError = true;
|
|
218
288
|
if (values['fail-on-rerender']) options.failOnRerender = true;
|
|
219
289
|
if (values['fail-on-page-error']) options.failOnPageError = true;
|
|
290
|
+
if (values['fail-on-error'] !== undefined) options.failOnError = values['fail-on-error'];
|
|
291
|
+
if (values['fail-on-empty-page'] !== undefined) {
|
|
292
|
+
options.failOnEmptyPage = values['fail-on-empty-page'];
|
|
293
|
+
}
|
|
294
|
+
if (values['fail-on-console-error']) options.failOnConsoleError = true;
|
|
295
|
+
if (values['fail-on-failed-request']) options.failOnFailedRequest = true;
|
|
296
|
+
if (values['fail-on-duplicate-content']) options.failOnDuplicateContent = true;
|
|
297
|
+
if (values['fail-on-build-origin-metadata']) options.failOnBuildOriginMetadata = true;
|
|
298
|
+
if (values['fail-on-undetected']) options.failOnUndetected = true;
|
|
299
|
+
if (values['fail-on-verification-error']) options.failOnVerificationError = true;
|
|
220
300
|
if (values['no-block-third-party']) options.blockThirdParty = false;
|
|
221
301
|
if (values['block-third-party']) options.blockThirdParty = true;
|
|
222
302
|
if (values['allowed-hosts'] !== undefined)
|
|
@@ -228,6 +308,7 @@ function cliOptions({ values, positionals }) {
|
|
|
228
308
|
if (values.flat) options.flatOutput = true;
|
|
229
309
|
if (values['not-found'] !== undefined) options.notFoundRoute = values['not-found'];
|
|
230
310
|
if (values.destination !== undefined) options.destination = values.destination;
|
|
311
|
+
if (values['clean-destination']) options.cleanDestination = true;
|
|
231
312
|
if (values['save-as'] !== undefined) options.saveAs = values['save-as'];
|
|
232
313
|
if (values['user-agent'] !== undefined) options.userAgent = values['user-agent'];
|
|
233
314
|
const metadata = metadataOptions(values);
|
|
@@ -241,6 +322,7 @@ function cliOptions({ values, positionals }) {
|
|
|
241
322
|
if (values['minify-html']) options.minifyHtml = true;
|
|
242
323
|
if (values['minify-css']) options.minifyCss = true;
|
|
243
324
|
if (values['no-preconnect-third-party']) options.preconnectThirdParty = false;
|
|
325
|
+
if (values['preconnect-third-party']) options.preconnectThirdParty = true;
|
|
244
326
|
if (values['preload-images']) options.preloadImages = true;
|
|
245
327
|
if (values['preload-manifest']) options.preloadManifest = true;
|
|
246
328
|
if (values.sitemap) options.sitemap = true;
|
|
@@ -256,6 +338,8 @@ function cliOptions({ values, positionals }) {
|
|
|
256
338
|
if (values['remove-attributes'] !== undefined) {
|
|
257
339
|
options.removeAttributes = splitList(values['remove-attributes']);
|
|
258
340
|
}
|
|
341
|
+
if (values['no-strip-build-origin-metadata']) options.stripBuildOriginMetadata = false;
|
|
342
|
+
if (values['prune-stale-routes']) options.pruneStaleRoutes = true;
|
|
259
343
|
if (values['ignore-https-errors']) options.ignoreHTTPSErrors = true;
|
|
260
344
|
if (values['browser-args'] !== undefined) options.browserArgs = splitList(values['browser-args']);
|
|
261
345
|
if (values.url !== undefined) options.url = values.url;
|
|
@@ -298,14 +382,14 @@ async function runInit({ values }) {
|
|
|
298
382
|
force: values.force === true,
|
|
299
383
|
});
|
|
300
384
|
log.success(`Wrote ${CONFIG_FILE}`);
|
|
301
|
-
printNextSteps({ detected, log });
|
|
385
|
+
printNextSteps({ detected, log, tag: readVersion().includes('-') ? '@next' : '' });
|
|
302
386
|
}
|
|
303
387
|
|
|
304
388
|
// Turns the flags the CLI already understands into the wizard's starting answers.
|
|
305
389
|
function defaultsFromFlags(detected, flags) {
|
|
306
390
|
return {
|
|
307
391
|
sourceDir: flags.sourceDir ?? detected.sourceDir,
|
|
308
|
-
base: flags.base ?? '/',
|
|
392
|
+
base: flags.base ?? detected.base ?? '/',
|
|
309
393
|
include: flags.include ?? ['/'],
|
|
310
394
|
crawl: flags.crawl ?? true,
|
|
311
395
|
siteUrl: flags.metadata?.siteUrl ?? '',
|
|
@@ -388,9 +472,9 @@ function validatedAnswers(answers, log) {
|
|
|
388
472
|
}
|
|
389
473
|
|
|
390
474
|
// Prints what to do next: install if needed, run it, and where the head layer goes.
|
|
391
|
-
function printNextSteps({ detected, log }) {
|
|
475
|
+
function printNextSteps({ detected, log, tag = '' }) {
|
|
392
476
|
const run = runnerCommand(detected.runner, PACKAGE_NAME);
|
|
393
|
-
const install = installCommand(detected.runner);
|
|
477
|
+
const install = installCommand(detected.runner, tag);
|
|
394
478
|
log.info('');
|
|
395
479
|
log.info('Next steps:');
|
|
396
480
|
if (!detected.installed) log.info(` Install ${install}`);
|
|
@@ -419,10 +503,11 @@ function runnerCommand(runner, binary) {
|
|
|
419
503
|
}
|
|
420
504
|
|
|
421
505
|
// Builds the command that installs this package as a dev dependency.
|
|
422
|
-
function installCommand(runner) {
|
|
423
|
-
|
|
424
|
-
if (runner === '
|
|
425
|
-
return `
|
|
506
|
+
function installCommand(runner, tag = '') {
|
|
507
|
+
const pkg = `${PACKAGE_NAME}${tag}`;
|
|
508
|
+
if (runner === 'pnpm') return `pnpm add -D ${pkg}`;
|
|
509
|
+
if (runner === 'yarn') return `yarn add -D ${pkg}`;
|
|
510
|
+
return `npm install --save-dev ${pkg}`;
|
|
426
511
|
}
|
|
427
512
|
|
|
428
513
|
// Collects the metadata flags, or null when none was passed so a config file value survives.
|
|
@@ -441,6 +526,7 @@ function metadataOptions(values) {
|
|
|
441
526
|
if (values['metadata-trailing-slash'] !== undefined) {
|
|
442
527
|
metadata.trailingSlash = values['metadata-trailing-slash'];
|
|
443
528
|
}
|
|
529
|
+
if (values['metadata-base'] !== undefined) metadata.base = values['metadata-base'];
|
|
444
530
|
return Object.keys(metadata).length > 0 ? metadata : null;
|
|
445
531
|
}
|
|
446
532
|
|