@webjsdev/server 0.8.63 → 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 +7 -2
- 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/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
package/src/component-elision.js
CHANGED
|
@@ -244,7 +244,7 @@ const CLIENT_ROUTER_SUBPATH_RE = /['"]@webjsdev\/core\/client-router['"]/;
|
|
|
244
244
|
* REACTIVE_IMPORTS: a router function the barrel re-exports but this list omits
|
|
245
245
|
* is not recognized as client work, so a component using it is wrongly elided.
|
|
246
246
|
*/
|
|
247
|
-
export const CLIENT_ROUTER_IMPORTS = ['navigate', 'enableClientRouter', 'disableClientRouter', 'revalidate', 'loadFrame'];
|
|
247
|
+
export const CLIENT_ROUTER_IMPORTS = ['navigate', 'enableClientRouter', 'disableClientRouter', 'revalidate', 'refreshPage', 'loadFrame'];
|
|
248
248
|
|
|
249
249
|
/** Identifiers that only exist in a browser; their presence means client work. */
|
|
250
250
|
const CLIENT_GLOBAL_RE = /\b(?:window|document|navigator|localStorage|sessionStorage|customElements|matchMedia|addEventListener)\b/;
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve, sep } from 'node:path';
|
|
3
|
+
import { compileHeaderRules } from '../headers.js';
|
|
4
|
+
import { compileRedirectRules, readTrailingSlashPolicy } from '../redirects.js';
|
|
5
|
+
import { readBasePath } from '../base-path.js';
|
|
6
|
+
import { validateAppWebjsConfig } from '../webjs-config-validate.js';
|
|
7
|
+
import { readAllowedOrigins } from '../csrf.js';
|
|
8
|
+
import { readCspConfig } from '../csp.js';
|
|
9
|
+
import { readBodyLimits, computeServerTimeouts } from '../body-limit.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Read the `WEBJS_ELIDE` environment override, if set.
|
|
13
|
+
* `0` / `false` / `off` / `no` (case-insensitive) force elision OFF;
|
|
14
|
+
* `1` / `true` / `on` / `yes` force it ON. Any other value, or an unset
|
|
15
|
+
* variable, returns `undefined` so the caller falls through to the
|
|
16
|
+
* `package.json` switch. The env override is the deploy-time / ops escape
|
|
17
|
+
* hatch: force-disable elision to rule it out while debugging a wrong-strip
|
|
18
|
+
* without editing committed code, or force-enable it regardless of an
|
|
19
|
+
* app's `package.json`. It is also the seam the differential elision test
|
|
20
|
+
* uses to render the same app on and off in one process.
|
|
21
|
+
* @returns {boolean | undefined}
|
|
22
|
+
*/
|
|
23
|
+
function elideEnvOverride() {
|
|
24
|
+
const raw = process.env.WEBJS_ELIDE;
|
|
25
|
+
if (raw == null || raw === '') return undefined;
|
|
26
|
+
const v = String(raw).trim().toLowerCase();
|
|
27
|
+
if (v === '0' || v === 'false' || v === 'off' || v === 'no') return false;
|
|
28
|
+
if (v === '1' || v === 'true' || v === 'on' || v === 'yes') return true;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read the project-level elision switch.
|
|
34
|
+
* Precedence: the `WEBJS_ELIDE` env override wins when set, otherwise the
|
|
35
|
+
* `package.json` `{ "webjs": { "elide": false } }` switch disables
|
|
36
|
+
* display-only and inert-route elision app-wide (everything ships, like
|
|
37
|
+
* before the feature existed). Any other value, or an absent key, leaves
|
|
38
|
+
* elision enabled (the default). Re-read on every rebuild so toggling
|
|
39
|
+
* either control takes effect without a server restart.
|
|
40
|
+
* @param {string} appDir
|
|
41
|
+
* @returns {Promise<boolean>}
|
|
42
|
+
*/
|
|
43
|
+
export async function readElideEnabled(appDir) {
|
|
44
|
+
const override = elideEnvOverride();
|
|
45
|
+
if (override !== undefined) return override;
|
|
46
|
+
try {
|
|
47
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
48
|
+
if (pkg && pkg.webjs && pkg.webjs.elide === false) return false;
|
|
49
|
+
} catch {
|
|
50
|
+
// No package.json, malformed JSON, or unreadable. Keep the default.
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Read the `WEBJS_SEED` environment override, if set. Same grammar as
|
|
57
|
+
* `WEBJS_ELIDE`: `0`/`false`/`off`/`no` force seeding OFF, `1`/`true`/`on`/`yes`
|
|
58
|
+
* force it ON, anything else (or unset) falls through to the package.json
|
|
59
|
+
* switch. The deploy-time / ops escape hatch and the seam tests use to render
|
|
60
|
+
* an app with seeding on and off in one process.
|
|
61
|
+
* @returns {boolean | undefined}
|
|
62
|
+
*/
|
|
63
|
+
function seedEnvOverride() {
|
|
64
|
+
const raw = process.env.WEBJS_SEED;
|
|
65
|
+
if (raw == null || raw === '') return undefined;
|
|
66
|
+
const v = String(raw).trim().toLowerCase();
|
|
67
|
+
if (v === '0' || v === 'false' || v === 'off' || v === 'no') return false;
|
|
68
|
+
if (v === '1' || v === 'true' || v === 'on' || v === 'yes') return true;
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read the project-level SSR action-seeding switch (#472). Precedence: the
|
|
74
|
+
* `WEBJS_SEED` env override wins when set, otherwise the package.json
|
|
75
|
+
* `{ "webjs": { "seed": false } }` switch disables it (the client re-fetches on
|
|
76
|
+
* hydration as it did before the feature). Default ON (opt-out), mirroring
|
|
77
|
+
* elision: any value other than the literal `false` keeps seeding enabled.
|
|
78
|
+
*
|
|
79
|
+
* Seeding installs a process-global `module.registerHooks` load hook, so unlike
|
|
80
|
+
* elision it is read ONCE at boot (not re-read per rebuild): toggling it needs a
|
|
81
|
+
* restart, like a deploy.
|
|
82
|
+
* @param {string} appDir
|
|
83
|
+
* @returns {Promise<boolean>}
|
|
84
|
+
*/
|
|
85
|
+
export async function readSeedEnabled(appDir) {
|
|
86
|
+
const override = seedEnvOverride();
|
|
87
|
+
if (override !== undefined) return override;
|
|
88
|
+
try {
|
|
89
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
90
|
+
if (pkg && pkg.webjs && pkg.webjs.seed === false) return false;
|
|
91
|
+
} catch {
|
|
92
|
+
// No package.json, malformed JSON, or unreadable. Keep the default.
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Read the client-router switch (`webjs.clientRouter`) from the app's
|
|
99
|
+
* package.json (#629). Default `true`: the client router auto-enables in the
|
|
100
|
+
* browser whenever `@webjsdev/core` loads (the automatic-navigation thesis).
|
|
101
|
+
* `{ "webjs": { "clientRouter": false } }` opts the WHOLE app out (pure MPA,
|
|
102
|
+
* full-page navigation), which the render path signals to the browser so the
|
|
103
|
+
* core bundle's module-end auto-enable is skipped. Any other value, or an
|
|
104
|
+
* absent key, keeps the router on. Re-read on every rebuild (alongside elide)
|
|
105
|
+
* so toggling it takes effect without a server restart.
|
|
106
|
+
* @param {string} appDir
|
|
107
|
+
* @returns {Promise<boolean>}
|
|
108
|
+
*/
|
|
109
|
+
export async function readClientRouterEnabled(appDir) {
|
|
110
|
+
try {
|
|
111
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
112
|
+
if (pkg && pkg.webjs && pkg.webjs.clientRouter === false) return false;
|
|
113
|
+
} catch {
|
|
114
|
+
// No package.json, malformed JSON, or unreadable. Keep the default.
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read the per-path response-header config (`webjs.headers`) from the
|
|
121
|
+
* app's package.json and compile it to URLPattern rules. A missing,
|
|
122
|
+
* malformed, or unreadable config yields an empty rule set (the secure
|
|
123
|
+
* defaults still apply), never a throw.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} appDir
|
|
126
|
+
* @returns {Promise<ReturnType<typeof compileHeaderRules>>}
|
|
127
|
+
*/
|
|
128
|
+
export async function readHeaderRules(appDir) {
|
|
129
|
+
try {
|
|
130
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
131
|
+
return compileHeaderRules(pkg);
|
|
132
|
+
} catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Read the declarative redirect config (`webjs.redirects`) from the app's
|
|
139
|
+
* package.json and compile it to URLPattern rules (issue #254). A missing,
|
|
140
|
+
* malformed, or unreadable config yields an empty rule set (no redirects),
|
|
141
|
+
* never a throw. Patterns are compiled ONCE here at boot, not per request.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} appDir
|
|
144
|
+
* @returns {Promise<ReturnType<typeof compileRedirectRules>>}
|
|
145
|
+
*/
|
|
146
|
+
export async function readRedirectRules(appDir) {
|
|
147
|
+
try {
|
|
148
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
149
|
+
return compileRedirectRules(pkg);
|
|
150
|
+
} catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read the trailing-slash policy (`webjs.trailingSlash`) from the app's
|
|
157
|
+
* package.json (issue #255). A missing, malformed, or unreadable config
|
|
158
|
+
* yields `'ignore'` (no canonicalization), never a throw, so an
|
|
159
|
+
* unconfigured app is unchanged.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} appDir
|
|
162
|
+
* @returns {Promise<'never' | 'always' | 'ignore'>}
|
|
163
|
+
*/
|
|
164
|
+
export async function readTrailingSlashFromApp(appDir) {
|
|
165
|
+
try {
|
|
166
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
167
|
+
return readTrailingSlashPolicy(pkg);
|
|
168
|
+
} catch {
|
|
169
|
+
return 'ignore';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Read the sub-path base path (`webjs.basePath`) from the app's
|
|
175
|
+
* package.json (issue #256). A missing, malformed, or unreadable config
|
|
176
|
+
* yields `''` (root mount), never a throw, so an unconfigured app is
|
|
177
|
+
* byte-identical to before this feature. Normalized to `''` or
|
|
178
|
+
* `/segment[/segment...]` by `readBasePath`.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} appDir
|
|
181
|
+
* @returns {Promise<string>}
|
|
182
|
+
*/
|
|
183
|
+
export async function readBasePathFromApp(appDir) {
|
|
184
|
+
try {
|
|
185
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
186
|
+
return readBasePath(pkg);
|
|
187
|
+
} catch {
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Read the app package.json and warn once about anything wrong with its `webjs`
|
|
194
|
+
* block (#1300). One aggregated warning, listing every problem, so a config with
|
|
195
|
+
* three typos does not produce three log lines.
|
|
196
|
+
*
|
|
197
|
+
* Never throws and never exits. See `webjs-config-validate.js` for why warning
|
|
198
|
+
* is the ruling and why this does not replace the CLI's `doctor.gate` check.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} appDir
|
|
201
|
+
* @param {{ warn?: (...args: any[]) => void }} logger
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
*/
|
|
204
|
+
export async function warnOnInvalidWebjsConfig(appDir, logger) {
|
|
205
|
+
let problems;
|
|
206
|
+
try {
|
|
207
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
208
|
+
problems = validateAppWebjsConfig(pkg);
|
|
209
|
+
} catch {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!problems.length) return;
|
|
213
|
+
logger?.warn?.(
|
|
214
|
+
`[webjs] the "webjs" block in package.json has ${problems.length} problem(s), ` +
|
|
215
|
+
`each ignored at its default: ${problems.join('; ')}. ` +
|
|
216
|
+
`See https://webjs.dev/docs/configuration`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Read the cross-origin allowlist (`webjs.allowedOrigins`) from the app's
|
|
222
|
+
* package.json. These hosts / origins are accepted by the action CSRF check
|
|
223
|
+
* even when cross-site (reverse-proxy / multi-domain setups). A missing or
|
|
224
|
+
* unreadable config yields `[]`.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} appDir
|
|
227
|
+
* @returns {Promise<string[]>}
|
|
228
|
+
*/
|
|
229
|
+
export async function readAllowedOriginsFromApp(appDir) {
|
|
230
|
+
try {
|
|
231
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
232
|
+
return readAllowedOrigins(pkg);
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Read the CSP config (`webjs.csp`) from the app's package.json and
|
|
240
|
+
* normalize it (issue #233). A missing, malformed, or unreadable config
|
|
241
|
+
* yields a disabled config (no nonce minted, no CSP header), never a
|
|
242
|
+
* throw: a broken security knob must fail closed, not take the app down.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} appDir
|
|
245
|
+
* @returns {Promise<ReturnType<typeof readCspConfig>>}
|
|
246
|
+
*/
|
|
247
|
+
export async function readCspConfigFromApp(appDir) {
|
|
248
|
+
try {
|
|
249
|
+
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
250
|
+
return readCspConfig(pkg);
|
|
251
|
+
} catch {
|
|
252
|
+
return readCspConfig(undefined);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Resolve the request body-size limits (issue #237) from the app's package.json
|
|
258
|
+
* `webjs.maxBodyBytes` / `webjs.maxMultipartBytes` plus the env overrides
|
|
259
|
+
* (`WEBJS_MAX_BODY_BYTES` / `WEBJS_MAX_MULTIPART_BYTES`). A missing or
|
|
260
|
+
* unreadable package.json falls through to the secure defaults (env still wins),
|
|
261
|
+
* never a throw.
|
|
262
|
+
*
|
|
263
|
+
* @param {string} appDir
|
|
264
|
+
* @returns {Promise<{ json: number, multipart: number }>}
|
|
265
|
+
*/
|
|
266
|
+
export async function readBodyLimitsFromApp(appDir) {
|
|
267
|
+
let pkg;
|
|
268
|
+
try {
|
|
269
|
+
pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
270
|
+
} catch {
|
|
271
|
+
pkg = undefined;
|
|
272
|
+
}
|
|
273
|
+
return readBodyLimits(pkg);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Extra dev watch roots from the app's package.json `webjs.dev.watch` (#894).
|
|
278
|
+
* These are directories the app READS from but that live OUTSIDE its appDir, so
|
|
279
|
+
* the recursive `fs.watch(appDir)` never sees them (e.g. the website renders
|
|
280
|
+
* posts from a repo-root `blog/` dir, a sibling of the app, so editing a post
|
|
281
|
+
* would not live-reload). Each entry is resolved relative to the appDir and MAY
|
|
282
|
+
* escape it (`"../blog"`); a change under one runs the same rebuild + reload as
|
|
283
|
+
* an in-tree edit. Opt-in: a missing/empty config yields `[]`, so a plain app
|
|
284
|
+
* watches only its appDir, unchanged.
|
|
285
|
+
*
|
|
286
|
+
* Returns absolute, de-duped paths, skipping any that overlap the appDir (the
|
|
287
|
+
* appDir is already watched recursively, so an ancestor or descendant root
|
|
288
|
+
* would just double-fire the rebuild). Existence is NOT checked here (the caller
|
|
289
|
+
* filters missing paths so it can log them); the reader stays a pure
|
|
290
|
+
* package.json read, matching the other `readXFromApp` helpers.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} appDir
|
|
293
|
+
* @returns {Promise<string[]>}
|
|
294
|
+
*/
|
|
295
|
+
export async function readDevWatchPathsFromApp(appDir) {
|
|
296
|
+
let pkg;
|
|
297
|
+
try {
|
|
298
|
+
pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
299
|
+
} catch {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
const raw = pkg && pkg.webjs && pkg.webjs.dev && pkg.webjs.dev.watch;
|
|
303
|
+
if (!Array.isArray(raw)) return [];
|
|
304
|
+
/** @type {string[]} */
|
|
305
|
+
const out = [];
|
|
306
|
+
const seen = new Set();
|
|
307
|
+
for (const entry of raw) {
|
|
308
|
+
if (typeof entry !== 'string' || !entry.trim()) continue;
|
|
309
|
+
const abs = resolve(appDir, entry);
|
|
310
|
+
// Skip the appDir itself and any ancestor/descendant overlap: the appDir is
|
|
311
|
+
// already watched recursively, so an overlapping extra root double-fires.
|
|
312
|
+
if (abs === appDir || appDir.startsWith(abs + sep) || abs.startsWith(appDir + sep)) continue;
|
|
313
|
+
if (seen.has(abs)) continue;
|
|
314
|
+
seen.add(abs);
|
|
315
|
+
out.push(abs);
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Resolve the node:http server timeouts (issue #237) from the app's
|
|
322
|
+
* package.json `webjs.requestTimeoutMs` / `webjs.headersTimeoutMs` /
|
|
323
|
+
* `webjs.keepAliveTimeoutMs` plus the env overrides. A missing or unreadable
|
|
324
|
+
* package.json falls through to the secure defaults (env still wins).
|
|
325
|
+
*
|
|
326
|
+
* @param {string} appDir
|
|
327
|
+
* @returns {Promise<{ requestTimeout: number, headersTimeout: number, keepAliveTimeout: number }>}
|
|
328
|
+
*/
|
|
329
|
+
export async function readServerTimeoutsFromApp(appDir) {
|
|
330
|
+
let pkg;
|
|
331
|
+
try {
|
|
332
|
+
pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
333
|
+
} catch {
|
|
334
|
+
pkg = undefined;
|
|
335
|
+
}
|
|
336
|
+
return computeServerTimeouts(pkg);
|
|
337
|
+
}
|
|
338
|
+
|