@stacksjs/stx 0.2.169 → 0.2.171
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/dist/cli.js +1166 -1162
- package/dist/client-script-bundler.js +2 -1
- package/dist/client-script.d.ts +1 -12
- package/dist/client-script.js +2 -149
- package/dist/codemod.js +5 -0
- package/dist/compression.d.ts +11 -4
- package/dist/compression.js +57 -13
- package/dist/craft.js +400 -405
- package/dist/expressions.d.ts +0 -21
- package/dist/expressions.js +12 -1
- package/dist/find-body-tag.js +29 -17
- package/dist/index.d.ts +9 -0
- package/dist/index.js +3 -0
- package/dist/page-action.d.ts +83 -0
- package/dist/page-action.js +64 -0
- package/dist/production-server.js +10 -3
- package/dist/pwa.js +304 -309
- package/dist/reactive-tracking.d.ts +81 -0
- package/dist/reactive-tracking.js +49 -0
- package/dist/reactivity.js +29 -23
- package/dist/runtime-injection.js +25 -39
- package/dist/serve.js +2 -1
- package/dist/signals-api.d.ts +18 -0
- package/dist/signals-api.js +26 -41
- package/dist/ssg.d.ts +8 -0
- package/dist/ssg.js +421 -426
- package/dist/state-management.js +2 -2
- package/dist/strip-literals.d.ts +40 -0
- package/dist/strip-literals.js +149 -0
- package/dist/stx-virtual-ts.d.ts +90 -1
- package/dist/stx-virtual-ts.js +90 -2
- package/dist/template-compiler.js +4 -2
- package/dist/template-hydrator.d.ts +1 -1
- package/dist/template-hydrator.js +13 -1
- package/dist/typecheck.d.ts +1 -1
- package/dist/typecheck.js +35 -7
- package/dist/types/config-types.d.ts +10 -3
- package/package.json +5 -5
|
@@ -30,7 +30,8 @@ export function resolveBundlerFallback() {
|
|
|
30
30
|
if (_fallbackOverride)
|
|
31
31
|
return _fallbackOverride;
|
|
32
32
|
try {
|
|
33
|
-
|
|
33
|
+
const strict = config.strict;
|
|
34
|
+
return typeof strict === "object" && strict !== null ? strict.bundlerFallback ?? "warn" : "warn";
|
|
34
35
|
} catch {
|
|
35
36
|
return "warn";
|
|
36
37
|
}
|
package/dist/client-script.d.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
* @module client-script
|
|
33
33
|
*/
|
|
34
34
|
import type { ParsedEvent } from './events';
|
|
35
|
+
export { findInterpolationEnd, stripCommentsAndLiterals } from './strip-literals';
|
|
35
36
|
export interface VendorCssImport {
|
|
36
37
|
/** The original specifier as written in the import — kept verbatim for the data attribute. */
|
|
37
38
|
source: string;
|
|
@@ -117,18 +118,6 @@ export interface BrowserCoreAutoImportResult {
|
|
|
117
118
|
* helpers together with the component, exactly like an explicit user import.
|
|
118
119
|
*/
|
|
119
120
|
export declare function injectBrowserCoreAutoImports(code: string): BrowserCoreAutoImportResult;
|
|
120
|
-
/**
|
|
121
|
-
* Blank out comments and string literals, preserving length and line structure
|
|
122
|
-
* so offsets still line up, so that identifier detection only ever sees code.
|
|
123
|
-
*
|
|
124
|
-
* Template-literal INTERPOLATIONS are kept, because `${...}` is code, not text.
|
|
125
|
-
* Blanking them hid every identifier that appears only inside one — a helper
|
|
126
|
-
* called as `${formatDate(d)}` looked unused to the auto-import scanner, and a
|
|
127
|
-
* server value read as `` `/api/users/${userId}` `` looked unreferenced to the
|
|
128
|
-
* data bridge, which would have withheld it and left the client throwing on an
|
|
129
|
-
* undefined name.
|
|
130
|
-
*/
|
|
131
|
-
export declare function stripCommentsAndLiterals(code: string): string;
|
|
132
121
|
export interface ClientScriptOptions {
|
|
133
122
|
/** Event bindings collected from template @event attributes */
|
|
134
123
|
eventBindings?: ParsedEvent[];
|
package/dist/client-script.js
CHANGED
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { asInvocableStatement } from "./events.js";
|
|
4
4
|
import { transformStoreImports } from "./store-imports.js";
|
|
5
|
+
import { findInterpolationEnd, stripCommentsAndLiterals } from "./strip-literals.js";
|
|
6
|
+
export { findInterpolationEnd, stripCommentsAndLiterals } from "./strip-literals.js";
|
|
5
7
|
import { shouldTranspileTypeScript, transpileTypeScript } from "./utils.js";
|
|
6
8
|
import { importOnce } from "./lazy-module.js";
|
|
7
9
|
import { STX_RUNTIME_GLOBALS, usesReactiveRuntime } from "./runtime-globals.js";
|
|
@@ -263,155 +265,6 @@ const JS_BUILTINS = new Set([
|
|
|
263
265
|
"Modal",
|
|
264
266
|
"Intl"
|
|
265
267
|
]);
|
|
266
|
-
function skipQuoted(code, start) {
|
|
267
|
-
const quote = code[start];
|
|
268
|
-
let i = start + 1;
|
|
269
|
-
while (i < code.length) {
|
|
270
|
-
if (code[i] === "\\") {
|
|
271
|
-
i += 2;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
if (code[i] === quote)
|
|
275
|
-
return i + 1;
|
|
276
|
-
i++;
|
|
277
|
-
}
|
|
278
|
-
return code.length;
|
|
279
|
-
}
|
|
280
|
-
function skipTemplate(code, start) {
|
|
281
|
-
let i = start + 1;
|
|
282
|
-
while (i < code.length) {
|
|
283
|
-
if (code[i] === "\\") {
|
|
284
|
-
i += 2;
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
if (code[i] === "`")
|
|
288
|
-
return i + 1;
|
|
289
|
-
if (code[i] === "$" && code[i + 1] === "{") {
|
|
290
|
-
i = findInterpolationEnd(code, i + 2);
|
|
291
|
-
if (i < code.length)
|
|
292
|
-
i++;
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
i++;
|
|
296
|
-
}
|
|
297
|
-
return code.length;
|
|
298
|
-
}
|
|
299
|
-
function findInterpolationEnd(code, start) {
|
|
300
|
-
let depth = 1, i = start;
|
|
301
|
-
while (i < code.length) {
|
|
302
|
-
const ch = code[i];
|
|
303
|
-
if (ch === "\\") {
|
|
304
|
-
i += 2;
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (ch === '"' || ch === "'") {
|
|
308
|
-
i = skipQuoted(code, i);
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (ch === "`") {
|
|
312
|
-
i = skipTemplate(code, i);
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
if (ch === "/" && code[i + 1] === "/") {
|
|
316
|
-
while (i < code.length && code[i] !== `
|
|
317
|
-
`)
|
|
318
|
-
i++;
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
if (ch === "/" && code[i + 1] === "*") {
|
|
322
|
-
i += 2;
|
|
323
|
-
while (i < code.length && code.slice(i, i + 2) !== "*/")
|
|
324
|
-
i++;
|
|
325
|
-
i += 2;
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
if (ch === "{") {
|
|
329
|
-
depth++;
|
|
330
|
-
i++;
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
if (ch === "}") {
|
|
334
|
-
depth--;
|
|
335
|
-
if (depth === 0)
|
|
336
|
-
return i;
|
|
337
|
-
i++;
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
i++;
|
|
341
|
-
}
|
|
342
|
-
return code.length;
|
|
343
|
-
}
|
|
344
|
-
export function stripCommentsAndLiterals(code) {
|
|
345
|
-
let out = "", i = 0;
|
|
346
|
-
while (i < code.length) {
|
|
347
|
-
const two = code.slice(i, i + 2);
|
|
348
|
-
if (two === "//") {
|
|
349
|
-
while (i < code.length && code[i] !== `
|
|
350
|
-
`)
|
|
351
|
-
out += " ", i++;
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (two === "/*") {
|
|
355
|
-
while (i < code.length && code.slice(i, i + 2) !== "*/")
|
|
356
|
-
out += code[i] === `
|
|
357
|
-
` ? `
|
|
358
|
-
` : " ", i++;
|
|
359
|
-
out += " ";
|
|
360
|
-
i += 2;
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
const ch = code[i];
|
|
364
|
-
if (ch === '"' || ch === "'") {
|
|
365
|
-
const quote = ch;
|
|
366
|
-
out += " ";
|
|
367
|
-
i++;
|
|
368
|
-
while (i < code.length && code[i] !== quote) {
|
|
369
|
-
if (code[i] === "\\") {
|
|
370
|
-
out += " ";
|
|
371
|
-
i += 2;
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
out += code[i] === `
|
|
375
|
-
` ? `
|
|
376
|
-
` : " ";
|
|
377
|
-
i++;
|
|
378
|
-
}
|
|
379
|
-
out += " ";
|
|
380
|
-
i++;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (ch === "`") {
|
|
384
|
-
out += " ";
|
|
385
|
-
i++;
|
|
386
|
-
while (i < code.length && code[i] !== "`") {
|
|
387
|
-
if (code[i] === "\\") {
|
|
388
|
-
out += " ";
|
|
389
|
-
i += 2;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (code[i] === "$" && code[i + 1] === "{") {
|
|
393
|
-
const bodyStart = i + 2, bodyEnd = findInterpolationEnd(code, bodyStart);
|
|
394
|
-
out += " ";
|
|
395
|
-
out += stripCommentsAndLiterals(code.slice(bodyStart, bodyEnd));
|
|
396
|
-
if (bodyEnd < code.length)
|
|
397
|
-
out += " ";
|
|
398
|
-
i = bodyEnd + 1;
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
out += code[i] === `
|
|
402
|
-
` ? `
|
|
403
|
-
` : " ";
|
|
404
|
-
i++;
|
|
405
|
-
}
|
|
406
|
-
out += " ";
|
|
407
|
-
i++;
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
out += ch;
|
|
411
|
-
i++;
|
|
412
|
-
}
|
|
413
|
-
return out;
|
|
414
|
-
}
|
|
415
268
|
function detectModelUsage(code) {
|
|
416
269
|
const models = new Set, modelPattern = /\b([A-Z][a-zA-Z0-9]*)\s*\.\s*(all|find|first|get|where|orderBy|orderByDesc|limit|select|pluck|create|update|delete)\s*\(/g;
|
|
417
270
|
let match;
|
package/dist/codemod.js
CHANGED
|
@@ -87,7 +87,12 @@ function isInsideAsyncFunction(source, index) {
|
|
|
87
87
|
const window = source.slice(Math.max(0, boundary - 120), boundary);
|
|
88
88
|
return /\basync\b[^;{}]*$/.test(window);
|
|
89
89
|
}
|
|
90
|
+
function declaresOwnConfirm(source) {
|
|
91
|
+
return /\b(?:async\s+)?function\s+confirm\s*\(/.test(source) || /\b(?:const|let|var)\s+confirm\s*=/.test(source) || /\bimport\s*\{[^}]*\bconfirm\b[^}]*\}/.test(source);
|
|
92
|
+
}
|
|
90
93
|
function applyConfirmRule(source, file, findings) {
|
|
94
|
+
if (declaresOwnConfirm(source))
|
|
95
|
+
return source;
|
|
91
96
|
const pattern = /(^|[^\w$.])confirm\s*\(/g;
|
|
92
97
|
let out = "", last = 0, match;
|
|
93
98
|
while ((match = pattern.exec(source)) !== null) {
|
package/dist/compression.d.ts
CHANGED
|
@@ -3,10 +3,17 @@ export declare function isCompressible(contentType: string | null): boolean;
|
|
|
3
3
|
/**
|
|
4
4
|
* The best encoding both sides support, or `null` for none.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* that
|
|
6
|
+
* This used to be `accepted.includes('br')` on the raw header, on the argument
|
|
7
|
+
* that q-values are only ever sent by testing tools. The substring is the
|
|
8
|
+
* problem: `br;q=0` contains `br`, and it is the one string in the header that
|
|
9
|
+
* means *do not send me brotli*. Answering that with a brotli body is not a
|
|
10
|
+
* missed optimisation, it is a response the client cannot read — the failure
|
|
11
|
+
* this function's `Vary` sibling exists to prevent, arrived at from the other
|
|
12
|
+
* direction.
|
|
13
|
+
*
|
|
14
|
+
* Brotli is still preferred when both are genuinely on offer: ~20% smaller
|
|
15
|
+
* than gzip on HTML at comparable cost, and every browser without it predates
|
|
16
|
+
* 2017.
|
|
10
17
|
*/
|
|
11
18
|
export declare function negotiateEncoding(acceptEncoding: string | null): 'br' | 'gzip' | null;
|
|
12
19
|
/**
|
package/dist/compression.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import zlib from "node:zlib";
|
|
2
|
-
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const brotliCompress = promisify(zlib.brotliCompress), gzipCompress = promisify(zlib.gzip), COMPRESSIBLE = [
|
|
3
4
|
"text/",
|
|
4
5
|
"application/json",
|
|
5
6
|
"application/javascript",
|
|
@@ -9,20 +10,44 @@ const COMPRESSIBLE = [
|
|
|
9
10
|
"application/atom+xml",
|
|
10
11
|
"application/manifest+json",
|
|
11
12
|
"image/svg+xml"
|
|
13
|
+
], NEVER_BUFFER = [
|
|
14
|
+
"text/event-stream",
|
|
15
|
+
"multipart/x-mixed-replace"
|
|
12
16
|
], MIN_BYTES = 1024;
|
|
13
17
|
export function isCompressible(contentType) {
|
|
14
18
|
if (!contentType)
|
|
15
19
|
return !1;
|
|
16
20
|
const type = contentType.toLowerCase();
|
|
21
|
+
if (NEVER_BUFFER.some((prefix) => type.startsWith(prefix)))
|
|
22
|
+
return !1;
|
|
17
23
|
return COMPRESSIBLE.some((prefix) => type.startsWith(prefix));
|
|
18
24
|
}
|
|
25
|
+
function parseAcceptEncoding(header) {
|
|
26
|
+
const weights = new Map;
|
|
27
|
+
for (const part of header.split(",")) {
|
|
28
|
+
const [rawName, ...params] = part.trim().split(";"), name = rawName.trim().toLowerCase();
|
|
29
|
+
if (!name)
|
|
30
|
+
continue;
|
|
31
|
+
let quality = 1;
|
|
32
|
+
for (const param of params) {
|
|
33
|
+
const match = /^\s*q\s*=\s*([\d.]+)\s*$/i.exec(param);
|
|
34
|
+
if (!match)
|
|
35
|
+
continue;
|
|
36
|
+
const parsed = Number.parseFloat(match[1]);
|
|
37
|
+
if (Number.isFinite(parsed))
|
|
38
|
+
quality = parsed;
|
|
39
|
+
}
|
|
40
|
+
weights.set(name, quality);
|
|
41
|
+
}
|
|
42
|
+
return weights;
|
|
43
|
+
}
|
|
19
44
|
export function negotiateEncoding(acceptEncoding) {
|
|
20
45
|
if (!acceptEncoding)
|
|
21
46
|
return null;
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
47
|
+
const weights = parseAcceptEncoding(acceptEncoding), wildcard = weights.get("*"), quality = (coding) => weights.get(coding) ?? wildcard ?? 0;
|
|
48
|
+
if (quality("br") > 0)
|
|
24
49
|
return "br";
|
|
25
|
-
if (
|
|
50
|
+
if (quality("gzip") > 0)
|
|
26
51
|
return "gzip";
|
|
27
52
|
return null;
|
|
28
53
|
}
|
|
@@ -36,28 +61,47 @@ export async function compressResponse(request, response, options = {}) {
|
|
|
36
61
|
const encoding = negotiateEncoding(request.headers.get("accept-encoding"));
|
|
37
62
|
if (!encoding)
|
|
38
63
|
return response;
|
|
64
|
+
let body;
|
|
65
|
+
try {
|
|
66
|
+
body = new Uint8Array(await response.arrayBuffer());
|
|
67
|
+
} catch {
|
|
68
|
+
return response;
|
|
69
|
+
}
|
|
70
|
+
const passthrough = () => new Response(body, {
|
|
71
|
+
status: response.status,
|
|
72
|
+
statusText: response.statusText,
|
|
73
|
+
headers: varyByAcceptEncoding(response.headers)
|
|
74
|
+
});
|
|
75
|
+
if (body.length < (options.threshold ?? MIN_BYTES))
|
|
76
|
+
return passthrough();
|
|
39
77
|
try {
|
|
40
|
-
const
|
|
41
|
-
if (body.length < (options.threshold ?? MIN_BYTES))
|
|
42
|
-
return response;
|
|
43
|
-
const compressed = encoding === "br" ? zlib.brotliCompressSync(body, {
|
|
78
|
+
const compressed = encoding === "br" ? await brotliCompress(body, {
|
|
44
79
|
params: {
|
|
45
80
|
[zlib.constants.BROTLI_PARAM_QUALITY]: options.quality ?? 5,
|
|
46
81
|
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: body.length
|
|
47
82
|
}
|
|
48
|
-
}) :
|
|
83
|
+
}) : await gzipCompress(body);
|
|
49
84
|
if (compressed.length >= body.length)
|
|
50
|
-
return
|
|
51
|
-
const headers =
|
|
85
|
+
return passthrough();
|
|
86
|
+
const headers = varyByAcceptEncoding(response.headers);
|
|
52
87
|
headers.set("Content-Encoding", encoding);
|
|
53
88
|
headers.set("Content-Length", String(compressed.length));
|
|
54
|
-
headers.append("Vary", "Accept-Encoding");
|
|
55
89
|
return new Response(compressed, {
|
|
56
90
|
status: response.status,
|
|
57
91
|
statusText: response.statusText,
|
|
58
92
|
headers
|
|
59
93
|
});
|
|
60
94
|
} catch {
|
|
61
|
-
return
|
|
95
|
+
return passthrough();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function varyByAcceptEncoding(source) {
|
|
99
|
+
const headers = new Headers(source), existing = headers.get("Vary");
|
|
100
|
+
if (!existing) {
|
|
101
|
+
headers.set("Vary", "Accept-Encoding");
|
|
102
|
+
return headers;
|
|
62
103
|
}
|
|
104
|
+
if (!existing.split(",").some((field) => field.trim().toLowerCase() === "accept-encoding"))
|
|
105
|
+
headers.set("Vary", `${existing}, Accept-Encoding`);
|
|
106
|
+
return headers;
|
|
63
107
|
}
|