@webjsdev/server 0.8.63 → 0.8.65

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.
Files changed (47) hide show
  1. package/index.d.ts +7 -2
  2. package/package.json +2 -2
  3. package/src/browser-entries.js +64 -0
  4. package/src/check/helpers.js +365 -0
  5. package/src/check/rules-actions.js +174 -0
  6. package/src/check/rules-components.js +275 -0
  7. package/src/check/rules-imports.js +166 -0
  8. package/src/check/rules-registry.js +128 -0
  9. package/src/check/rules-routing.js +224 -0
  10. package/src/check/rules-typescript.js +177 -0
  11. package/src/check/rules.js +127 -0
  12. package/src/check/runner-support.js +405 -0
  13. package/src/check/runner.js +113 -0
  14. package/src/check.js +4 -1774
  15. package/src/component-elision.js +173 -26
  16. package/src/dev/config.js +338 -0
  17. package/src/dev/handler.js +1372 -0
  18. package/src/dev/helpers.js +542 -0
  19. package/src/dev/serve.js +840 -0
  20. package/src/dev/server.js +322 -0
  21. package/src/dev-classify.js +174 -0
  22. package/src/dev-reload-worker.js +133 -7
  23. package/src/dev-styles.js +97 -0
  24. package/src/dev.js +22 -3077
  25. package/src/form-dispatch.js +12 -3
  26. package/src/importmap.js +6 -3
  27. package/src/listener-core.js +16 -2
  28. package/src/module-graph.js +112 -7
  29. package/src/router.js +11 -1
  30. package/src/ssr/client-router-flag.js +42 -0
  31. package/src/ssr/document.js +361 -0
  32. package/src/ssr/env-shim.js +51 -0
  33. package/src/ssr/escape.js +29 -0
  34. package/src/ssr/head.js +857 -0
  35. package/src/ssr/preloads.js +195 -0
  36. package/src/ssr/render.js +1481 -0
  37. package/src/ssr/responses.js +201 -0
  38. package/src/ssr.js +28 -2445
  39. package/src/vendor/audit.js +400 -0
  40. package/src/vendor/integrity.js +245 -0
  41. package/src/vendor/jspm.js +348 -0
  42. package/src/vendor/manifest.js +96 -0
  43. package/src/vendor/pins.js +892 -0
  44. package/src/vendor/providers.js +35 -0
  45. package/src/vendor/resolver.js +225 -0
  46. package/src/vendor/scanner.js +132 -0
  47. package/src/vendor.js +29 -2371
@@ -0,0 +1,195 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { resolve } from 'node:path';
3
+ import { lookupModuleUrl, isLazy } from '@webjsdev/core';
4
+ import { transitiveDeps, bareImports } from '../module-graph.js';
5
+
6
+ /**
7
+ * Working out which modules a page's HTML should preload, and which vendor
8
+ * specifiers its importmap needs.
9
+ *
10
+ * Pure graph work: it takes the module graph plus the page's entry files and
11
+ * returns URL lists. It touches no request, no response, and no rendering, so
12
+ * it splits cleanly off the render path and is the reason `render.js` fits
13
+ * under the size ceiling with room to spare.
14
+ */
15
+
16
+ /**
17
+ * @param {string} file
18
+ * @param {string} appDir
19
+ */
20
+ export function toUrlPath(file, appDir) {
21
+ let rel = file.startsWith(appDir) ? file.slice(appDir.length) : file;
22
+ return rel.split('\\').join('/').replace(/^\/?/, '/');
23
+ }
24
+
25
+ /**
26
+ * Translate a Set of custom element tag names used on the page into browser
27
+ * URLs for modulepreload. Components that didn't pass a module URL to
28
+ * `register()` are skipped silently (no harm, just no preload hint).
29
+ *
30
+ * Returns separate eager and lazy lists. Lazy components (static lazy = true)
31
+ * are NOT preloaded: they're loaded by the IntersectionObserver-based
32
+ * lazy-loader when the element enters the viewport.
33
+ *
34
+ * Elidable (display-only) components are skipped entirely: their imports
35
+ * are stripped from the served source, so preloading their module would
36
+ * fetch JS the browser never executes.
37
+ *
38
+ * @param {Set<string>} usedTags
39
+ * @param {string} appDir
40
+ * @param {Set<string>} [elidable] absolute paths of elidable component files
41
+ * @returns {{ eager: string[], lazy: Record<string, string> }}
42
+ */
43
+ export function componentPreloads(usedTags, appDir, elidable) {
44
+ const eager = [];
45
+ /** @type {Record<string, string>} */
46
+ const lazy = {};
47
+ for (const tag of usedTags) {
48
+ const fileUrl = lookupModuleUrl(tag);
49
+ if (!fileUrl) continue;
50
+ try {
51
+ const abs = fileURLToPath(fileUrl);
52
+ if (!abs.startsWith(appDir)) continue;
53
+ if (elidable && elidable.has(abs)) continue;
54
+ const url = toUrlPath(abs, appDir);
55
+ if (isLazy(tag)) {
56
+ lazy[tag] = url;
57
+ } else {
58
+ eager.push(url);
59
+ }
60
+ } catch { /* ignore */ }
61
+ }
62
+ return { eager, lazy };
63
+ }
64
+
65
+ /**
66
+ * Merge component preloads with transitive dependencies from the module
67
+ * graph, then deduplicate against the already-imported module URLs.
68
+ *
69
+ * The walk ROOTS (`entryFiles`) are the boot's actually-SHIPPED page/layout
70
+ * module set (the caller passes the absolute paths of `moduleUrls`, which
71
+ * already drops an inert page/layout and substitutes an import-only page with
72
+ * its components), NOT the raw `[route.file, ...route.layouts]` route entries.
73
+ * This matches `reachedVendorSpecifiers`' roots so the two walks stay
74
+ * consistent. Rooting at the shipped set means a module reached ONLY through a
75
+ * dropped page/layout (its SSR-only direct app import OR its SSR-only relative
76
+ * helper) is never a walk root's dep, so it gets no `modulepreload` hint (no
77
+ * over-fetch, #780). A module that also ships some other way (a component shared
78
+ * with a live route, or reached via an import-only page's substituted
79
+ * components) is still reached through a real shipped root, so its hint stays
80
+ * (no under-fetch). `seen = new Set(moduleUrls)` already excludes the shipped
81
+ * modules' own URLs; the shipped-roots change closes the TRANSITIVE gap.
82
+ *
83
+ * @param {string[]} componentUrls direct component module URLs
84
+ * @param {string[]} moduleUrls boot script imports (page + layouts)
85
+ * @param {import('../module-graph.js').ModuleGraph | undefined} graph
86
+ * @param {string[]} entryFiles absolute paths of the SHIPPED page/layout modules (from `moduleUrls`)
87
+ * @param {string} appDir
88
+ * @param {Set<string>} [elidableComponents] absolute paths to skip in the walk
89
+ * @returns {string[]}
90
+ */
91
+ export function deduplicatedPreloads(componentUrls, moduleUrls, graph, entryFiles, appDir, serverFiles, elidableComponents) {
92
+ const seen = new Set(moduleUrls);
93
+ const result = [];
94
+
95
+ // Server-only modules are never useful to preload: they're imported by
96
+ // pages/layouts on the server, or surfaced to client components as
97
+ // generated RPC stubs that load lazily on first call. Preloading them
98
+ // wastes a roundtrip and pollutes the network tab with server-named files.
99
+ //
100
+ // Detection is belt-and-suspenders: filename suffix catches `.server.*`;
101
+ // the `serverFiles` set (built from the action index) also catches files
102
+ // that opted in via `'use server'` directive without the suffix.
103
+ const byName = (url) => /\.server\.m?[jt]s$/.test(url);
104
+ const byIndex = serverFiles
105
+ ? (abs) => (serverFiles.has ? serverFiles.has(abs) : false)
106
+ : () => false;
107
+
108
+ // Add direct component URLs
109
+ for (const url of componentUrls) {
110
+ if (seen.has(url) || byName(url)) continue;
111
+ seen.add(url);
112
+ result.push(url);
113
+ }
114
+
115
+ // Add transitive deps from the module graph
116
+ if (graph) {
117
+ // Combine entry files + component files for graph lookup
118
+ const allEntries = [...entryFiles];
119
+ for (const url of componentUrls) {
120
+ // Convert URL back to absolute path for graph lookup
121
+ const abs = resolve(appDir, url.startsWith('/') ? url.slice(1) : url);
122
+ allEntries.push(abs);
123
+ }
124
+ // Skip elidable components and any subtree reachable only through
125
+ // them: their imports are stripped from served source, so the
126
+ // browser never fetches these modules.
127
+ const deps = transitiveDeps(graph, allEntries, appDir, elidableComponents);
128
+ for (const dep of deps) {
129
+ if (byIndex(dep)) continue;
130
+ const url = toUrlPath(dep, appDir);
131
+ if (seen.has(url) || byName(url)) continue;
132
+ seen.add(url);
133
+ result.push(url);
134
+ }
135
+ }
136
+
137
+ return result;
138
+ }
139
+
140
+ /**
141
+ * Collect the bare npm vendor specifiers the page's SHIPPED modules import
142
+ * (#754). The walk ROOTS are the boot's actually-shipped module set: the caller
143
+ * passes `entryFiles` = the absolute paths of `moduleUrls` (which already drops
144
+ * inert page/layout modules and substitutes an import-only page with its
145
+ * components), plus `componentUrls` = the rendered components. From those roots
146
+ * it walks the transitive app-graph closure (elidable components and the subtree
147
+ * reachable only through them excluded), and collects each reached file's bare
148
+ * imports, excluding server files. The specifiers are resolved to `modulepreload`
149
+ * targets via the vendor importmap; a specifier not in the map (unpinned /
150
+ * unreached) drops out there.
151
+ *
152
+ * Because the roots are the SHIPPED set, a vendor reached ONLY through a module
153
+ * dropped from the boot, whether a dropped page's SSR-only DIRECT vendor import
154
+ * or its SSR-only RELATIVE HELPER's vendor, is never collected: the dropped
155
+ * module is not a root, and nothing that ships imports it (pages/layouts are not
156
+ * importable). So the canonical SSR-only-dependency pattern (which elision keeps
157
+ * off the client) is never preloaded (no over-fetch).
158
+ *
159
+ * @param {import('../module-graph.js').ModuleGraph | undefined} graph
160
+ * @param {string[]} entryFiles absolute paths of the SHIPPED page/layout modules (from `moduleUrls`)
161
+ * @param {string[]} componentUrls rendered eager component URL paths
162
+ * @param {string} appDir
163
+ * @param {Set<string>} [elidableComponents]
164
+ * @param {Set<string>} [serverFiles] the action / server-file index (`'use server'`, incl. no-`.server.` files)
165
+ * @returns {Set<string>}
166
+ */
167
+ export function reachedVendorSpecifiers(graph, entryFiles, componentUrls, appDir, elidableComponents, serverFiles) {
168
+ /** @type {Set<string>} */
169
+ const specs = new Set();
170
+ if (!graph) return specs;
171
+ const bare = bareImports(graph);
172
+ if (!bare.size) return specs;
173
+ // Roots = the SHIPPED page/layout modules (already inert-dropped + import-only
174
+ // expanded by the caller) + the rendered components, keyed by the graph's own
175
+ // absolute paths. Walk their non-elided transitive closure.
176
+ const allEntries = [...entryFiles];
177
+ for (const url of componentUrls) {
178
+ allEntries.push(resolve(appDir, url.startsWith('/') ? url.slice(1) : url));
179
+ }
180
+ const files = new Set(allEntries);
181
+ for (const dep of transitiveDeps(graph, allEntries, appDir, elidableComponents)) files.add(dep);
182
+ for (const file of files) {
183
+ // A server file is never served to the browser (its source is an RPC /
184
+ // throw-at-load stub), so a vendor it imports never ships and must NOT be
185
+ // preloaded. `transitiveDeps` stops AT a server-file boundary but still
186
+ // returns the boundary file itself, so filter it: the `.server.*` suffix
187
+ // AND the action index (a `'use server'` file without the suffix), matching
188
+ // `deduplicatedPreloads`' `byIndex` filter.
189
+ if (/\.server\.m?[jt]s$/.test(file)) continue;
190
+ if (serverFiles && serverFiles.has && serverFiles.has(file)) continue;
191
+ const fileBare = bare.get(file);
192
+ if (fileBare) for (const spec of fileBare) specs.add(spec);
193
+ }
194
+ return specs;
195
+ }