@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/server",
3
- "version": "0.8.90",
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",
@@ -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
- return windowMs === 0 || now() >= notBefore;
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
- windowMs = 0;
52
- notBefore = 0;
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
  }
@@ -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
- /** @type {string[]} */
678
- const urls = [];
679
- const seen = new Set();
680
- const push = (abs) => {
681
- const u = toUrlPath(abs, opts.appDir);
682
- if (!seen.has(u)) { seen.add(u); urls.push(u); }
683
- };
684
- for (const f of (boundaryFile ? [boundaryFile, ...wrapLayouts] : [...wrapLayouts])) {
685
- if (opts.inertRouteModules && opts.inertRouteModules.has(f)) continue;
686
- const emit = opts.importOnlyRouteModules && opts.importOnlyRouteModules.get(f);
687
- if (emit) emit.forEach(push);
688
- else push(f);
689
- }
690
- // instrumentation-client.{js,ts} (#848) rides the SAME first-import contract
691
- // it has on the happy path: it runs before app modules so the app's client
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 [route.file, ...route.layouts]) {
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
- const value = { moduleUrls, frontierLazy };
85
- MEMO.set(route, { epoch: buildEpoch(), value, hinted: null, hintedBasePath: '' });
86
- return value;
102
+ return { moduleUrls, frontierLazy };
87
103
  }
88
104
 
89
105
  /**