@webjsdev/server 0.8.62 → 0.8.64
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/index.d.ts +32 -3
- package/package.json +2 -2
- package/src/browser-entries.js +64 -0
- package/src/check/helpers.js +365 -0
- package/src/check/rules-actions.js +174 -0
- package/src/check/rules-components.js +275 -0
- package/src/check/rules-imports.js +166 -0
- package/src/check/rules-registry.js +128 -0
- package/src/check/rules-routing.js +224 -0
- package/src/check/rules-typescript.js +177 -0
- package/src/check/rules.js +127 -0
- package/src/check/runner-support.js +405 -0
- package/src/check/runner.js +113 -0
- package/src/check.js +4 -1774
- package/src/component-elision.js +1 -1
- package/src/dev/config.js +338 -0
- package/src/dev/handler.js +1372 -0
- package/src/dev/helpers.js +542 -0
- package/src/dev/serve.js +840 -0
- package/src/dev/server.js +322 -0
- package/src/dev-classify.js +174 -0
- package/src/dev-reload-worker.js +133 -7
- package/src/dev-styles.js +97 -0
- package/src/dev.js +22 -3077
- package/src/form-dispatch.js +12 -3
- package/src/importmap.js +6 -3
- package/src/listener-core.js +16 -2
- package/src/module-graph.js +112 -7
- package/src/rate-limit.js +50 -5
- package/src/router.js +11 -1
- package/src/ssr/client-router-flag.js +42 -0
- package/src/ssr/document.js +361 -0
- package/src/ssr/env-shim.js +51 -0
- package/src/ssr/escape.js +29 -0
- package/src/ssr/head.js +857 -0
- package/src/ssr/preloads.js +195 -0
- package/src/ssr/render.js +1481 -0
- package/src/ssr/responses.js +201 -0
- package/src/ssr.js +28 -2445
- package/src/vendor/audit.js +400 -0
- package/src/vendor/integrity.js +245 -0
- package/src/vendor/jspm.js +348 -0
- package/src/vendor/manifest.js +96 -0
- package/src/vendor/pins.js +892 -0
- package/src/vendor/providers.js +35 -0
- package/src/vendor/resolver.js +225 -0
- package/src/vendor/scanner.js +132 -0
- 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
|
+
}
|