@webjsdev/server 0.8.90 → 0.8.91
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 +1 -1
- package/src/dev/vendor-retry.js +12 -14
- package/src/ssr/preloads.js +5 -0
- package/src/ssr/render.js +16 -29
- package/src/ssr/shipped-modules.js +20 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.91",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
|
|
6
6
|
"main": "index.js",
|
package/src/dev/vendor-retry.js
CHANGED
|
@@ -39,23 +39,21 @@ export function createRetryBackoff(opts = {}) {
|
|
|
39
39
|
const now = opts.now ?? Date.now;
|
|
40
40
|
let windowMs = 0;
|
|
41
41
|
let notBefore = 0;
|
|
42
|
+
// Closures over the state, not methods reading `this`, so a handle can be
|
|
43
|
+
// destructured or passed as a callback (`onRebuild(backoff.reset)`).
|
|
44
|
+
const succeeded = () => {
|
|
45
|
+
windowMs = 0;
|
|
46
|
+
notBefore = 0;
|
|
47
|
+
};
|
|
42
48
|
return {
|
|
43
|
-
allow()
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
failed() {
|
|
49
|
+
allow: () => windowMs === 0 || now() >= notBefore,
|
|
50
|
+
failed: () => {
|
|
47
51
|
windowMs = windowMs === 0 ? initialMs : Math.min(maxMs, windowMs * 2);
|
|
48
52
|
notBefore = now() + windowMs;
|
|
49
53
|
},
|
|
50
|
-
succeeded
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
reset() {
|
|
55
|
-
this.succeeded();
|
|
56
|
-
},
|
|
57
|
-
windowMs() {
|
|
58
|
-
return windowMs;
|
|
59
|
-
},
|
|
54
|
+
succeeded,
|
|
55
|
+
// A rebuild forgets the same thing a success does.
|
|
56
|
+
reset: succeeded,
|
|
57
|
+
windowMs: () => windowMs,
|
|
60
58
|
};
|
|
61
59
|
}
|
package/src/ssr/preloads.js
CHANGED
|
@@ -91,6 +91,9 @@ export function componentPreloads(usedTags, appDir, elidable, lazyFiles) {
|
|
|
91
91
|
* @param {string[]} entryFiles absolute paths of the SHIPPED page/layout modules (from `moduleUrls`)
|
|
92
92
|
* @param {string} appDir
|
|
93
93
|
* @param {Set<string>} [elidableComponents] absolute paths to skip in the walk
|
|
94
|
+
* @param {Set<string>} [serverFiles] the action / server-file index, never preloaded
|
|
95
|
+
* @param {string[]} [precomputedDeps] the transitive closure `pagePreloadHints` already walked for
|
|
96
|
+
* THESE roots and THIS skip set. Omitted, the walk runs here (a direct caller)
|
|
94
97
|
* @returns {string[]}
|
|
95
98
|
*/
|
|
96
99
|
export function deduplicatedPreloads(componentUrls, moduleUrls, graph, entryFiles, appDir, serverFiles, elidableComponents, precomputedDeps) {
|
|
@@ -172,6 +175,8 @@ export function deduplicatedPreloads(componentUrls, moduleUrls, graph, entryFile
|
|
|
172
175
|
* @param {string} appDir
|
|
173
176
|
* @param {Set<string>} [elidableComponents]
|
|
174
177
|
* @param {Set<string>} [serverFiles] the action / server-file index (`'use server'`, incl. no-`.server.` files)
|
|
178
|
+
* @param {string[]} [precomputedDeps] the transitive closure `pagePreloadHints` already walked for
|
|
179
|
+
* THESE roots and THIS skip set. Omitted, the walk runs here (a direct caller)
|
|
175
180
|
* @returns {Set<string>}
|
|
176
181
|
*/
|
|
177
182
|
export function reachedVendorSpecifiers(graph, entryFiles, componentUrls, appDir, elidableComponents, serverFiles, precomputedDeps) {
|
package/src/ssr/render.js
CHANGED
|
@@ -3,7 +3,7 @@ import { vendorPreloadTargets } from '../importmap.js';
|
|
|
3
3
|
import {
|
|
4
4
|
componentPreloads, pagePreloadHints, toUrlPath,
|
|
5
5
|
} from './preloads.js';
|
|
6
|
-
import { shippedBootModules } from './shipped-modules.js';
|
|
6
|
+
import { shippedBootModules, assembleBootModules } from './shipped-modules.js';
|
|
7
7
|
import { seedingEnabled, collectSeeds, buildSeedScript, SEED_DROP_BLOCK } from '../action-seed.js';
|
|
8
8
|
import {
|
|
9
9
|
readRevalidate,
|
|
@@ -674,34 +674,21 @@ function reportBoundaryLayoutError(err, opts, cfg = {}) {
|
|
|
674
674
|
* @returns {string[]}
|
|
675
675
|
*/
|
|
676
676
|
function boundaryModuleUrls(boundaryFile, wrapLayouts, opts) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
// error reporting is installed before anything can throw. A boundary page is
|
|
693
|
-
// where that matters most, so it must not be the one place it is missing.
|
|
694
|
-
// The set is never empty by the time we get here (the boundary file is
|
|
695
|
-
// always pushed above, since only pages and layouts are fed to the elision
|
|
696
|
-
// analysis, so a boundary is never inert and never import-only), which is
|
|
697
|
-
// why this needs no non-empty guard.
|
|
698
|
-
if (opts.instrumentationClient) {
|
|
699
|
-
const u = toUrlPath(opts.instrumentationClient, opts.appDir);
|
|
700
|
-
const i = urls.indexOf(u);
|
|
701
|
-
if (i !== -1) urls.splice(i, 1);
|
|
702
|
-
urls.unshift(u);
|
|
703
|
-
}
|
|
704
|
-
return urls;
|
|
677
|
+
// The happy path's own assembly (`shipped-modules.js`), un-memoized because a
|
|
678
|
+
// boundary's file list is built per render. instrumentation-client.{js,ts}
|
|
679
|
+
// (#848) rides the SAME first-import contract it has on the happy path: it
|
|
680
|
+
// runs before app modules so the app's client error reporting is installed
|
|
681
|
+
// before anything can throw, and a boundary page is where that matters most.
|
|
682
|
+
// `lazyFiles` is deliberately not passed: a boundary render has no lazy map
|
|
683
|
+
// of its own to register a diverted frontier component with, so a `static
|
|
684
|
+
// lazy` component imported by a boundary is imported eagerly, as before.
|
|
685
|
+
return assembleBootModules(boundaryFile ? [boundaryFile, ...wrapLayouts] : wrapLayouts, {
|
|
686
|
+
appDir: opts.appDir,
|
|
687
|
+
inert: opts.inertRouteModules,
|
|
688
|
+
importOnly: opts.importOnlyRouteModules,
|
|
689
|
+
lazyFiles: undefined,
|
|
690
|
+
instrumentationClient: opts.instrumentationClient,
|
|
691
|
+
}).moduleUrls;
|
|
705
692
|
}
|
|
706
693
|
|
|
707
694
|
/**
|
|
@@ -54,6 +54,24 @@ const MEMO = new WeakMap();
|
|
|
54
54
|
export function shippedBootModules(route, deps) {
|
|
55
55
|
const hit = MEMO.get(route);
|
|
56
56
|
if (hit && hit.epoch === buildEpoch()) return hit.value;
|
|
57
|
+
const value = assembleBootModules([route.file, ...route.layouts], deps);
|
|
58
|
+
MEMO.set(route, { epoch: buildEpoch(), value, hinted: null, hintedBasePath: '' });
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The assembly itself, un-memoized: the one loop behind `shippedBootModules`
|
|
64
|
+
* (a matched page and its layouts) and the boundary render in `ssr/render.js`
|
|
65
|
+
* (an error / not-found / forbidden / unauthorized file and the layouts it is
|
|
66
|
+
* wrapped in, a per-render list with no stable object to key a memo on). One
|
|
67
|
+
* function so the two cannot drift, which is how the 103 Early Hints lost the
|
|
68
|
+
* lazy-frontier rule before #1644.
|
|
69
|
+
*
|
|
70
|
+
* @param {string[]} files the route modules in import order (page or boundary first, then its layouts)
|
|
71
|
+
* @param {ShippedDeps} deps
|
|
72
|
+
* @returns {ShippedModules}
|
|
73
|
+
*/
|
|
74
|
+
export function assembleBootModules(files, deps) {
|
|
57
75
|
const { appDir, inert, importOnly, lazyFiles, instrumentationClient } = deps;
|
|
58
76
|
/** @type {string[]} */
|
|
59
77
|
const moduleUrls = [];
|
|
@@ -66,7 +84,7 @@ export function shippedBootModules(route, deps) {
|
|
|
66
84
|
if (tags) { for (const t of tags) frontierLazy[t] = u; return; }
|
|
67
85
|
if (!seen.has(u)) { seen.add(u); moduleUrls.push(u); }
|
|
68
86
|
};
|
|
69
|
-
for (const f of
|
|
87
|
+
for (const f of files) {
|
|
70
88
|
if (inert && inert.has(f)) continue;
|
|
71
89
|
const emit = importOnly && importOnly.get(f);
|
|
72
90
|
if (emit) emit.forEach(push);
|
|
@@ -81,9 +99,7 @@ export function shippedBootModules(route, deps) {
|
|
|
81
99
|
if (i !== -1) moduleUrls.splice(i, 1);
|
|
82
100
|
moduleUrls.unshift(u);
|
|
83
101
|
}
|
|
84
|
-
|
|
85
|
-
MEMO.set(route, { epoch: buildEpoch(), value, hinted: null, hintedBasePath: '' });
|
|
86
|
-
return value;
|
|
102
|
+
return { moduleUrls, frontierLazy };
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
/**
|