@ultimat3/render 22.3.2 → 22.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/module-loader.ts +30 -5
- package/src/registry.ts +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/render",
|
|
3
|
-
"version": "22.3.
|
|
3
|
+
"version": "22.3.4",
|
|
4
4
|
"description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"test": "bun test"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@ultimat3/cache": "22.3.
|
|
40
|
-
"@ultimat3/core": "22.3.
|
|
41
|
-
"@ultimat3/http": "22.3.
|
|
42
|
-
"@ultimat3/i18n": "22.3.
|
|
43
|
-
"@ultimat3/seo": "22.3.
|
|
39
|
+
"@ultimat3/cache": "22.3.4",
|
|
40
|
+
"@ultimat3/core": "22.3.4",
|
|
41
|
+
"@ultimat3/http": "22.3.4",
|
|
42
|
+
"@ultimat3/i18n": "22.3.4",
|
|
43
|
+
"@ultimat3/seo": "22.3.4",
|
|
44
44
|
"sass": "1.104.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/module-loader.ts
CHANGED
|
@@ -78,6 +78,14 @@ export interface Stylesheet {
|
|
|
78
78
|
readonly surface: Surface | null;
|
|
79
79
|
/** A plain (non-module) stylesheet: the tokens and the reset, which the cascade needs first. */
|
|
80
80
|
readonly global: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Registered only by an ISLAND build, never by the server's module graph. Such a sheet is ordered
|
|
83
|
+
* by path, not by arrival: `Bun.build` loads an island graph in parallel, so its arrival order
|
|
84
|
+
* differs between processes, and the surface stylesheet — its URL a hash of the joined bytes —
|
|
85
|
+
* differed between two pods of one image (notificado.co, 22.3.2: one pod's `/styles/<hash>.css`
|
|
86
|
+
* was the other's 404, and each `sw.js` precached a sheet the other did not serve).
|
|
87
|
+
*/
|
|
88
|
+
readonly island: boolean;
|
|
81
89
|
readonly css: string;
|
|
82
90
|
}
|
|
83
91
|
|
|
@@ -158,9 +166,17 @@ export function stylesFor(surface: Surface | null): string {
|
|
|
158
166
|
);
|
|
159
167
|
// `stripCharset` on every sheet, not only the first: a `@charset` or a BOM is legal at byte 0 of
|
|
160
168
|
// a FILE and nowhere else, and this join is what turns seven files into one.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
//
|
|
170
|
+
// Within each group, the server graph's sheets keep load order — deterministic, because `loadApp`
|
|
171
|
+
// imports in sorted order, and meaningful, because a page's module loads after the component
|
|
172
|
+
// module it overrides — and the island-only sheets follow, by path.
|
|
173
|
+
const byPath = (a: Stylesheet, b: Stylesheet): number =>
|
|
174
|
+
a.file < b.file ? -1 : a.file > b.file ? 1 : 0;
|
|
175
|
+
const group = (global: boolean): Stylesheet[] => [
|
|
176
|
+
...carried.filter((sheet) => sheet.global === global && !sheet.island),
|
|
177
|
+
...carried.filter((sheet) => sheet.global === global && sheet.island).sort(byPath),
|
|
178
|
+
];
|
|
179
|
+
return [...group(true), ...group(false)].map((sheet) => stripCharset(sheet.css)).join('');
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
/**
|
|
@@ -181,17 +197,26 @@ export function transformTsx(source: string): string {
|
|
|
181
197
|
* compilation — so a token file `@use`d by twenty `page.module.scss` would inline its `:root` block
|
|
182
198
|
* twenty times. One file, imported for its side effect, is the shape that cannot do that.
|
|
183
199
|
*/
|
|
184
|
-
export function loadStylesheet(
|
|
200
|
+
export function loadStylesheet(
|
|
201
|
+
path: string,
|
|
202
|
+
source: string,
|
|
203
|
+
origin: 'module' | 'island' = 'module',
|
|
204
|
+
): string {
|
|
185
205
|
const compiled = compileStylesheet(path, source);
|
|
186
206
|
// An EMPTY compile unregisters: under `x dev` an edit that deleted every rule left the old entry
|
|
187
207
|
// in place, serving rules the file no longer had until the process restarted.
|
|
188
208
|
if (compiled.css.length === 0 && stylesheets.delete(path)) revision += 1;
|
|
189
209
|
if (compiled.css.length > 0) {
|
|
190
|
-
|
|
210
|
+
const held = stylesheets.get(path);
|
|
211
|
+
// Island-only until the server graph loads it too; then it takes its place in load order.
|
|
212
|
+
const island = origin === 'island' && (held === undefined || held.island);
|
|
213
|
+
if (held?.css !== compiled.css || held.island !== island) revision += 1;
|
|
214
|
+
if (held?.island === true && !island) stylesheets.delete(path);
|
|
191
215
|
stylesheets.set(path, {
|
|
192
216
|
file: path,
|
|
193
217
|
surface: surfaceOfSheet(path),
|
|
194
218
|
global: isGlobalStylesheet(path),
|
|
219
|
+
island,
|
|
195
220
|
css: compiled.css,
|
|
196
221
|
});
|
|
197
222
|
}
|
package/src/registry.ts
CHANGED
|
@@ -68,7 +68,19 @@ export interface RouteDescriptor {
|
|
|
68
68
|
readonly prerenderable: boolean;
|
|
69
69
|
readonly dynamic: boolean;
|
|
70
70
|
readonly hasPolicy: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* The document is rendered FOR someone: a `policy`, a `stream` (always `private, no-store`), or a
|
|
73
|
+
* declared `cache` of `no-store` / `private`. `sw.js` never caches one (`@ultimat3/pwa`'s
|
|
74
|
+
* `strategyFor`): a per-member page kept for offline answered the previous member's data on a
|
|
75
|
+
* shared device after sign-out.
|
|
76
|
+
*/
|
|
77
|
+
readonly personal: boolean;
|
|
71
78
|
readonly islands: readonly string[];
|
|
79
|
+
/**
|
|
80
|
+
* Each island's `src`, relative to `file`, in `islands` order — what the island bundle resolves
|
|
81
|
+
* to a chunk URL, so `sw.js` precaches only the chunks a precached page boots.
|
|
82
|
+
*/
|
|
83
|
+
readonly islandSources: readonly string[];
|
|
72
84
|
readonly budgetJs: string | null;
|
|
73
85
|
readonly budgetLcp: number | null;
|
|
74
86
|
}
|
|
@@ -361,12 +373,22 @@ function buildDescriptors(): RouteDescriptor[] {
|
|
|
361
373
|
prerenderable: entry.config.prerender !== undefined,
|
|
362
374
|
dynamic: entry.pattern.keys.length > 0,
|
|
363
375
|
hasPolicy: entry.config.policy !== undefined,
|
|
376
|
+
personal: isPersonal(entry.config),
|
|
364
377
|
islands: entry.islands,
|
|
378
|
+
islandSources: entry.config.islands.map((spec) => spec.src),
|
|
365
379
|
budgetJs: entry.config.budget.js ?? null,
|
|
366
380
|
budgetLcp: entry.config.budget.lcp ?? null,
|
|
367
381
|
}));
|
|
368
382
|
}
|
|
369
383
|
|
|
384
|
+
/** See `RouteDescriptor.personal`. */
|
|
385
|
+
function isPersonal(config: RouteConfig): boolean {
|
|
386
|
+
if (config.policy !== undefined || config.render === 'stream') return true;
|
|
387
|
+
const cache = config.cache;
|
|
388
|
+
if (cache === undefined) return false;
|
|
389
|
+
return cache === 'no-store' || cache.mode === 'no-store' || cache.mode === 'private';
|
|
390
|
+
}
|
|
391
|
+
|
|
370
392
|
/**
|
|
371
393
|
* `undefined` for a malformed percent-escape. A pathname is whatever the client typed, and
|
|
372
394
|
* `decodeURIComponent('%zz')` throws a bare `URIError` — no code, no fix line — where a router
|