@webjsdev/server 0.8.81 → 0.8.82
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/component-elision.js +125 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.82",
|
|
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/component-elision.js
CHANGED
|
@@ -285,6 +285,118 @@ const PURE_DATA_CONSTRUCTORS = new Set([
|
|
|
285
285
|
'BigInt64Array', 'BigUint64Array',
|
|
286
286
|
]);
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Marks where `hasModuleScopeSideEffect` dropped a top-level `{...}` body from
|
|
290
|
+
* its depth-0 frame, so an arrow followed by a block body is not mistaken for
|
|
291
|
+
* an expression body that runs on into the next statement. Removed again
|
|
292
|
+
* before the call scan.
|
|
293
|
+
*/
|
|
294
|
+
const BLOCK = '\x01';
|
|
295
|
+
|
|
296
|
+
/** `function`, `function*`, `async function name` up to its parameter list's `(`. */
|
|
297
|
+
const FUNCTION_PARAMS_RE = /(?<![\w$.])function\s*\*?\s*(?:[A-Za-z_$][\w$]*)?\s*\(/g;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The text before an arrow's `=>` when that arrow is a declarator initializer
|
|
301
|
+
* (`const usd = (m) =>`, `let f = async x =>`, `, g = (a) =>`). The parameter
|
|
302
|
+
* list must hold no parens, so a call in a parameter default
|
|
303
|
+
* (`(a = init()) =>`) keeps its arrow unrecognised and still ships.
|
|
304
|
+
*/
|
|
305
|
+
const ARROW_DECLARATOR_RE =
|
|
306
|
+
/(?:(?<![\w$.])(?:const|let|var)\s+|,\s*)[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*$/;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* An expression body that ends this way cannot stop at the newline (a binary
|
|
310
|
+
* operator, `?`, `:`, or a curried `=>`). Postfix `++` / `--` and a regex
|
|
311
|
+
* literal's closing `/` end a statement, so neither is listed.
|
|
312
|
+
*/
|
|
313
|
+
const BODY_CONTINUES_RE = /(?:=>|&&|\|\||\?\?|[?:=*%|&^<>]|(?<![+])\+|(?<![-])-)\s*$/;
|
|
314
|
+
|
|
315
|
+
/** A line starting this way continues the previous one: no statement starts with it. */
|
|
316
|
+
const LINE_CONTINUES_RE = /^\s*(?:\?|:|&&|\|\||\.(?![.\d])|\*|%|=(?!>)|\|)/;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Removes the two depth-0 shapes that look like calls and run nothing at module
|
|
320
|
+
* load: a `function` declaration's parameter list (a default such as
|
|
321
|
+
* `now = Date.now()` runs per call) and an arrow's expression body when the
|
|
322
|
+
* arrow initializes a declarator (`const usd = (m) => Math.round(m) / MICROS`).
|
|
323
|
+
*
|
|
324
|
+
* The arrow body ends at a `,` or `;` at its own paren/bracket depth 0, at a
|
|
325
|
+
* `)` / `]` closing something outside it, or at a newline once the body has
|
|
326
|
+
* content, unless `BODY_CONTINUES_RE` / `LINE_CONTINUES_RE` prove the
|
|
327
|
+
* expression carries on. Ending early is always safe, because whatever is left
|
|
328
|
+
* stays in the frame and is still scanned; ending late could swallow a real
|
|
329
|
+
* statement, so every rule errs towards ending.
|
|
330
|
+
*
|
|
331
|
+
* @param {string} frame depth-0 text, with `BLOCK` where a `{...}` was dropped
|
|
332
|
+
* @returns {string | null} the frame without those parts, or null when a
|
|
333
|
+
* parameter list does not close (the caller then ships)
|
|
334
|
+
*/
|
|
335
|
+
function blankDeclarationOnlyCode(frame) {
|
|
336
|
+
let out = '';
|
|
337
|
+
let last = 0;
|
|
338
|
+
for (const m of frame.matchAll(FUNCTION_PARAMS_RE)) {
|
|
339
|
+
if (m.index < last) continue;
|
|
340
|
+
const open = m.index + m[0].length - 1;
|
|
341
|
+
let depth = 0;
|
|
342
|
+
let close = -1;
|
|
343
|
+
for (let j = open; j < frame.length; j++) {
|
|
344
|
+
if (frame[j] === '(') depth++;
|
|
345
|
+
else if (frame[j] === ')' && --depth === 0) { close = j; break; }
|
|
346
|
+
}
|
|
347
|
+
if (close < 0) return null;
|
|
348
|
+
out += frame.slice(last, open + 1);
|
|
349
|
+
last = close;
|
|
350
|
+
}
|
|
351
|
+
frame = out + frame.slice(last);
|
|
352
|
+
|
|
353
|
+
out = '';
|
|
354
|
+
let depth = 0;
|
|
355
|
+
for (let i = 0; i < frame.length; i++) {
|
|
356
|
+
const c = frame[i];
|
|
357
|
+
if (c === '(' || c === '[') depth++;
|
|
358
|
+
else if ((c === ')' || c === ']') && depth > 0) depth--;
|
|
359
|
+
if (depth === 0 && c === '=' && frame[i + 1] === '>' && ARROW_DECLARATOR_RE.test(out)) {
|
|
360
|
+
out += '=>';
|
|
361
|
+
i = arrowBodyEnd(frame, i + 2) - 1;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
out += c;
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Index where an arrow's expression body starting at `start` ends (see
|
|
371
|
+
* `blankDeclarationOnlyCode`).
|
|
372
|
+
*
|
|
373
|
+
* @param {string} frame
|
|
374
|
+
* @param {number} start index just past the `=>`
|
|
375
|
+
* @returns {number}
|
|
376
|
+
*/
|
|
377
|
+
function arrowBodyEnd(frame, start) {
|
|
378
|
+
let depth = 0;
|
|
379
|
+
let i = start;
|
|
380
|
+
for (; i < frame.length; i++) {
|
|
381
|
+
const c = frame[i];
|
|
382
|
+
if (c === '(' || c === '[') { depth++; continue; }
|
|
383
|
+
if (c === ')' || c === ']') {
|
|
384
|
+
if (depth === 0) return i;
|
|
385
|
+
depth--;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (depth > 0) continue;
|
|
389
|
+
if (c === ',' || c === ';') return i;
|
|
390
|
+
if (c === '\n') {
|
|
391
|
+
const body = frame.slice(start, i);
|
|
392
|
+
if (!body.trim() || BODY_CONTINUES_RE.test(body)) continue;
|
|
393
|
+
if (LINE_CONTINUES_RE.test(frame.slice(i + 1))) continue;
|
|
394
|
+
return i;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return i;
|
|
398
|
+
}
|
|
399
|
+
|
|
288
400
|
/**
|
|
289
401
|
* Module-scope client work, detected by an ALLOWLIST of safe top-level forms
|
|
290
402
|
* rather than a denylist of browser globals. A module that runs ANY code when
|
|
@@ -320,8 +432,13 @@ const PURE_DATA_CONSTRUCTORS = new Set([
|
|
|
320
432
|
* request path is covered; a DIRECT caller handing it `.ts` source as authored
|
|
321
433
|
* is opting into that false positive.
|
|
322
434
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
435
|
+
* A `function` declaration's parameter list and the expression body of an
|
|
436
|
+
* arrow that initializes a `const` / `let` / `var` declarator are dropped from
|
|
437
|
+
* the frame first (`blankDeclarationOnlyCode`): `now = Date.now()` and
|
|
438
|
+
* `(m) => Math.round(m) / MICROS` declare code, they do not run it.
|
|
439
|
+
*
|
|
440
|
+
* Over-detection is safe (a pure top-level helper call, or an arrow outside a
|
|
441
|
+
* declarator whose body calls something, only ships). The accepted residual misses, all
|
|
325
442
|
* contrived and structural (so they do not rot), are a call buried inside a
|
|
326
443
|
* top-level object / array initializer or a destructuring default, and a
|
|
327
444
|
* side-effecting tagged-template hole evaluated at module scope.
|
|
@@ -361,7 +478,7 @@ export function hasModuleScopeSideEffect(src, literals) {
|
|
|
361
478
|
if (depth === 0) frame += "''";
|
|
362
479
|
continue;
|
|
363
480
|
}
|
|
364
|
-
if (c === '{') { depth++; continue; }
|
|
481
|
+
if (c === '{') { if (depth === 0) frame += BLOCK; depth++; continue; }
|
|
365
482
|
if (c === '}') { if (depth > 0) depth--; continue; }
|
|
366
483
|
if (depth === 0) frame += c;
|
|
367
484
|
}
|
|
@@ -369,6 +486,11 @@ export function hasModuleScopeSideEffect(src, literals) {
|
|
|
369
486
|
// literal with a stray brace is the common case). Ship rather than risk a
|
|
370
487
|
// hidden top-level statement.
|
|
371
488
|
if (depth !== 0) return true;
|
|
489
|
+
// Parameter lists and declarator arrow bodies are declarations, not code
|
|
490
|
+
// that runs at load.
|
|
491
|
+
const declared = blankDeclarationOnlyCode(frame);
|
|
492
|
+
if (declared === null) return true;
|
|
493
|
+
frame = declared.replaceAll(BLOCK, '');
|
|
372
494
|
// Optional-chaining call/index: `foo?.()`, `x?.[i]()` (the `?.` defeats the
|
|
373
495
|
// identifier-before-paren match below).
|
|
374
496
|
if (/\?\.\s*[([]/.test(frame)) return true;
|