lit-ui-router-ssr 0.7.0 → 0.8.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/README.md CHANGED
@@ -24,14 +24,14 @@ imports the pre-1.0 renderer or re-derives the incantation.
24
24
  on the page carries `defer-hydration` and renders nothing until the client's walk reaches it.
25
25
  - **The hydration signature.** A page rendered from a template opens on a JSON data block,
26
26
  `<script type="application/json" data-lit-ui-router-ssr>`, naming this package's version, the
27
- state the router stood on, that state's url parameter values, and the resolved values the
28
- `payload` hook supplies. A page `renderShell` returns as a string is written as-is, without one.
27
+ state the router stood on, that state's url parameter values, and the resolved values
28
+ `dehydrate` or the `payload` hook draws. A page `renderShell` returns as a string is written as-is, without one.
29
29
  - **The lit render inside the emit loop.** The loop itself — verdict to file name, redirect to
30
30
  rules line, the `_redirects` file, delegating states skipped, tally, warnings for paths that
31
31
  matched nothing — is
32
32
  `prerender` from `ui-router-server/prerender`, which takes any render. This package's `prerender`
33
33
  supplies the lit one and takes the same options, plus `router`, `renderShell`, `document`,
34
- `root`, `elementRenderers`, `payload`, `responses` and `origin`. No SPA catch-all is ever written: one
34
+ `root`, `elementRenderers`, `dehydrate`, `payload`, `responses` and `origin`. No SPA catch-all is ever written: one
35
35
  turns every 404 into a 200.
36
36
  - **Response states after settle.** `responder` runs the handler bound to a response state's name
37
37
  with the resolves its transition landed with.
@@ -51,7 +51,8 @@ npm install lit-ui-router-ssr
51
51
  `lit-ui-router`, `ui-router-server`, `@lit-labs/ssr`, `@lit-labs/ssr-client`, `lit`, and
52
52
  `@uirouter/core` are peer dependencies. `@lit-labs/ssr` is the server half and `@lit-labs/ssr-client`
53
53
  the client half, so a bundle takes one or the other, never both. `@lit-labs/ssr-client` is optional:
54
- install it when you import `lit-ui-router-ssr/client`.
54
+ install it when you import `lit-ui-router-ssr/client`. `lit-ui-router-ssr/vite` wraps
55
+ `ui-router-server/vite`, so it takes `vite` through that package's optional peer.
55
56
 
56
57
  The `lit-ui-router` peer is `^1.19.0`: the server renders a view through the helper `<ui-view>`
57
58
  renders through, and the client hands a served class element to the view it wakes.
@@ -88,6 +89,14 @@ on. `dryRun: true` plans all of it and writes nothing.
88
89
  Files land through `node:fs`, imported lazily on first write; pass `write` to emit into memory or a
89
90
  virtual fs instead.
90
91
 
92
+ ## After `vite build`
93
+
94
+ `lit-ui-router-ssr/vite` prerenders the built app after `vite build`. Its `serverRouterPlugin` is
95
+ `ui-router-server/vite`'s, with the DOM shim and `lit-ui-router-ssr/register` loaded before the
96
+ prerender script, and its `definePrerender` calls this package's `prerender` with the options the
97
+ script returns; [Prerender with Vite](https://lit-ui-router.dev/guides/prerender-with-vite) shows
98
+ both files in full.
99
+
91
100
  ## Static hosts add a trailing slash
92
101
 
93
102
  A shell page lands at `<subpath>/index.html`. Cloudflare Pages, Netlify, and S3-style hosts serve
@@ -144,22 +153,17 @@ as `responses`, writing a 2xx body at the path its url spells or a 3xx as a rule
144
153
 
145
154
  ## Adopting the resolves on boot
146
155
 
147
- The `payload` option hands each rendered page's landed transition to a hook, which returns the
148
- resolve payload to write. `dehydrate` from `ui-router-server/ssr` draws one, and the hook decides what to do with the resolves it leaves out:
156
+ The `dehydrate` option draws each rendered page's resolve payload: `prerender` calls `dehydrate`
157
+ from `ui-router-server/ssr` on the page's landed transition with the options given, so
158
+ `shouldDehydrate` and `transport` act exactly as they do there. Pass `{}` to draw every resolve:
149
159
 
150
160
  ```typescript
151
161
  import { prerender } from 'lit-ui-router-ssr';
152
- import { dehydrate } from 'ui-router-server/ssr';
153
162
 
154
163
  await prerender({
155
164
  // mounts, router, paths, renderShell, …
156
- payload: (transition, { path }) => {
157
- const { payload, unserializable } = dehydrate(transition);
158
-
159
- for (const { state, token } of unserializable)
160
- console.warn(`${path}: ${state}.${token} re-runs on the client`);
161
-
162
- return payload;
165
+ dehydrate: {
166
+ shouldDehydrate: ({ token }) => token !== 'manifest',
163
167
  },
164
168
  });
165
169
  ```
@@ -169,15 +173,47 @@ The payload's `resolves` go into the page's hydration signature, beside `version
169
173
  the router starts: each resolve the page was drawn with adopts its value in place of running its
170
174
  function. Every other resolve runs as it would without one: a value `dehydrate` left out, a
171
175
  `NOWAIT` resolve, every resolve of a boot that lands on another state or other parameters, and
172
- every resolve of a page whose block carries no `resolves`. Without the hook no page carries
173
- resolves. What `dehydrate` draws, and which values it leaves out, is in
176
+ every resolve of a page whose block carries no `resolves`. Without `dehydrate` or a `payload` hook
177
+ no page carries resolves. What `dehydrate` draws, and which values it leaves out, is in
174
178
  [its documentation](https://lit-ui-router.dev/packages/server#handing-resolves-to-the-client).
175
179
 
176
180
  A value plain JSON can't carry, such as a `Date`, travels through a transporter. Pass the same
177
- `transport` to `dehydrate` in the hook and to `bootstrap()`: the signature keeps the payload's
178
- `transported` marks, and `bootstrap()` hands `transport` to `seed`. See
181
+ `transport` in `dehydrate` and to `bootstrap()`: the signature keeps the payload's `transported`
182
+ marks, and `bootstrap()` hands `transport` to `seed`. See
179
183
  [values beyond plain JSON](https://lit-ui-router.dev/packages/server#values-beyond-plain-json).
180
184
 
185
+ The `payload` option takes the drawing over by hand: a hook handed each page's landed transition
186
+ returns the payload to write, or `undefined` to write none. Pass one or the other; passing both
187
+ throws.
188
+
189
+ ```typescript
190
+ import { dehydrate } from 'ui-router-server/ssr';
191
+
192
+ await prerender({
193
+ // mounts, router, paths, renderShell, …
194
+ payload: (transition, { path }) =>
195
+ path.startsWith('/drafts/')
196
+ ? undefined
197
+ : dehydrate(transition, { transport }).payload,
198
+ });
199
+ ```
200
+
201
+ Each rendered page's entry in `result.pages` reports its payload as `payload`: `bytes`, what the
202
+ payload adds to the page's signature as written, and, when `dehydrate` drew it, `unserializable`,
203
+ the resolves left out to re-run on the client. A hook returns the payload alone, so behind one the
204
+ report carries `bytes` only. A page with no payload has no `payload` field.
205
+
206
+ ```typescript
207
+ const { pages } = await prerender({ /* … */ dehydrate: {} });
208
+
209
+ for (const { path, payload } of pages) {
210
+ if (!payload) continue;
211
+ console.log(`${path}: ${payload.bytes} bytes of resolves`);
212
+ for (const { state, token } of payload.unserializable ?? [])
213
+ console.warn(`${path}: ${state}.${token} re-runs on the client`);
214
+ }
215
+ ```
216
+
181
217
  ## The routed view, drawn on the server
182
218
 
183
219
  `elementRenderers` defaults to `[UiViewRenderer, LightElementRenderer]` — not `@lit-labs/ssr`'s
@@ -362,23 +398,41 @@ await bootstrap(router, root, page(router));
362
398
 
363
399
  A `<ui-view>` another class already defined throws, naming both ways out.
364
400
 
365
- The build-time entry that calls `prerender()` imports `lit-ui-router-ssr/register` too, once the
366
- DOM shim has finished installing. The shim module awaits at its top level, so a static import beside
367
- it evaluates first: lit's Node build installs a registry of its own, the elements are defined there,
368
- and the shim then replaces `globalThis.customElements` with an empty one. Import the shim
369
- statically and everything that defines or reaches an element by dynamic import:
401
+ The build-time entry that calls `prerender()` needs the DOM shim installed before anything defines
402
+ an element. The shim module awaits at its top level, so a static import beside it evaluates first:
403
+ lit's Node build installs a registry of its own, the elements are defined there, and the shim then
404
+ replaces `globalThis.customElements` with an empty one. Load the shim and `lit-ui-router-ssr/register`
405
+ before the entry starts. `serverRouterPlugin` from `lit-ui-router-ssr/vite` names both as preloads
406
+ of its prerender worker, and its `definePrerender` calls `prerender()` with the options the entry
407
+ returns:
370
408
 
371
409
  ```typescript
372
- import '@lit-labs/ssr/lib/install-global-dom-shim.js';
410
+ // vite.config.ts
411
+ import { createServerRouter } from 'ui-router-server';
412
+ import { serverRouterPlugin } from 'lit-ui-router-ssr/vite';
413
+
414
+ plugins: [
415
+ serverRouterPlugin(createServerRouter({ mounts }), {
416
+ prerender: './src/prerender.ts',
417
+ }),
418
+ ];
373
419
 
374
- await import('lit-ui-router-ssr/register');
375
- const { UIRouterLit } = await import('lit-ui-router/pure');
376
- const { prerender, settle } = await import('lit-ui-router-ssr');
377
- const { page } = await import('./views.js');
420
+ // src/prerender.ts
421
+ import { definePrerender } from 'lit-ui-router-ssr/vite';
422
+
423
+ export default definePrerender((site) => ({
424
+ mounts,
425
+ router,
426
+ paths,
427
+ renderShell,
428
+ document,
429
+ }));
378
430
  ```
379
431
 
380
- Preloading the shim with `node --import @lit-labs/ssr/lib/install-global-dom-shim.js` finishes it
381
- before the entry's graph starts, so static imports hold there too. `UiViewRenderer` draws the served
432
+ The entry then uses plain static imports; see
433
+ [Prerender with Vite](https://lit-ui-router.dev/guides/prerender-with-vite). An entry node runs
434
+ directly gets the same order from `node --import @lit-labs/ssr/lib/install-global-dom-shim.js`.
435
+ `UiViewRenderer` draws the served
382
436
  class, and a `<ui-view>` nothing defined on the server renders as an inert element with an empty
383
437
  part pair — no error, and every page's body missing. In development `prerender()` warns when the
384
438
  registry holds no `<ui-view>`.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","mappings":"AAMA,SAGE,iBAMK;AAEP,SAEE,cAEK;AAEP,SAAS,4BAA4B;AAErC,SAA8C,iBAAiB;AAQ/D,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../../src/index.ts"]}
1
+ {"version":3,"file":"index.js","mappings":"AAMA,SAIE,iBAOK;AAEP,SAEE,cAEK;AAEP,SAAS,4BAA4B;AAErC,SAA8C,iBAAiB;AAQ/D,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../../src/index.ts"]}
@@ -3,6 +3,7 @@ import { collectResultSync } from "@lit-labs/ssr/lib/render-result.js";
3
3
  import { provideRouter, withRouterSync } from "lit-ui-router/context";
4
4
  import { isString } from "@uirouter/core";
5
5
  import { prerender as prerenderSite } from "ui-router-server/prerender";
6
+ import { dehydrate as dehydrateLanded } from "ui-router-server/ssr";
6
7
  import { devGate } from "#dev-gate";
7
8
  import { LightElementRenderer } from "./light-element-renderer.js";
8
9
  import { responder } from "./respond.js";
@@ -45,6 +46,8 @@ export const signatureBlock = (signature) => {
45
46
  const json = JSON.stringify(signature).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e");
46
47
  return `<script type="application/json" ${signatureAttribute}>${json}<\/script>`;
47
48
  };
49
+ const utf8Length = (text) => new TextEncoder().encode(text).length;
50
+ const payloadBytes = (router, drawn) => utf8Length(signatureBlock(signatureOf(router, drawn))) - utf8Length(signatureBlock(signatureOf(router)));
48
51
  export const renderPage = (body, router, root, elementRenderers, drawn) => signatureBlock(signatureOf(router, drawn)) + withRouterSync(router, () => collectResultSync(render(body, {
49
52
  eventTargetStack: rootedStack(root),
50
53
  elementRenderers,
@@ -53,15 +56,32 @@ export const renderPage = (body, router, root, elementRenderers, drawn) => signa
53
56
  const warnUnregistered = () => {
54
57
  if (!devGate()) return;
55
58
  if (globalThis.customElements?.get("ui-view")) return;
56
- console.warn("lit-ui-router-ssr: <ui-view> is not defined on the current customElements registry, so every <ui-view> on these pages renders empty. The DOM shim replaces the registry when it finishes installing: load lit-ui-router-ssr/register by dynamic import after the shim, or preload the shim with node --import.");
59
+ console.warn("lit-ui-router-ssr: <ui-view> is not defined on the current customElements registry, so every <ui-view> on these pages renders empty. The DOM shim replaces the registry when it finishes installing: prerender through serverRouterPlugin from lit-ui-router-ssr/vite, which preloads both, load lit-ui-router-ssr/register by dynamic import after the shim, or preload the shim with node --import.");
57
60
  };
58
61
  export async function prerender(options) {
59
- const { router, renderShell, document, elementRenderers = [UiViewRenderer, LightElementRenderer], root = new EventTarget(), write = nodeWrite, payload, responses, origin = "http://lit-ui-router-ssr", ...site } = options;
62
+ const { router, renderShell, document, elementRenderers = [UiViewRenderer, LightElementRenderer], root = new EventTarget(), write = nodeWrite, dehydrate: dehydrateOptions, payload, responses, origin = "http://lit-ui-router-ssr", ...site } = options;
63
+ if (dehydrateOptions && payload) throw new Error("prerender: pass `dehydrate` or `payload`, not both; a `payload` hook calls `dehydrate()` itself");
60
64
  warnUnregistered();
61
- const payloadOf = (context) => {
62
- if (!payload) return void 0;
65
+ const draw = (transition, context) => {
66
+ if (dehydrateOptions) return dehydrateLanded(transition, dehydrateOptions);
67
+ return { payload: payload?.(transition, context) };
68
+ };
69
+ const drawLanded = (context) => {
70
+ if (!dehydrateOptions && !payload) return {};
63
71
  const landed = router.globals.successfulTransitions.peekTail();
64
- return landed && payload(landed, context);
72
+ return landed ? draw(landed, context) : {};
73
+ };
74
+ const reports = new Map();
75
+ const renderTemplate = (body, context) => {
76
+ const { payload: drawn, unserializable } = drawLanded(context);
77
+ if (drawn) {
78
+ const bytes = payloadBytes(router, drawn);
79
+ reports.set(context.file, unserializable ? {
80
+ bytes,
81
+ unserializable
82
+ } : { bytes });
83
+ }
84
+ return renderPage(body, router, root, elementRenderers, drawn);
65
85
  };
66
86
  const renderLit = async (verdict, siteContext) => {
67
87
  const context = {
@@ -69,7 +89,7 @@ export async function prerender(options) {
69
89
  root
70
90
  };
71
91
  const body = await renderShell(verdict, context);
72
- const markup = isString(body) ? body : renderPage(body, router, root, elementRenderers, payloadOf(context));
92
+ const markup = isString(body) ? body : renderTemplate(body, context);
73
93
  return document ? document(markup, context) : markup;
74
94
  };
75
95
  const uninstall = provideRouter(root, router);
@@ -78,13 +98,22 @@ export async function prerender(options) {
78
98
  if (responses === void 0) throw new Error(`prerender: '${path}' matched response state '${verdict.state}'; pass \`responses\` to answer it`);
79
99
  return responder(router, responses)(verdict, new Request(new URL(path, origin)));
80
100
  };
101
+ const result = await prerenderSite({
102
+ ...site,
103
+ render: renderLit,
104
+ respond,
105
+ write
106
+ });
107
+ const pages = result.pages.map((page) => {
108
+ const report = reports.get(page.file);
109
+ return report ? {
110
+ ...page,
111
+ payload: report
112
+ } : page;
113
+ });
81
114
  return {
82
- ...await prerenderSite({
83
- ...site,
84
- render: renderLit,
85
- respond,
86
- write
87
- }),
115
+ ...result,
116
+ pages,
88
117
  root
89
118
  };
90
119
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.js","mappings":"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","names":[],"ignoreList":[],"sources":["../../src/prerender.ts"]}
1
+ {"version":3,"file":"prerender.js","mappings":"AACA,SAAS,cAAc;AAEvB,SAAS,yBAAyB;AAIlC,SAAS,eAAe,sBAAsB;AAC9C,SAAS,gBAAgB;AAEzB,SAAS,aAAa,qBAAqB;AAQ3C,SAAS,aAAa,uBAAuB;AAM7C,SAAS,eAAe;AAExB,SAAS,4BAA4B;AACrC,SAAS,iBAAiB;AAE1B,SAAS,gBAAgB,SAAS,0BAA0B;AAC5D,SAAS,sBAAsB;AAc/B,MAAM,UAAU;AAWhB,MAAM,SAAS,SAAyB;CACtC,MAAM,MAAM,KAAK,YAAY,GAAG;CAEhC,OAAO,QAAQ,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG;AAC5C;AAEA,MAAM,YAAwB,OAAO,MAAM,SAAS;CAElD,MAAM,EAAE,OAAO,cAAe,MAAM,OAAO;CAC3C,MAAM,MAAM,MAAM,IAAI;CAEtB,IAAI,KAAK,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CAC7C,MAAM,UAAU,MAAM,MAAM,MAAM;AACpC;AAiKA,MAAM,eAAe,SAAqC;CAExD,MAAM,gBAAiB,WACpB;CAEH,OAAO,iBAAiB,kBAAkB,OACtC,CAAC,eAAe,IAAI,IACpB,CAAC,IAAI;AACX;AAeA,OAAO,MAAM,eACX,QACA,UACqB;CACrB,MAAM,CAAC,OAAO,UAAU,QAAQ,OAAO,OAAO;CAC9C,MAAM,YAAY;EAAE,SAAS;EAAgB;EAAO;CAAO;CAE3D,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,EAAE,UAAU,gBAAgB;CAElC,OAAO,cACH;EAAE,GAAG;EAAW;EAAU;CAAY,IACtC;EAAE,GAAG;EAAW;CAAS;AAC/B;AAOA,OAAO,MAAM,kBAAkB,cAAwC;CACrE,MAAM,OAAO,KAAK,UAAU,SAAS,CAAC,CACnC,WAAW,KAAK,SAAS,CAAC,CAC1B,WAAW,KAAK,SAAS;CAE5B,OAAO,mCAAmC,mBAAmB,GAAG,KAAK;AACvE;AAEA,MAAM,cAAc,SAClB,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;AAGjC,MAAM,gBAAgB,QAAqB,UACzC,WAAW,eAAe,YAAY,QAAQ,KAAK,CAAC,CAAC,IACrD,WAAW,eAAe,YAAY,MAAM,CAAC,CAAC;AAQhD,OAAO,MAAM,cACX,MACA,QACA,MACA,kBACA,UAEA,eAAe,YAAY,QAAQ,KAAK,CAAC,IACzC,eAAe,cACb,kBACE,OAAO,MAAM;CAEX,kBAAkB,YAAY,IAAI;CAClC;CAEA,gBAAgB;AAClB,CAAC,CACH,CACF;AAEF,MAAM,yBAA+B;CAEnC,IAA4B,CAAC,QAAQ,GAAG;CAExC,IAAI,WAAW,gBAAgB,IAAI,SAAS,GAAG;CAC/C,QAAQ,KACN,uYAEF;AACF;AAuDA,OAAO,eAAe,UACpB,SAC0B;CAC1B,MAAM,EACJ,QACA,aACA,UACA,mBAAmB,CAAC,gBAAgB,oBAAoB,GACxD,OAAO,IAAI,YAAY,GACvB,QAAQ,WACR,WAAW,kBACX,SACA,WACA,SAAS,4BACT,GAAG,SACD;CAEJ,IAAI,oBAAoB,SACtB,MAAM,IAAI,MACR,iGACF;CAEF,iBAAiB;CAEjB,MAAM,QACJ,YACA,YAC6B;EAC7B,IAAI,kBAAkB,OAAO,gBAAgB,YAAY,gBAAgB;EAEzE,OAAO,EAAE,SAAS,UAAU,YAAY,OAAO,EAAE;CACnD;CAEA,MAAM,cAAc,YAAqD;EACvE,IAAI,CAAC,oBAAoB,CAAC,SAAS,OAAO,CAAC;EAG3C,MAAM,SAAS,OAAO,QAAQ,sBAAsB,SAAS;EAI7D,OAAO,SAAS,KAAK,QAAQ,OAAO,IAAI,CAAC;CAC3C;CAEA,MAAM,UAAU,IAAI,IAAyB;CAE7C,MAAM,kBACJ,MACA,YACW;EACX,MAAM,EAAE,SAAS,OAAO,mBAAmB,WAAW,OAAO;EAE7D,IAAI,OAAO;GACT,MAAM,QAAQ,aAAa,QAAQ,KAAK;GAExC,QAAQ,IACN,QAAQ,MACR,iBAAiB;IAAE;IAAO;GAAe,IAAI,EAAE,MAAM,CACvD;EACF;EAEA,OAAO,WAAW,MAAM,QAAQ,MAAM,kBAAkB,KAAK;CAC/D;CAEA,MAAM,YAAY,OAChB,SACA,gBACoB;EACpB,MAAM,UAAyB;GAAE,GAAG;GAAa;EAAK;EACtD,MAAM,OAAO,MAAM,YAAY,SAAS,OAAO;EAE/C,MAAM,SAAS,SAAS,IAAI,IAAI,OAAO,eAAe,MAAM,OAAO;EAEnE,OAAO,WAAW,SAAS,QAAQ,OAAO,IAAI;CAChD;CAEA,MAAM,YAAY,cAAc,MAAM,MAAM;CAE5C,IAAI;EACF,MAAM,WACJ,SACA,EAAE,WACoB;GACtB,IAAI,cAAc,QAChB,MAAM,IAAI,MACR,eAAe,KAAK,4BAA4B,QAAQ,MAAM,mCAChE;GAEF,OAAO,UAAU,QAAQ,SAAS,CAAC,CACjC,SACA,IAAI,QAAQ,IAAI,IAAI,MAAM,MAAM,CAAC,CACnC;EACF;EAEA,MAAM,SAAS,MAAM,cAAc;GACjC,GAAG;GACH,QAAQ;GACR;GACA;EACF,CAAC;EAED,MAAM,QAAQ,OAAO,MAAM,KAAK,SAA0B;GACxD,MAAM,SAAS,QAAQ,IAAI,KAAK,IAAI;GAEpC,OAAO,SAAS;IAAE,GAAG;IAAM,SAAS;GAAO,IAAI;EACjD,CAAC;EAED,OAAO;GAAE,GAAG;GAAQ;GAAO;EAAK;CAClC,UAAU;EACR,UAAU;CACZ;AACF","names":[],"ignoreList":[],"sources":["../../src/prerender.ts"]}
@@ -1,7 +1,7 @@
1
1
  import { DefType } from "@uirouter/core";
2
2
  export const signatureAttribute = "data-lit-ui-router-ssr";
3
3
  export const signatureSelector = `script[type="application/json"][${signatureAttribute}]`;
4
- export const packageVersion = "0.7.0";
4
+ export const packageVersion = "0.8.0";
5
5
  export const urlParamsOf = (state, params) => Object.fromEntries(state.parameters().flatMap((param) => param.location === DefType.CONFIG ? [] : [[param.id, param.type.encode(params[param.id])]]));
6
6
  export const routeOf = ({ $current, params }) => [$current.name, urlParamsOf($current, params)];
7
7
  //# sourceMappingURL=signature.js.map
@@ -0,0 +1,7 @@
1
+ export const DOM_SHIM = ["@lit-labs/ssr/lib/install-global-dom-shim.js"];
2
+ export const litPreload = (shim, preload) => [...new Set([
3
+ ...shim,
4
+ "lit-ui-router-ssr/register",
5
+ ...preload
6
+ ])];
7
+ //# sourceMappingURL=vite-preload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-preload.js","mappings":"AACA,OAAO,MAAM,WAA8B,CACzC,8CACF;AASA,OAAO,MAAM,cACX,MACA,YACa,CACb,GAAG,IAAI,IAAI;CAAC,GAAG;CAAM;CAA8B,GAAG;AAAO,CAAC,CAChE","names":[],"ignoreList":[],"sources":["../../src/vite-preload.ts"]}
@@ -0,0 +1,27 @@
1
+ import { serverRouterPlugin as siteRouterPlugin } from "ui-router-server/vite";
2
+ import { DOM_SHIM, litPreload } from "./vite-preload.js";
3
+ const loadPrerender = async () => (await import("./prerender.js")).prerender;
4
+ export function definePrerender(script) {
5
+ return async (build) => {
6
+ const [options, prerender] = await Promise.all([script(build), loadPrerender()]);
7
+ const { root: _root, ...result } = await prerender({
8
+ ...build,
9
+ ...options
10
+ });
11
+ return result;
12
+ };
13
+ }
14
+ export function serverRouterPlugin(router, options = {}) {
15
+ const { shim = DOM_SHIM, ...siteOptions } = options;
16
+ const { prerender } = siteOptions;
17
+ if (prerender === void 0) return siteRouterPlugin(router, siteOptions);
18
+ const { entry, preload = [] } = typeof prerender === "string" ? { entry: prerender } : prerender;
19
+ return siteRouterPlugin(router, {
20
+ ...siteOptions,
21
+ prerender: {
22
+ entry,
23
+ preload: litPreload(shim, preload)
24
+ }
25
+ });
26
+ }
27
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","mappings":"AAcA,SAAS,sBAAsB,wBAAwB;AAUvD,SAAS,UAAU,kBAAkB;AA6CrC,MAAM,gBAAgB,aAAa,MAAM,OAAO,kBAAiB,CAAE;AAyBnE,OAAO,SAAS,gBAAgB,QAAyC;CACvE,OAAO,OAAO,UAAU;EACtB,MAAM,CAAC,SAAS,aAAa,MAAM,QAAQ,IAAI,CAC7C,OAAO,KAAK,GACZ,cAAc,CAChB,CAAC;EAGD,MAAM,EAAE,MAAM,OAAO,GAAG,WAAW,MAAM,UAAU;GACjD,GAAG;GACH,GAAG;EACL,CAAC;EAED,OAAO;CACT;AACF;AA+BA,OAAO,SAAS,mBACd,QACA,UAAqC,CAAC,GAClB;CACpB,MAAM,EAAE,OAAO,UAAU,GAAG,gBAAgB;CAC5C,MAAM,EAAE,cAAc;CAEtB,IAAI,cAAc,QAAW,OAAO,iBAAiB,QAAQ,WAAW;CAExE,MAAM,EAAE,OAAO,UAAU,CAAC,MAExB,OAAO,cAAc,WAAW,EAAE,OAAO,UAAU,IAAI;CAEzD,OAAO,iBAAiB,QAAQ;EAC9B,GAAG;EACH,WAAW;GAAE;GAAO,SAAS,WAAW,MAAM,OAAO;EAAE;CACzD,CAAC;AACH","names":[],"ignoreList":[],"sources":["../../src/vite.ts"]}
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @module
6
6
  */
7
- export { type EmittedPage, type FileWriter, prerender, type PrerenderOptions, type PrerenderResult, type PrerenderTally, type RedirectLine, type RenderContext } from "./prerender.js";
7
+ export { type EmittedPage, type FileWriter, type PagePayload, prerender, type PrerenderedPage, type PrerenderOptions, type PrerenderResult, type PrerenderTally, type RedirectLine, type RenderContext } from "./prerender.js";
8
8
  export { type ResolvePayload, settle, type SettleOptions } from "ui-router-server/ssr";
9
9
  export { LightElementRenderer } from "./light-element-renderer.js";
10
10
  export { type BoundResponses, type Responder, responder } from "./respond.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","mappings":";;;;;;AAMA,cACO,kBACA,YACL,gBACK,uBACA,sBACA,qBACA,mBACA,qBACA;AAEP,cACO,gBACL,aACK,qBACA;AAEP,SAAS,4BAA4B;AAErC,cAAc,qBAAqB,WAAW,iBAAiB;AAE/D,cACE,iBACA,iBACA,wBACK;AAEP,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
1
+ {"version":3,"file":"index.d.ts","mappings":";;;;;;AAMA,cACO,kBACA,iBACA,aACL,gBACK,sBACA,uBACA,sBACA,qBACA,mBACA,qBACA;AAEP,cACO,gBACL,aACK,qBACA;AAEP,SAAS,4BAA4B;AAErC,cAAc,qBAAqB,WAAW,iBAAiB;AAE/D,cACE,iBACA,iBACA,wBACK;AAEP,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","mappings":"AAMA,SAGE,iBAMK;AAEP,SAEE,cAEK;AAEP,SAAS,4BAA4B;AAErC,SAA8C,iBAAiB;AAQ/D,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
1
+ {"version":3,"file":"index.js","mappings":"AAMA,SAIE,iBAOK;AAEP,SAEE,cAEK;AAEP,SAAS,4BAA4B;AAErC,SAA8C,iBAAiB;AAQ/D,SAAS,sBAAsB","names":[],"ignoreList":[],"sources":["../src/index.ts"]}
@@ -3,8 +3,8 @@ import type { TemplateResult } from "lit";
3
3
  import type { Transition } from "@uirouter/core";
4
4
  import type { UIRouterLit } from "lit-ui-router";
5
5
  import type { Verdict } from "ui-router-server";
6
- import type { FileWriter, PrerenderOptions as SitePrerenderOptions, PrerenderResult as SitePrerenderResult, RenderContext as SiteRenderContext } from "ui-router-server/prerender";
7
- import type { ResolvePayload } from "ui-router-server/ssr";
6
+ import type { EmittedPage, FileWriter, PrerenderOptions as SitePrerenderOptions, PrerenderResult as SitePrerenderResult, RenderContext as SiteRenderContext } from "ui-router-server/prerender";
7
+ import type { DehydrateOptions, DehydrateResult, ResolvePayload } from "ui-router-server/ssr";
8
8
  import type { BoundResponses } from "./respond.js";
9
9
  export type { EmittedPage, FileWriter, PrerenderTally, RedirectLine } from "ui-router-server/prerender";
10
10
  type ShellVerdict = Extract<Verdict, {
@@ -79,32 +79,78 @@ export interface PrerenderOptions extends Omit<SitePrerenderOptions, "render" |
79
79
  /** Writes a file. Defaults to `node:fs`, imported lazily on first write. */
80
80
  write?: FileWriter;
81
81
  /**
82
- * Draws the resolve payload for a rendered page, handed the transition the
82
+ * Draws each rendered page's resolve payload with `dehydrate` from
83
+ * `ui-router-server/ssr`, called with these options on the transition the
83
84
  * router last landed, once `renderShell` has returned a template. The
84
85
  * payload's `resolves` go into the page's hydration signature, beside the
85
86
  * state and parameters the router stands on, and the client adopts them on
86
- * boot in place of running those resolve functions. Return `undefined` to
87
- * write none. Without this hook no page carries resolves.
87
+ * boot in place of running those resolve functions. Pass `{}` to draw every
88
+ * resolve `dehydrate` draws by default. Without this option or
89
+ * {@link PrerenderOptions.payload | `payload`}, no page carries resolves.
88
90
  *
89
- * `dehydrate` from `ui-router-server/ssr` draws the payload; its
90
- * documentation says which values it leaves out. The payload's
91
+ * `dehydrate`'s documentation says which values it leaves out and how
92
+ * `shouldDehydrate` and `transport` act. The payload's
91
93
  * `transported` marks go into the signature too, so a value a transporter
92
94
  * carried is rebuilt when `bootstrap()` gets the same `transport`.
93
95
  *
94
96
  * @example
95
97
  * ```ts
96
- * payload: (transition, { path }) => {
97
- * const { payload, unserializable } = dehydrate(transition);
98
- * for (const { state, token } of unserializable)
99
- * console.warn(`${path}: ${state}.${token} re-runs on the client`);
100
- * return payload;
98
+ * dehydrate: {
99
+ * shouldDehydrate: ({ token }) => token !== 'manifest',
100
+ * transport,
101
101
  * },
102
102
  * ```
103
103
  */
104
+ dehydrate?: DehydrateOptions;
105
+ /**
106
+ * Draws the resolve payload for a rendered page by hand, in place of
107
+ * {@link PrerenderOptions.dehydrate | `dehydrate`}: handed the transition the
108
+ * router last landed, once `renderShell` has returned a template, it returns
109
+ * the payload the page's signature carries, or `undefined` to write none.
110
+ * Passing both this and `dehydrate` throws.
111
+ *
112
+ * @example
113
+ * Shape the payload `dehydrate` draws before it is written:
114
+ * ```ts
115
+ * import { dehydrate } from 'ui-router-server/ssr';
116
+ *
117
+ * payload: (transition, { path }) =>
118
+ * path.startsWith('/drafts/')
119
+ * ? undefined
120
+ * : dehydrate(transition, { transport }).payload,
121
+ * ```
122
+ */
104
123
  payload?: (transition: Transition, context: RenderContext) => ResolvePayload | undefined;
105
124
  }
125
+ /** What a page's hydration signature carries of its resolve payload. */
126
+ export interface PagePayload {
127
+ /**
128
+ * The bytes the payload adds to the page's signature as written: its
129
+ * `resolves` and `transported` members, escaped as the block spells them.
130
+ */
131
+ bytes: number;
132
+ /**
133
+ * The resolves {@link PrerenderOptions.dehydrate | `dehydrate`} left out of
134
+ * the payload because their value is not plain JSON data and no transporter
135
+ * carries it, each to re-run on the client. Present only when `dehydrate`
136
+ * drew the payload: a {@link PrerenderOptions.payload | `payload`} hook
137
+ * returns the payload alone.
138
+ */
139
+ unserializable?: DehydrateResult["unserializable"];
140
+ }
141
+ /** A page {@link prerender} emitted. */
142
+ export interface PrerenderedPage extends EmittedPage {
143
+ /**
144
+ * What the page's signature carries of its resolve payload. Absent on a page
145
+ * that carries none: one drawn with no payload, one `renderShell` returned
146
+ * as a string, and a response state's body.
147
+ */
148
+ payload?: PagePayload;
149
+ }
106
150
  /** What {@link prerender} did. */
107
- export interface PrerenderResult extends SitePrerenderResult {
151
+ export interface PrerenderResult extends Omit<SitePrerenderResult, "pages"> {
152
+ /** Every page emitted or planned, in enumeration order. */
153
+ pages: PrerenderedPage[];
108
154
  /** The root event target, created or passed through. */
109
155
  root: EventTarget;
110
156
  }
@@ -150,9 +196,12 @@ export declare const renderPage: (body: TemplateResult, router: UIRouterLit, roo
150
196
  * A rendered page opens on its hydration signature: a JSON data block,
151
197
  * `<script type="application/json" data-lit-ui-router-ssr>`, naming this
152
198
  * package's version and the state and parameter values the router stood on,
153
- * which `hydrateRoot()` reads before it touches the document. A
154
- * {@link PrerenderOptions.payload | `payload`} hook adds the resolved values
155
- * the page was drawn with, which `bootstrap()` adopts. A `renderShell`
199
+ * which `hydrateRoot()` reads before it touches the document.
200
+ * {@link PrerenderOptions.dehydrate | `dehydrate`}, or a
201
+ * {@link PrerenderOptions.payload | `payload`} hook, adds the resolved values
202
+ * the page was drawn with, which `bootstrap()` adopts; the page's entry in
203
+ * the result's `pages` reports the bytes they add and, from `dehydrate`, the
204
+ * resolves left to re-run on the client. A `renderShell`
156
205
  * that returns a string is written as-is, with no signature, so the client
157
206
  * renders it cold.
158
207
  *
@@ -166,6 +215,7 @@ export declare const renderPage: (body: TemplateResult, router: UIRouterLit, roo
166
215
  * @returns what was emitted, planned or written
167
216
  * @throws an `Error` when neither `mounts` nor `resolver` is supplied
168
217
  * @throws an `Error` when the 404 document needs a default probe and only a `resolver` is supplied
218
+ * @throws an `Error` when both `dehydrate` and `payload` are supplied
169
219
  *
170
220
  * @example
171
221
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.d.ts","mappings":"AAEA,cAAc,kBAAkB;AAEhC,cAAc,sBAAsB;AACpC,cAAc,kBAAkB;AAChC,cAAc,mBAAmB;AAGjC,cAAc,eAAe;AAE7B,cACE,YACA,oBAAoB,sBACpB,mBAAmB,qBACnB,iBAAiB,yBACZ;AACP,cAAc,sBAAsB;AAKpC,cAAc,sBAAsB;AAIpC,cACE,aACA,YACA,gBACA,oBACK;KAEF,eAAe,QAAQ,SAAS;CAAE,MAAM;AAAQ;;AAgCrD,iBAAiB,sBAAsB,kBAAkB;;CAEvD,MAAM;AACR;;;;;AAMA,iBAAiB,yBAAyB,KACxC,sBACA,WAAW,YAAY,SACvB;;;;;;CAMA,QAAQ;;;;;;;CAOR,cACE,SAAS,cACT,SAAS,kBACN,0BAA0B,QAAQ;;;;;;CAMvC,YAAY,cAAc,SAAS,2BAA2B;;;;;;;;CAQ9D,OAAO;;;;;;;;;;;CAWP,mBAAmB,WAAW;;;;;;;;;;CAU9B,YAAY;;;;;;;;CAQZ;;CAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,WACE,YAAY,YACZ,SAAS,kBACN;AACP;;AAGA,iBAAiB,wBAAwB,oBAAoB;;CAE3D,MAAM;AACR;;UAcU,iBAAiB;UAChB;UACA;UACA,QAAQ,SAAS;UACjB,WAAW,eAAe;UAC1B,cAAc,eAAe;AACxC;;KAGK,gBAAgB,KAAK,gBAAgB,aAAa;;AAGvD,OAAO,cAAM,cACX,QAAQ,aACR,QAAQ,kBACP;;;;;;AAkBH,OAAO,cAAM,iBAAkB,WAAW;;;;;;;AAc1C,OAAO,cAAM,aACX,MAAM,gBACN,QAAQ,aACR,MAAM,aACN,kBAAkB,WAAW,qBAC7B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EV,OAAO,iBAAe,UACpB,SAAS,mBACR,QAAQ","names":[],"ignoreList":[],"sources":["../src/prerender.ts"]}
1
+ {"version":3,"file":"prerender.d.ts","mappings":"AAEA,cAAc,kBAAkB;AAEhC,cAAc,sBAAsB;AACpC,cAAc,kBAAkB;AAChC,cAAc,mBAAmB;AAGjC,cAAc,eAAe;AAE7B,cACE,aACA,YACA,oBAAoB,sBACpB,mBAAmB,qBACnB,iBAAiB,yBACZ;AAEP,cACE,kBACA,iBACA,sBACK;AAKP,cAAc,sBAAsB;AAIpC,cACE,aACA,YACA,gBACA,oBACK;KAEF,eAAe,QAAQ,SAAS;CAAE,MAAM;AAAQ;;AAgCrD,iBAAiB,sBAAsB,kBAAkB;;CAEvD,MAAM;AACR;;;;;AAMA,iBAAiB,yBAAyB,KACxC,sBACA,WAAW,YAAY,SACvB;;;;;;CAMA,QAAQ;;;;;;;CAOR,cACE,SAAS,cACT,SAAS,kBACN,0BAA0B,QAAQ;;;;;;CAMvC,YAAY,cAAc,SAAS,2BAA2B;;;;;;;;CAQ9D,OAAO;;;;;;;;;;;CAWP,mBAAmB,WAAW;;;;;;;;;;CAU9B,YAAY;;;;;;;;CAQZ;;CAEA,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,YAAY;;;;;;;;;;;;;;;;;;;CAmBZ,WACE,YAAY,YACZ,SAAS,kBACN;AACP;;AAGA,iBAAiB,YAAY;;;;;CAK3B;;;;;;;;CAQA,iBAAiB,gBAAgB;AACnC;;AAGA,iBAAiB,wBAAwB,YAAY;;;;;;CAMnD,UAAU;AACZ;;AAGA,iBAAiB,wBAAwB,KAAK,qBAAqB,SAAS;;CAE1E,OAAO;;CAEP,MAAM;AACR;;UAcU,iBAAiB;UAChB;UACA;UACA,QAAQ,SAAS;UACjB,WAAW,eAAe;UAC1B,cAAc,eAAe;AACxC;;KAGK,gBAAgB,KAAK,gBAAgB,aAAa;;AAGvD,OAAO,cAAM,cACX,QAAQ,aACR,QAAQ,kBACP;;;;;;AAkBH,OAAO,cAAM,iBAAkB,WAAW;;;;;;;AAsB1C,OAAO,cAAM,aACX,MAAM,gBACN,QAAQ,aACR,MAAM,aACN,kBAAkB,WAAW,qBAC7B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EV,OAAO,iBAAe,UACpB,SAAS,mBACR,QAAQ","names":[],"ignoreList":[],"sources":["../src/prerender.ts"]}
package/dist/prerender.js CHANGED
@@ -3,6 +3,7 @@ import { collectResultSync } from "@lit-labs/ssr/lib/render-result.js";
3
3
  import { provideRouter, withRouterSync } from "lit-ui-router/context";
4
4
  import { isString } from "@uirouter/core";
5
5
  import { prerender as prerenderSite } from "ui-router-server/prerender";
6
+ import { dehydrate as dehydrateLanded } from "ui-router-server/ssr";
6
7
  import "#dev-gate";
7
8
  import { LightElementRenderer } from "./light-element-renderer.js";
8
9
  import { responder } from "./respond.js";
@@ -45,17 +46,36 @@ export const signatureBlock = (signature) => {
45
46
  const json = JSON.stringify(signature).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e");
46
47
  return `<script type="application/json" ${signatureAttribute}>${json}<\/script>`;
47
48
  };
49
+ const utf8Length = (text) => new TextEncoder().encode(text).length;
50
+ const payloadBytes = (router, drawn) => utf8Length(signatureBlock(signatureOf(router, drawn))) - utf8Length(signatureBlock(signatureOf(router)));
48
51
  export const renderPage = (body, router, root, elementRenderers, drawn) => signatureBlock(signatureOf(router, drawn)) + withRouterSync(router, () => collectResultSync(render(body, {
49
52
  eventTargetStack: rootedStack(root),
50
53
  elementRenderers,
51
54
  deferHydration: true
52
55
  })));
53
56
  export async function prerender(options) {
54
- const { router, renderShell, document, elementRenderers = [UiViewRenderer, LightElementRenderer], root = new EventTarget(), write = nodeWrite, payload, responses, origin = "http://lit-ui-router-ssr", ...site } = options;
55
- const payloadOf = (context) => {
56
- if (!payload) return void 0;
57
+ const { router, renderShell, document, elementRenderers = [UiViewRenderer, LightElementRenderer], root = new EventTarget(), write = nodeWrite, dehydrate: dehydrateOptions, payload, responses, origin = "http://lit-ui-router-ssr", ...site } = options;
58
+ if (dehydrateOptions && payload) throw new Error("prerender: pass `dehydrate` or `payload`, not both; a `payload` hook calls `dehydrate()` itself");
59
+ const draw = (transition, context) => {
60
+ if (dehydrateOptions) return dehydrateLanded(transition, dehydrateOptions);
61
+ return { payload: payload?.(transition, context) };
62
+ };
63
+ const drawLanded = (context) => {
64
+ if (!dehydrateOptions && !payload) return {};
57
65
  const landed = router.globals.successfulTransitions.peekTail();
58
- return landed && payload(landed, context);
66
+ return landed ? draw(landed, context) : {};
67
+ };
68
+ const reports = new Map();
69
+ const renderTemplate = (body, context) => {
70
+ const { payload: drawn, unserializable } = drawLanded(context);
71
+ if (drawn) {
72
+ const bytes = payloadBytes(router, drawn);
73
+ reports.set(context.file, unserializable ? {
74
+ bytes,
75
+ unserializable
76
+ } : { bytes });
77
+ }
78
+ return renderPage(body, router, root, elementRenderers, drawn);
59
79
  };
60
80
  const renderLit = async (verdict, siteContext) => {
61
81
  const context = {
@@ -63,7 +83,7 @@ export async function prerender(options) {
63
83
  root
64
84
  };
65
85
  const body = await renderShell(verdict, context);
66
- const markup = isString(body) ? body : renderPage(body, router, root, elementRenderers, payloadOf(context));
86
+ const markup = isString(body) ? body : renderTemplate(body, context);
67
87
  return document ? document(markup, context) : markup;
68
88
  };
69
89
  const uninstall = provideRouter(root, router);
@@ -72,13 +92,22 @@ export async function prerender(options) {
72
92
  if (responses === void 0) throw new Error(`prerender: '${path}' matched response state '${verdict.state}'; pass \`responses\` to answer it`);
73
93
  return responder(router, responses)(verdict, new Request(new URL(path, origin)));
74
94
  };
95
+ const result = await prerenderSite({
96
+ ...site,
97
+ render: renderLit,
98
+ respond,
99
+ write
100
+ });
101
+ const pages = result.pages.map((page) => {
102
+ const report = reports.get(page.file);
103
+ return report ? {
104
+ ...page,
105
+ payload: report
106
+ } : page;
107
+ });
75
108
  return {
76
- ...await prerenderSite({
77
- ...site,
78
- render: renderLit,
79
- respond,
80
- write
81
- }),
109
+ ...result,
110
+ pages,
82
111
  root
83
112
  };
84
113
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.js","mappings":"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","names":[],"ignoreList":[],"sources":["../src/prerender.ts"]}
1
+ {"version":3,"file":"prerender.js","mappings":"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","names":[],"ignoreList":[],"sources":["../src/prerender.ts"]}
package/dist/signature.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DefType } from "@uirouter/core";
2
2
  export const signatureAttribute = "data-lit-ui-router-ssr";
3
3
  export const signatureSelector = `script[type="application/json"][${signatureAttribute}]`;
4
- export const packageVersion = "0.7.0";
4
+ export const packageVersion = "0.8.0";
5
5
  export const urlParamsOf = (state, params) => Object.fromEntries(state.parameters().flatMap((param) => param.location === DefType.CONFIG ? [] : [[param.id, param.type.encode(params[param.id])]]));
6
6
  export const routeOf = ({ $current, params }) => [$current.name, urlParamsOf($current, params)];
7
7
  //# sourceMappingURL=signature.js.map
@@ -0,0 +1,11 @@
1
+ /** `@lit-labs/ssr`'s DOM shim: the default for `serverRouterPlugin`'s `shim`. @internal */
2
+ export declare const DOM_SHIM: readonly string[];
3
+ /**
4
+ * The worker's preloads for a Lit app: the shim modules, then
5
+ * `lit-ui-router-ssr/register` in the registry they installed, then the app's
6
+ * own, each once, at its first position.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare const litPreload: (shim: readonly string[], preload: readonly string[]) => string[];
11
+ //# sourceMappingURL=vite-preload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-preload.d.ts","mappings":";AACA,OAAO,cAAM;;;;;;;;AAWb,OAAO,cAAM,aACX,yBACA","names":[],"ignoreList":[],"sources":["../src/vite-preload.ts"]}
@@ -0,0 +1,7 @@
1
+ export const DOM_SHIM = ["@lit-labs/ssr/lib/install-global-dom-shim.js"];
2
+ export const litPreload = (shim, preload) => [...new Set([
3
+ ...shim,
4
+ "lit-ui-router-ssr/register",
5
+ ...preload
6
+ ])];
7
+ //# sourceMappingURL=vite-preload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-preload.js","mappings":"AACA,OAAO,MAAM,WAA8B,CACzC,8CACF;AASA,OAAO,MAAM,cACX,MACA,YACa,CACb,GAAG,IAAI,IAAI;CAAC,GAAG;CAAM;CAA8B,GAAG;AAAO,CAAC,CAChE","names":[],"ignoreList":[],"sources":["../src/vite-preload.ts"]}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ import type { PrerenderBuild, PrerenderEntry, ServerRouterPlugin, ServerRouterPluginOptions as SiteRouterPluginOptions } from "ui-router-server/vite";
2
+ import type { ServerRouter } from "ui-router-server";
3
+ import type { PrerenderOptions } from "./prerender.js";
4
+ export type { PrerenderBuild, PrerenderEntry, ServerRouterPlugin } from "ui-router-server/vite";
5
+ /**
6
+ * `ui-router-server/vite`'s plugin options, plus the DOM the prerender
7
+ * worker installs for Lit.
8
+ */
9
+ export interface ServerRouterPluginOptions extends SiteRouterPluginOptions {
10
+ /**
11
+ * Modules that install a DOM in the prerender worker, loaded in order
12
+ * before `lit-ui-router-ssr/register` and any `preload`. Defaults to
13
+ * `@lit-labs/ssr`'s DOM shim. List another DOM's setup to use it
14
+ * instead, or pass `[]` to install none: Lit's Node build still renders,
15
+ * but app code that reads `document` or `window` fails.
16
+ */
17
+ shim?: readonly string[];
18
+ }
19
+ /**
20
+ * What a prerender script's callback returns: this package's
21
+ * {@link PrerenderOptions}, less what the build supplies. `outDir` and
22
+ * `write` come from the build, so a script may still pass either to
23
+ * override it.
24
+ */
25
+ export type PrerenderScriptOptions = Omit<PrerenderOptions, "outDir" | "write"> & Partial<Pick<PrerenderOptions, "outDir" | "write">>;
26
+ /**
27
+ * The callback {@link definePrerender} takes: handed the build's values,
28
+ * such as `shellHtml` for a `document` hook, it returns the prerender
29
+ * options.
30
+ */
31
+ export type PrerenderScript = (build: PrerenderBuild) => PrerenderScriptOptions | Promise<PrerenderScriptOptions>;
32
+ /**
33
+ * Turns a callback that returns this package's prerender options into a
34
+ * prerender script's default export. The export spreads the build's values
35
+ * (`outDir`, `shellHtml`, `write`) under the options and calls this
36
+ * package's `prerender()`, so the script never calls it itself.
37
+ *
38
+ * @param script - returns the prerender options; handed the build's values
39
+ * @returns the default export for the prerender script
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * export default definePrerender((site) => ({
44
+ * mounts,
45
+ * router,
46
+ * paths: ['/', '/about'],
47
+ * renderShell: async (_verdict, { path }) => {
48
+ * await settle(router, path);
49
+ * return page(router);
50
+ * },
51
+ * document: (body) => site.shellHtml.replace('<!--app-->', body),
52
+ * }));
53
+ * ```
54
+ */
55
+ export declare function definePrerender(script: PrerenderScript): PrerenderEntry;
56
+ /**
57
+ * `ui-router-server/vite`'s `serverRouterPlugin`, set up for a Lit app: with
58
+ * {@link ServerRouterPluginOptions.prerender | prerender}, the worker loads
59
+ * the {@link ServerRouterPluginOptions.shim | shim}, then
60
+ * `lit-ui-router-ssr/register`, then any `preload` passed here, each once,
61
+ * before the prerender script, so the script imports everything statically. Its default export
62
+ * comes from this module's {@link definePrerender}.
63
+ *
64
+ * `vite dev` and `vite preview` behave as under `ui-router-server/vite`.
65
+ *
66
+ * @param router - the server router the middleware and the build step read
67
+ * @param options - the Connect adapter's options, plus the build step
68
+ * @returns a plugin to spread into `plugins: [...]`
69
+ * @throws from the build step when the script has no default export
70
+ * function, the built `index.html` is missing, or a listed path matches no
71
+ * route
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { createServerRouter } from 'ui-router-server';
76
+ * import { serverRouterPlugin } from 'lit-ui-router-ssr/vite';
77
+ *
78
+ * plugins: [
79
+ * serverRouterPlugin(createServerRouter({ mounts }), {
80
+ * prerender: './src/prerender.ts',
81
+ * }),
82
+ * ],
83
+ * ```
84
+ */
85
+ export declare function serverRouterPlugin(router: ServerRouter, options?: ServerRouterPluginOptions): ServerRouterPlugin;
86
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","mappings":"AAeA,cACE,gBACA,gBACA,oBACA,6BAA6B,+BACxB;AACP,cAAc,oBAAoB;AAElC,cAAc,wBAAwB;AAGtC,cACE,gBACA,gBACA,0BACK;;;;;AAMP,iBAAiB,kCAAkC,wBAAwB;;;;;;;;CAQzE;AACF;;;;;;;AAQA,YAAY,yBAAyB,KACnC,kBACA,WAAW,WAEX,QAAQ,KAAK,kBAAkB,WAAW;;;;;;AAO5C,YAAY,mBACV,OAAO,mBACJ,yBAAyB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;AA4BtC,OAAO,iBAAS,gBAAgB,QAAQ,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8C1D,OAAO,iBAAS,mBACd,QAAQ,cACR,UAAS,4BACR","names":[],"ignoreList":[],"sources":["../src/vite.ts"]}
package/dist/vite.js ADDED
@@ -0,0 +1,27 @@
1
+ import { serverRouterPlugin as siteRouterPlugin } from "ui-router-server/vite";
2
+ import { DOM_SHIM, litPreload } from "./vite-preload.js";
3
+ const loadPrerender = async () => (await import("./prerender.js")).prerender;
4
+ export function definePrerender(script) {
5
+ return async (build) => {
6
+ const [options, prerender] = await Promise.all([script(build), loadPrerender()]);
7
+ const { root: _root, ...result } = await prerender({
8
+ ...build,
9
+ ...options
10
+ });
11
+ return result;
12
+ };
13
+ }
14
+ export function serverRouterPlugin(router, options = {}) {
15
+ const { shim = DOM_SHIM, ...siteOptions } = options;
16
+ const { prerender } = siteOptions;
17
+ if (prerender === void 0) return siteRouterPlugin(router, siteOptions);
18
+ const { entry, preload = [] } = typeof prerender === "string" ? { entry: prerender } : prerender;
19
+ return siteRouterPlugin(router, {
20
+ ...siteOptions,
21
+ prerender: {
22
+ entry,
23
+ preload: litPreload(shim, preload)
24
+ }
25
+ });
26
+ }
27
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","mappings":"AAcA,SAAS,sBAAsB,wBAAwB;AAUvD,SAAS,UAAU,kBAAkB;AA6CrC,MAAM,gBAAgB,aAAa,MAAM,OAAO,kBAAiB,CAAE;AAyBnE,OAAO,SAAS,gBAAgB,QAAyC;CACvE,OAAO,OAAO,UAAU;EACtB,MAAM,CAAC,SAAS,aAAa,MAAM,QAAQ,IAAI,CAC7C,OAAO,KAAK,GACZ,cAAc,CAChB,CAAC;EAGD,MAAM,EAAE,MAAM,OAAO,GAAG,WAAW,MAAM,UAAU;GACjD,GAAG;GACH,GAAG;EACL,CAAC;EAED,OAAO;CACT;AACF;AA+BA,OAAO,SAAS,mBACd,QACA,UAAqC,CAAC,GAClB;CACpB,MAAM,EAAE,OAAO,UAAU,GAAG,gBAAgB;CAC5C,MAAM,EAAE,cAAc;CAEtB,IAAI,cAAc,QAAW,OAAO,iBAAiB,QAAQ,WAAW;CAExE,MAAM,EAAE,OAAO,UAAU,CAAC,MAExB,OAAO,cAAc,WAAW,EAAE,OAAO,UAAU,IAAI;CAEzD,OAAO,iBAAiB,QAAQ;EAC9B,GAAG;EACH,WAAW;GAAE;GAAO,SAAS,WAAW,MAAM,OAAO;EAAE;CACzD,CAAC;AACH","names":[],"ignoreList":[],"sources":["../src/vite.ts"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lit-ui-router-ssr",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Static prerendering for lit-ui-router: mount verdicts in, an emitted site out",
5
5
  "homepage": "https://lit-ui-router.dev/packages/ssr",
6
6
  "bugs": {
@@ -69,6 +69,14 @@
69
69
  "deno": "./dist/testing.js",
70
70
  "node": "./dist/development/testing.js",
71
71
  "default": "./dist/testing.js"
72
+ },
73
+ "./vite": {
74
+ "types": "./dist/vite.d.ts",
75
+ "development": "./dist/development/vite.js",
76
+ "production": "./dist/vite.js",
77
+ "deno": "./dist/vite.js",
78
+ "node": "./dist/development/vite.js",
79
+ "default": "./dist/vite.js"
72
80
  }
73
81
  },
74
82
  "dependencies": {
@@ -80,7 +88,7 @@
80
88
  "@uirouter/core": "^6.0.8",
81
89
  "lit": "^3.1.2",
82
90
  "lit-ui-router": "^1.19.0",
83
- "ui-router-server": "^0.7.0"
91
+ "ui-router-server": "^0.8.0"
84
92
  },
85
93
  "peerDependenciesMeta": {
86
94
  "@lit-labs/ssr-client": {
@@ -130,6 +138,19 @@
130
138
  "node:fs",
131
139
  "node:path"
132
140
  ]
141
+ },
142
+ "./vite": {
143
+ "free": [
144
+ "@lit-labs/ssr",
145
+ "@lit-labs/ssr-client",
146
+ "@uirouter/core",
147
+ "lit",
148
+ "lit-ui-router",
149
+ "hono",
150
+ "vite",
151
+ "node:fs",
152
+ "node:path"
153
+ ]
133
154
  }
134
155
  }
135
156
  }
package/src/index.ts CHANGED
@@ -7,7 +7,9 @@
7
7
  export {
8
8
  type EmittedPage,
9
9
  type FileWriter,
10
+ type PagePayload,
10
11
  prerender,
12
+ type PrerenderedPage,
11
13
  type PrerenderOptions,
12
14
  type PrerenderResult,
13
15
  type PrerenderTally,
package/src/prerender.ts CHANGED
@@ -10,12 +10,18 @@ import { isString } from '@uirouter/core';
10
10
  import type { Verdict } from 'ui-router-server';
11
11
  import { prerender as prerenderSite } from 'ui-router-server/prerender';
12
12
  import type {
13
+ EmittedPage,
13
14
  FileWriter,
14
15
  PrerenderOptions as SitePrerenderOptions,
15
16
  PrerenderResult as SitePrerenderResult,
16
17
  RenderContext as SiteRenderContext,
17
18
  } from 'ui-router-server/prerender';
18
- import type { ResolvePayload } from 'ui-router-server/ssr';
19
+ import { dehydrate as dehydrateLanded } from 'ui-router-server/ssr';
20
+ import type {
21
+ DehydrateOptions,
22
+ DehydrateResult,
23
+ ResolvePayload,
24
+ } from 'ui-router-server/ssr';
19
25
  import { devGate } from '#dev-gate';
20
26
 
21
27
  import { LightElementRenderer } from './light-element-renderer.js';
@@ -138,36 +144,84 @@ export interface PrerenderOptions extends Omit<
138
144
  /** Writes a file. Defaults to `node:fs`, imported lazily on first write. */
139
145
  write?: FileWriter;
140
146
  /**
141
- * Draws the resolve payload for a rendered page, handed the transition the
147
+ * Draws each rendered page's resolve payload with `dehydrate` from
148
+ * `ui-router-server/ssr`, called with these options on the transition the
142
149
  * router last landed, once `renderShell` has returned a template. The
143
150
  * payload's `resolves` go into the page's hydration signature, beside the
144
151
  * state and parameters the router stands on, and the client adopts them on
145
- * boot in place of running those resolve functions. Return `undefined` to
146
- * write none. Without this hook no page carries resolves.
152
+ * boot in place of running those resolve functions. Pass `{}` to draw every
153
+ * resolve `dehydrate` draws by default. Without this option or
154
+ * {@link PrerenderOptions.payload | `payload`}, no page carries resolves.
147
155
  *
148
- * `dehydrate` from `ui-router-server/ssr` draws the payload; its
149
- * documentation says which values it leaves out. The payload's
156
+ * `dehydrate`'s documentation says which values it leaves out and how
157
+ * `shouldDehydrate` and `transport` act. The payload's
150
158
  * `transported` marks go into the signature too, so a value a transporter
151
159
  * carried is rebuilt when `bootstrap()` gets the same `transport`.
152
160
  *
153
161
  * @example
154
162
  * ```ts
155
- * payload: (transition, { path }) => {
156
- * const { payload, unserializable } = dehydrate(transition);
157
- * for (const { state, token } of unserializable)
158
- * console.warn(`${path}: ${state}.${token} re-runs on the client`);
159
- * return payload;
163
+ * dehydrate: {
164
+ * shouldDehydrate: ({ token }) => token !== 'manifest',
165
+ * transport,
160
166
  * },
161
167
  * ```
162
168
  */
169
+ dehydrate?: DehydrateOptions;
170
+ /**
171
+ * Draws the resolve payload for a rendered page by hand, in place of
172
+ * {@link PrerenderOptions.dehydrate | `dehydrate`}: handed the transition the
173
+ * router last landed, once `renderShell` has returned a template, it returns
174
+ * the payload the page's signature carries, or `undefined` to write none.
175
+ * Passing both this and `dehydrate` throws.
176
+ *
177
+ * @example
178
+ * Shape the payload `dehydrate` draws before it is written:
179
+ * ```ts
180
+ * import { dehydrate } from 'ui-router-server/ssr';
181
+ *
182
+ * payload: (transition, { path }) =>
183
+ * path.startsWith('/drafts/')
184
+ * ? undefined
185
+ * : dehydrate(transition, { transport }).payload,
186
+ * ```
187
+ */
163
188
  payload?: (
164
189
  transition: Transition,
165
190
  context: RenderContext,
166
191
  ) => ResolvePayload | undefined;
167
192
  }
168
193
 
194
+ /** What a page's hydration signature carries of its resolve payload. */
195
+ export interface PagePayload {
196
+ /**
197
+ * The bytes the payload adds to the page's signature as written: its
198
+ * `resolves` and `transported` members, escaped as the block spells them.
199
+ */
200
+ bytes: number;
201
+ /**
202
+ * The resolves {@link PrerenderOptions.dehydrate | `dehydrate`} left out of
203
+ * the payload because their value is not plain JSON data and no transporter
204
+ * carries it, each to re-run on the client. Present only when `dehydrate`
205
+ * drew the payload: a {@link PrerenderOptions.payload | `payload`} hook
206
+ * returns the payload alone.
207
+ */
208
+ unserializable?: DehydrateResult['unserializable'];
209
+ }
210
+
211
+ /** A page {@link prerender} emitted. */
212
+ export interface PrerenderedPage extends EmittedPage {
213
+ /**
214
+ * What the page's signature carries of its resolve payload. Absent on a page
215
+ * that carries none: one drawn with no payload, one `renderShell` returned
216
+ * as a string, and a response state's body.
217
+ */
218
+ payload?: PagePayload;
219
+ }
220
+
169
221
  /** What {@link prerender} did. */
170
- export interface PrerenderResult extends SitePrerenderResult {
222
+ export interface PrerenderResult extends Omit<SitePrerenderResult, 'pages'> {
223
+ /** Every page emitted or planned, in enumeration order. */
224
+ pages: PrerenderedPage[];
171
225
  /** The root event target, created or passed through. */
172
226
  root: EventTarget;
173
227
  }
@@ -225,6 +279,14 @@ export const signatureBlock = (signature: WrittenSignature): string => {
225
279
  return `<script type="application/json" ${signatureAttribute}>${json}</script>`;
226
280
  };
227
281
 
282
+ const utf8Length = (text: string): number =>
283
+ new TextEncoder().encode(text).length;
284
+
285
+ /** The bytes `drawn` adds to the signature of the page `router` stands on. */
286
+ const payloadBytes = (router: UIRouterLit, drawn: DrawnResolves): number =>
287
+ utf8Length(signatureBlock(signatureOf(router, drawn))) -
288
+ utf8Length(signatureBlock(signatureOf(router)));
289
+
228
290
  /**
229
291
  * Renders one page: the signature first, then the render, so the client reads
230
292
  * the one before it hydrates the other.
@@ -258,7 +320,7 @@ const warnUnregistered = (): void => {
258
320
  if (globalThis.customElements?.get('ui-view')) return;
259
321
  console.warn(
260
322
  'lit-ui-router-ssr: <ui-view> is not defined on the current customElements registry, so every <ui-view> on these pages renders empty. ' +
261
- 'The DOM shim replaces the registry when it finishes installing: load lit-ui-router-ssr/register by dynamic import after the shim, or preload the shim with node --import.',
323
+ 'The DOM shim replaces the registry when it finishes installing: prerender through serverRouterPlugin from lit-ui-router-ssr/vite, which preloads both, load lit-ui-router-ssr/register by dynamic import after the shim, or preload the shim with node --import.',
262
324
  );
263
325
  };
264
326
 
@@ -279,9 +341,12 @@ const warnUnregistered = (): void => {
279
341
  * A rendered page opens on its hydration signature: a JSON data block,
280
342
  * `<script type="application/json" data-lit-ui-router-ssr>`, naming this
281
343
  * package's version and the state and parameter values the router stood on,
282
- * which `hydrateRoot()` reads before it touches the document. A
283
- * {@link PrerenderOptions.payload | `payload`} hook adds the resolved values
284
- * the page was drawn with, which `bootstrap()` adopts. A `renderShell`
344
+ * which `hydrateRoot()` reads before it touches the document.
345
+ * {@link PrerenderOptions.dehydrate | `dehydrate`}, or a
346
+ * {@link PrerenderOptions.payload | `payload`} hook, adds the resolved values
347
+ * the page was drawn with, which `bootstrap()` adopts; the page's entry in
348
+ * the result's `pages` reports the bytes they add and, from `dehydrate`, the
349
+ * resolves left to re-run on the client. A `renderShell`
285
350
  * that returns a string is written as-is, with no signature, so the client
286
351
  * renders it cold.
287
352
  *
@@ -295,6 +360,7 @@ const warnUnregistered = (): void => {
295
360
  * @returns what was emitted, planned or written
296
361
  * @throws an `Error` when neither `mounts` nor `resolver` is supplied
297
362
  * @throws an `Error` when the 404 document needs a default probe and only a `resolver` is supplied
363
+ * @throws an `Error` when both `dehydrate` and `payload` are supplied
298
364
  *
299
365
  * @example
300
366
  * ```ts
@@ -321,23 +387,58 @@ export async function prerender(
321
387
  elementRenderers = [UiViewRenderer, LightElementRenderer],
322
388
  root = new EventTarget(),
323
389
  write = nodeWrite,
390
+ dehydrate: dehydrateOptions,
324
391
  payload,
325
392
  responses,
326
393
  origin = 'http://lit-ui-router-ssr',
327
394
  ...site
328
395
  } = options;
329
396
 
397
+ if (dehydrateOptions && payload)
398
+ throw new Error(
399
+ 'prerender: pass `dehydrate` or `payload`, not both; a `payload` hook calls `dehydrate()` itself',
400
+ );
401
+
330
402
  warnUnregistered();
331
403
 
332
- const payloadOf = (context: RenderContext): ResolvePayload | undefined => {
333
- if (!payload) return undefined;
404
+ const draw = (
405
+ transition: Transition,
406
+ context: RenderContext,
407
+ ): Partial<DehydrateResult> => {
408
+ if (dehydrateOptions) return dehydrateLanded(transition, dehydrateOptions);
409
+
410
+ return { payload: payload?.(transition, context) };
411
+ };
412
+
413
+ const drawLanded = (context: RenderContext): Partial<DehydrateResult> => {
414
+ if (!dehydrateOptions && !payload) return {};
334
415
 
335
416
  // SAFETY: `peekTail()` is typed `Transition` and returns `undefined` on an empty queue.
336
417
  const landed = router.globals.successfulTransitions.peekTail() as
337
418
  | Transition
338
419
  | undefined;
339
420
 
340
- return landed && payload(landed, context);
421
+ return landed ? draw(landed, context) : {};
422
+ };
423
+
424
+ const reports = new Map<string, PagePayload>();
425
+
426
+ const renderTemplate = (
427
+ body: TemplateResult,
428
+ context: RenderContext,
429
+ ): string => {
430
+ const { payload: drawn, unserializable } = drawLanded(context);
431
+
432
+ if (drawn) {
433
+ const bytes = payloadBytes(router, drawn);
434
+
435
+ reports.set(
436
+ context.file,
437
+ unserializable ? { bytes, unserializable } : { bytes },
438
+ );
439
+ }
440
+
441
+ return renderPage(body, router, root, elementRenderers, drawn);
341
442
  };
342
443
 
343
444
  const renderLit = async (
@@ -347,9 +448,7 @@ export async function prerender(
347
448
  const context: RenderContext = { ...siteContext, root };
348
449
  const body = await renderShell(verdict, context);
349
450
 
350
- const markup = isString(body)
351
- ? body
352
- : renderPage(body, router, root, elementRenderers, payloadOf(context));
451
+ const markup = isString(body) ? body : renderTemplate(body, context);
353
452
 
354
453
  return document ? document(markup, context) : markup;
355
454
  };
@@ -379,7 +478,13 @@ export async function prerender(
379
478
  write,
380
479
  });
381
480
 
382
- return { ...result, root };
481
+ const pages = result.pages.map((page): PrerenderedPage => {
482
+ const report = reports.get(page.file);
483
+
484
+ return report ? { ...page, payload: report } : page;
485
+ });
486
+
487
+ return { ...result, pages, root };
383
488
  } finally {
384
489
  uninstall();
385
490
  }
@@ -0,0 +1,18 @@
1
+ /** `@lit-labs/ssr`'s DOM shim: the default for `serverRouterPlugin`'s `shim`. @internal */
2
+ export const DOM_SHIM: readonly string[] = [
3
+ '@lit-labs/ssr/lib/install-global-dom-shim.js',
4
+ ];
5
+
6
+ /**
7
+ * The worker's preloads for a Lit app: the shim modules, then
8
+ * `lit-ui-router-ssr/register` in the registry they installed, then the app's
9
+ * own, each once, at its first position.
10
+ *
11
+ * @internal
12
+ */
13
+ export const litPreload = (
14
+ shim: readonly string[],
15
+ preload: readonly string[],
16
+ ): string[] => [
17
+ ...new Set([...shim, 'lit-ui-router-ssr/register', ...preload]),
18
+ ];
package/src/vite.ts ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The Vite plugin for a Lit app: `ui-router-server/vite`'s
3
+ * `serverRouterPlugin`, with the prerender worker set up for Lit. It names
4
+ * a DOM shim (`@lit-labs/ssr`'s by default) and `lit-ui-router-ssr/register` as preloads, so
5
+ * the prerender script imports the app, the router and this package
6
+ * statically, and `definePrerender` calls this package's `prerender()` with
7
+ * the options the script returns.
8
+ *
9
+ * Nothing here imports Lit, `@lit-labs/ssr` or Vite, so `vite.config.ts`
10
+ * loads it without defining an element or installing a global in Vite's
11
+ * process.
12
+ *
13
+ * @module
14
+ */
15
+ import { serverRouterPlugin as siteRouterPlugin } from 'ui-router-server/vite';
16
+ import type {
17
+ PrerenderBuild,
18
+ PrerenderEntry,
19
+ ServerRouterPlugin,
20
+ ServerRouterPluginOptions as SiteRouterPluginOptions,
21
+ } from 'ui-router-server/vite';
22
+ import type { ServerRouter } from 'ui-router-server';
23
+
24
+ import type { PrerenderOptions } from './prerender.js';
25
+ import { DOM_SHIM, litPreload } from './vite-preload.js';
26
+
27
+ export type {
28
+ PrerenderBuild,
29
+ PrerenderEntry,
30
+ ServerRouterPlugin,
31
+ } from 'ui-router-server/vite';
32
+
33
+ /**
34
+ * `ui-router-server/vite`'s plugin options, plus the DOM the prerender
35
+ * worker installs for Lit.
36
+ */
37
+ export interface ServerRouterPluginOptions extends SiteRouterPluginOptions {
38
+ /**
39
+ * Modules that install a DOM in the prerender worker, loaded in order
40
+ * before `lit-ui-router-ssr/register` and any `preload`. Defaults to
41
+ * `@lit-labs/ssr`'s DOM shim. List another DOM's setup to use it
42
+ * instead, or pass `[]` to install none: Lit's Node build still renders,
43
+ * but app code that reads `document` or `window` fails.
44
+ */
45
+ shim?: readonly string[];
46
+ }
47
+
48
+ /**
49
+ * What a prerender script's callback returns: this package's
50
+ * {@link PrerenderOptions}, less what the build supplies. `outDir` and
51
+ * `write` come from the build, so a script may still pass either to
52
+ * override it.
53
+ */
54
+ export type PrerenderScriptOptions = Omit<
55
+ PrerenderOptions,
56
+ 'outDir' | 'write'
57
+ > &
58
+ Partial<Pick<PrerenderOptions, 'outDir' | 'write'>>;
59
+
60
+ /**
61
+ * The callback {@link definePrerender} takes: handed the build's values,
62
+ * such as `shellHtml` for a `document` hook, it returns the prerender
63
+ * options.
64
+ */
65
+ export type PrerenderScript = (
66
+ build: PrerenderBuild,
67
+ ) => PrerenderScriptOptions | Promise<PrerenderScriptOptions>;
68
+
69
+ // Loaded on call, inside the worker: the plugin's process never evaluates lit.
70
+ const loadPrerender = async () => (await import('./prerender.js')).prerender;
71
+
72
+ /**
73
+ * Turns a callback that returns this package's prerender options into a
74
+ * prerender script's default export. The export spreads the build's values
75
+ * (`outDir`, `shellHtml`, `write`) under the options and calls this
76
+ * package's `prerender()`, so the script never calls it itself.
77
+ *
78
+ * @param script - returns the prerender options; handed the build's values
79
+ * @returns the default export for the prerender script
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * export default definePrerender((site) => ({
84
+ * mounts,
85
+ * router,
86
+ * paths: ['/', '/about'],
87
+ * renderShell: async (_verdict, { path }) => {
88
+ * await settle(router, path);
89
+ * return page(router);
90
+ * },
91
+ * document: (body) => site.shellHtml.replace('<!--app-->', body),
92
+ * }));
93
+ * ```
94
+ */
95
+ export function definePrerender(script: PrerenderScript): PrerenderEntry {
96
+ return async (build) => {
97
+ const [options, prerender] = await Promise.all([
98
+ script(build),
99
+ loadPrerender(),
100
+ ]);
101
+
102
+ // `root` is an event target, which the worker can't post back.
103
+ const { root: _root, ...result } = await prerender({
104
+ ...build,
105
+ ...options,
106
+ });
107
+
108
+ return result;
109
+ };
110
+ }
111
+
112
+ /**
113
+ * `ui-router-server/vite`'s `serverRouterPlugin`, set up for a Lit app: with
114
+ * {@link ServerRouterPluginOptions.prerender | prerender}, the worker loads
115
+ * the {@link ServerRouterPluginOptions.shim | shim}, then
116
+ * `lit-ui-router-ssr/register`, then any `preload` passed here, each once,
117
+ * before the prerender script, so the script imports everything statically. Its default export
118
+ * comes from this module's {@link definePrerender}.
119
+ *
120
+ * `vite dev` and `vite preview` behave as under `ui-router-server/vite`.
121
+ *
122
+ * @param router - the server router the middleware and the build step read
123
+ * @param options - the Connect adapter's options, plus the build step
124
+ * @returns a plugin to spread into `plugins: [...]`
125
+ * @throws from the build step when the script has no default export
126
+ * function, the built `index.html` is missing, or a listed path matches no
127
+ * route
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { createServerRouter } from 'ui-router-server';
132
+ * import { serverRouterPlugin } from 'lit-ui-router-ssr/vite';
133
+ *
134
+ * plugins: [
135
+ * serverRouterPlugin(createServerRouter({ mounts }), {
136
+ * prerender: './src/prerender.ts',
137
+ * }),
138
+ * ],
139
+ * ```
140
+ */
141
+ export function serverRouterPlugin(
142
+ router: ServerRouter,
143
+ options: ServerRouterPluginOptions = {},
144
+ ): ServerRouterPlugin {
145
+ const { shim = DOM_SHIM, ...siteOptions } = options;
146
+ const { prerender } = siteOptions;
147
+
148
+ if (prerender === undefined) return siteRouterPlugin(router, siteOptions);
149
+
150
+ const { entry, preload = [] } =
151
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- the option's own union: a string is shorthand for `{ entry }`
152
+ typeof prerender === 'string' ? { entry: prerender } : prerender;
153
+
154
+ return siteRouterPlugin(router, {
155
+ ...siteOptions,
156
+ prerender: { entry, preload: litPreload(shim, preload) },
157
+ });
158
+ }