@stacksjs/stx 0.2.152 → 0.2.154
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/animation.js +6 -6
- package/dist/app-shell.js +2 -1
- package/dist/browser-composables.d.ts +8 -0
- package/dist/build-id.d.ts +49 -0
- package/dist/build-id.js +20 -0
- package/dist/build-message.d.ts +10 -0
- package/dist/build-message.js +26 -0
- package/dist/builtins/attrs.js +6 -1
- package/dist/cli.js +1743 -1255
- package/dist/client-script-bundler.js +2 -4
- package/dist/client-script.d.ts +29 -0
- package/dist/client-script.js +142 -97
- package/dist/color-mode-boot.d.ts +46 -10
- package/dist/color-mode-boot.js +53 -13
- package/dist/component-client-factories.js +3 -2
- package/dist/component-renderer.js +10 -21
- package/dist/composables/index.d.ts +8 -1
- package/dist/composables/use-clipboard.d.ts +1 -5
- package/dist/composables/use-clipboard.js +57 -46
- package/dist/composables/use-color-mode.d.ts +19 -4
- package/dist/composables/use-color-mode.js +49 -28
- package/dist/composables/use-router.d.ts +28 -11
- package/dist/composables/use-router.js +63 -9
- package/dist/composables/use-storage.d.ts +14 -15
- package/dist/composables/use-storage.js +63 -2
- package/dist/config.js +22 -7
- package/dist/craft.js +1319 -841
- package/dist/dev-server/crosswind.js +8 -6
- package/dist/dev-server/serve-app.js +13 -7
- package/dist/dev-server/serve-markdown.js +4 -1
- package/dist/dev-server/serve-multi.js +32 -20
- package/dist/dev-server/types.d.ts +8 -1
- package/dist/document-shell.d.ts +120 -0
- package/dist/document-shell.js +161 -14
- package/dist/dynamic-components.js +123 -4
- package/dist/events.d.ts +26 -49
- package/dist/events.js +23 -9
- package/dist/expressions.d.ts +1 -1
- package/dist/expressions.js +3 -3
- package/dist/framework-composables.d.ts +41 -0
- package/dist/framework-composables.js +115 -0
- package/dist/head.js +5 -1
- package/dist/hot-reload.js +8 -2
- package/dist/includes.js +4 -4
- package/dist/index.d.ts +17 -0
- package/dist/index.js +9 -0
- package/dist/isolated-build.d.ts +12 -0
- package/dist/isolated-build.js +18 -0
- package/dist/plugin-system.d.ts +1 -1
- package/dist/process.js +46 -12
- package/dist/production-builder.js +3 -1
- package/dist/production-server.js +4 -1
- package/dist/prop-sentinels.d.ts +31 -0
- package/dist/prop-sentinels.js +7 -0
- package/dist/pwa.js +1258 -780
- package/dist/reactive.js +1 -1
- package/dist/reactivity.d.ts +0 -15
- package/dist/reactivity.js +15 -6
- package/dist/runtime-globals.d.ts +40 -2
- package/dist/runtime-globals.js +90 -0
- package/dist/runtime-injection.d.ts +51 -0
- package/dist/runtime-injection.js +13 -0
- package/dist/script-emit.d.ts +20 -0
- package/dist/script-emit.js +11 -0
- package/dist/script-validation.js +12 -7
- package/dist/signal-processing.js +25 -40
- package/dist/signals-api.d.ts +30 -0
- package/dist/signals-api.js +18 -0
- package/dist/signals.js +532 -85
- package/dist/site-builder/build.js +21 -7
- package/dist/site-builder/seo.js +19 -10
- package/dist/site-builder/theme.js +5 -4
- package/dist/site-builder/types.d.ts +1 -0
- package/dist/ssg.js +1255 -777
- package/dist/state-management.js +4 -1
- package/dist/store-loader.d.ts +0 -11
- package/dist/store-loader.js +27 -3
- package/dist/strict-lint.d.ts +9 -6
- package/dist/strict-lint.js +2 -21
- package/dist/stx.d.ts +125 -22
- package/dist/types/config-types.d.ts +1 -0
- package/dist/unresolved-identifiers.d.ts +50 -0
- package/dist/unresolved-identifiers.js +146 -0
- package/dist/utils.js +3 -3
- package/dist/variable-extractor.js +17 -1
- package/dist/x-element.js +1 -1
- package/package.json +5 -5
- package/stx.d.ts +125 -22
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { getPublicEnvDefine } from "./public-env.js";
|
|
4
4
|
import { stateDir } from "./state-dir.js";
|
|
5
|
+
import { formatBuildFailure } from "./build-message.js";
|
|
5
6
|
const BUNDLE_CACHE_VERSION = 5, BUNDLE_CACHE_METADATA_VERSION = 1;
|
|
6
7
|
export function shouldLogBundlerDiagnostics(environment = process.env) {
|
|
7
8
|
return environment.STX_DEBUG === "true" || environment.STX_DEBUG === "1";
|
|
@@ -267,10 +268,7 @@ ${publicAssignments}`.trim();
|
|
|
267
268
|
}));
|
|
268
269
|
return bundled;
|
|
269
270
|
} catch (error) {
|
|
270
|
-
|
|
271
|
-
${bunErrors.map((e) => `- ${e?.message ?? e}`).join(`
|
|
272
|
-
`)}` : "";
|
|
273
|
-
console.warn(`[stx:bundler] error: ${msg}${detail}`);
|
|
271
|
+
console.warn(`[stx:bundler] error: ${formatBuildFailure(error, filePath || void 0)}`);
|
|
274
272
|
return code;
|
|
275
273
|
} finally {
|
|
276
274
|
try {
|
package/dist/client-script.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ export declare function injectBrowserCoreAutoImports(code: string): BrowserCoreA
|
|
|
46
46
|
/**
|
|
47
47
|
* Blank out comments and string literals, preserving length and line structure
|
|
48
48
|
* so offsets still line up, so that identifier detection only ever sees code.
|
|
49
|
+
*
|
|
50
|
+
* Template-literal INTERPOLATIONS are kept, because `${...}` is code, not text.
|
|
51
|
+
* Blanking them hid every identifier that appears only inside one — a helper
|
|
52
|
+
* called as `${formatDate(d)}` looked unused to the auto-import scanner, and a
|
|
53
|
+
* server value read as `` `/api/users/${userId}` `` looked unreferenced to the
|
|
54
|
+
* data bridge, which would have withheld it and left the client throwing on an
|
|
55
|
+
* undefined name.
|
|
49
56
|
*/
|
|
50
57
|
export declare function stripCommentsAndLiterals(code: string): string;
|
|
51
58
|
/**
|
|
@@ -91,6 +98,28 @@ export declare function generateServerDataBridge(code: string, serverData?: Reco
|
|
|
91
98
|
* @returns A complete `<script>...</script>` tag ready for browser injection
|
|
92
99
|
*/
|
|
93
100
|
export declare function processClientScript(scriptContent: string, options?: ClientScriptOptions): Promise<string>;
|
|
101
|
+
/**
|
|
102
|
+
* Exports from 'stx' auto-imported into classic `<script>` / `<script client>`
|
|
103
|
+
* blocks.
|
|
104
|
+
*
|
|
105
|
+
* Derived from {@link STX_RUNTIME_GLOBALS} rather than hand-maintained (#1804).
|
|
106
|
+
* This was a separate 82-name literal, and it had drifted: 16 of those names
|
|
107
|
+
* were not on `window.stx` at all. Because the generated prologue destructures
|
|
108
|
+
* (`var { … } = window.stx`), a missing name is not an error — it binds
|
|
109
|
+
* `undefined`, and the author sees "undefined is not a function" at the call
|
|
110
|
+
* site with nothing naming the identifier or saying why. `provide` worked and
|
|
111
|
+
* `inject` did not; `onMounted` worked and `onUpdated` did not.
|
|
112
|
+
*
|
|
113
|
+
* Five of the 16 (`isDerived`, `inject`, `useSlots`, `watchMultiple`,
|
|
114
|
+
* `onBeforeMount`) are now implemented in the runtime and live in the shared
|
|
115
|
+
* list. The remaining 11 are server- or compile-time only — `h` and `Fragment`
|
|
116
|
+
* belong to the JSX runtime, `useMeta`/`getCurrentInstance`/`useAttrs`/
|
|
117
|
+
* `onErrorCaptured`/`onBeforeUpdate`/`onUpdated`/`createStore`/`createSelector`
|
|
118
|
+
* are module exports with no client counterpart, and `action` has no
|
|
119
|
+
* implementation anywhere — so they are no longer offered. A `ReferenceError`
|
|
120
|
+
* naming the identifier beats a silent `undefined`.
|
|
121
|
+
*/
|
|
122
|
+
export declare const STX_AUTO_IMPORTS: readonly string[];
|
|
94
123
|
export declare interface VendorCssImport {
|
|
95
124
|
source: string
|
|
96
125
|
resolvedPath: string
|
package/dist/client-script.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { asInvocableStatement } from "./events.js";
|
|
3
4
|
import { transformStoreImports } from "./store-imports.js";
|
|
4
5
|
import { shouldTranspileTypeScript, transpileTypeScript } from "./utils.js";
|
|
5
6
|
import { importOnce } from "./lazy-module.js";
|
|
7
|
+
import { STX_RUNTIME_GLOBALS, usesReactiveRuntime } from "./runtime-globals.js";
|
|
8
|
+
import { escapeScriptBody } from "./script-emit.js";
|
|
6
9
|
const CSS_SIDE_EFFECT_IMPORT_REGEX = /^[ \t]*import\s+['"]([^'"]+?\.css)(\?[^'"]*)?['"]\s*;?\s*$/gm;
|
|
7
10
|
function splitBareSpecifier(spec) {
|
|
8
11
|
if (spec.startsWith(".") || spec.startsWith("/"))
|
|
@@ -141,90 +144,8 @@ ${s.contents}
|
|
|
141
144
|
`)}
|
|
142
145
|
`;
|
|
143
146
|
}
|
|
144
|
-
const STX_AUTO_IMPORTS =
|
|
145
|
-
|
|
146
|
-
"derived",
|
|
147
|
-
"effect",
|
|
148
|
-
"batch",
|
|
149
|
-
"untrack",
|
|
150
|
-
"peek",
|
|
151
|
-
"isSignal",
|
|
152
|
-
"isDerived",
|
|
153
|
-
"onMount",
|
|
154
|
-
"onDestroy",
|
|
155
|
-
"useRef",
|
|
156
|
-
"navigate",
|
|
157
|
-
"goBack",
|
|
158
|
-
"goForward",
|
|
159
|
-
"useRoute",
|
|
160
|
-
"setRouteParams",
|
|
161
|
-
"useSearchParams",
|
|
162
|
-
"useFetch",
|
|
163
|
-
"useQuery",
|
|
164
|
-
"useMutation",
|
|
165
|
-
"useOptimistic",
|
|
166
|
-
"useId",
|
|
167
|
-
"useReactiveProp",
|
|
168
|
-
"useLocalStorage",
|
|
169
|
-
"useSessionStorage",
|
|
170
|
-
"useCookie",
|
|
171
|
-
"useWebSocket",
|
|
172
|
-
"useHead",
|
|
173
|
-
"useSeoMeta",
|
|
174
|
-
"definePageMeta",
|
|
175
|
-
"useEventListener",
|
|
176
|
-
"useMeta",
|
|
177
|
-
"useDebounce",
|
|
178
|
-
"useDebouncedValue",
|
|
179
|
-
"useThrottle",
|
|
180
|
-
"useInterval",
|
|
181
|
-
"useTimeout",
|
|
182
|
-
"useToggle",
|
|
183
|
-
"useCounter",
|
|
184
|
-
"useClickOutside",
|
|
185
|
-
"useFocus",
|
|
186
|
-
"useAsync",
|
|
187
|
-
"useColorMode",
|
|
188
|
-
"useDark",
|
|
189
|
-
"useMediaQuery",
|
|
190
|
-
"useScrollLock",
|
|
191
|
-
"usePreferredDark",
|
|
192
|
-
"usePreferredLight",
|
|
193
|
-
"usePreferredReducedMotion",
|
|
194
|
-
"usePreferredContrast",
|
|
195
|
-
"ref",
|
|
196
|
-
"reactive",
|
|
197
|
-
"computed",
|
|
198
|
-
"watch",
|
|
199
|
-
"watchEffect",
|
|
200
|
-
"watchMultiple",
|
|
201
|
-
"onBeforeMount",
|
|
202
|
-
"onMounted",
|
|
203
|
-
"onBeforeUpdate",
|
|
204
|
-
"onUpdated",
|
|
205
|
-
"onBeforeUnmount",
|
|
206
|
-
"onUnmounted",
|
|
207
|
-
"defineProps",
|
|
208
|
-
"withDefaults",
|
|
209
|
-
"defineEmits",
|
|
210
|
-
"defineExpose",
|
|
211
|
-
"defineSlots",
|
|
212
|
-
"provide",
|
|
213
|
-
"inject",
|
|
214
|
-
"nextTick",
|
|
215
|
-
"getCurrentInstance",
|
|
216
|
-
"onErrorCaptured",
|
|
217
|
-
"useSlots",
|
|
218
|
-
"useAttrs",
|
|
219
|
-
"createStore",
|
|
220
|
-
"defineStore",
|
|
221
|
-
"registerStoresClient",
|
|
222
|
-
"useStore",
|
|
223
|
-
"action",
|
|
224
|
-
"createSelector",
|
|
225
|
-
"h",
|
|
226
|
-
"Fragment"
|
|
227
|
-
], BROWSER_CORE_IMPORTS = [
|
|
147
|
+
export const STX_AUTO_IMPORTS = STX_RUNTIME_GLOBALS;
|
|
148
|
+
const BROWSER_CORE_IMPORTS = [
|
|
228
149
|
"browserQuery",
|
|
229
150
|
"BrowserQueryBuilder",
|
|
230
151
|
"BrowserQueryError",
|
|
@@ -342,6 +263,84 @@ const JS_BUILTINS = new Set([
|
|
|
342
263
|
"Modal",
|
|
343
264
|
"Intl"
|
|
344
265
|
]);
|
|
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
|
+
}
|
|
345
344
|
export function stripCommentsAndLiterals(code) {
|
|
346
345
|
let out = "", i = 0;
|
|
347
346
|
while (i < code.length) {
|
|
@@ -362,7 +361,7 @@ export function stripCommentsAndLiterals(code) {
|
|
|
362
361
|
continue;
|
|
363
362
|
}
|
|
364
363
|
const ch = code[i];
|
|
365
|
-
if (ch === '"' || ch === "'"
|
|
364
|
+
if (ch === '"' || ch === "'") {
|
|
366
365
|
const quote = ch;
|
|
367
366
|
out += " ";
|
|
368
367
|
i++;
|
|
@@ -374,6 +373,33 @@ export function stripCommentsAndLiterals(code) {
|
|
|
374
373
|
}
|
|
375
374
|
out += code[i] === `
|
|
376
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
|
+
` ? `
|
|
377
403
|
` : " ";
|
|
378
404
|
i++;
|
|
379
405
|
}
|
|
@@ -535,14 +561,32 @@ function declaresClientIdentifier(code, name) {
|
|
|
535
561
|
return !0;
|
|
536
562
|
return !1;
|
|
537
563
|
}
|
|
564
|
+
function referencesIdentifier(searchable, name) {
|
|
565
|
+
return new RegExp(`(?<![.#\\w$])${name}(?![\\w$])`).test(searchable);
|
|
566
|
+
}
|
|
567
|
+
const CREDENTIAL_NAME = /token|secret|passw|apikey|api_key|privatekey|private_key|credential|webhook|bearer|jwt|signature/i, warnedBridgeKeys = new Set;
|
|
568
|
+
function warnAboutBridgedValue(name, json) {
|
|
569
|
+
const credential = CREDENTIAL_NAME.test(name), oversized = json.length > 32768;
|
|
570
|
+
if (!credential && !oversized)
|
|
571
|
+
return;
|
|
572
|
+
if (warnedBridgeKeys.has(name))
|
|
573
|
+
return;
|
|
574
|
+
warnedBridgeKeys.add(name);
|
|
575
|
+
if (credential)
|
|
576
|
+
console.warn(`[stx] server\u2192client bridge is publishing "${name}" into the page body. If it holds a credential, rename it with a "__" prefix (never bridged) and pass only what the template needs. See stacksjs/stx#1831.`);
|
|
577
|
+
if (oversized)
|
|
578
|
+
console.warn(`[stx] server\u2192client bridge is publishing ${(json.length / 1024).toFixed(1)}KB under "${name}". Every byte ships in the HTML on each request; prefix the binding with "__" and project just the fields the client reads.`);
|
|
579
|
+
}
|
|
538
580
|
export function generateServerDataBridge(code, serverData) {
|
|
539
581
|
if (!serverData)
|
|
540
582
|
return "";
|
|
541
|
-
const lines = [];
|
|
583
|
+
const searchable = stripCommentsAndLiterals(code), lines = [];
|
|
542
584
|
for (const [name, value] of Object.entries(serverData)) {
|
|
543
585
|
if (!/^[A-Za-z_$][\w$]*$/.test(name))
|
|
544
586
|
continue;
|
|
545
|
-
if (
|
|
587
|
+
if (name.startsWith("__") || name.startsWith("$"))
|
|
588
|
+
continue;
|
|
589
|
+
if (!referencesIdentifier(searchable, name))
|
|
546
590
|
continue;
|
|
547
591
|
if (declaresClientIdentifier(code, name))
|
|
548
592
|
continue;
|
|
@@ -554,6 +598,7 @@ export function generateServerDataBridge(code, serverData) {
|
|
|
554
598
|
}
|
|
555
599
|
if (json === void 0)
|
|
556
600
|
continue;
|
|
601
|
+
warnAboutBridgedValue(name, json);
|
|
557
602
|
lines.push(` var ${name} = ${json.replace(/</g, "\\u003c")};`);
|
|
558
603
|
}
|
|
559
604
|
if (lines.length === 0)
|
|
@@ -589,7 +634,7 @@ function generateModifierChecks(modifiers) {
|
|
|
589
634
|
return checks.join("; ");
|
|
590
635
|
}
|
|
591
636
|
function generateSingleEventBinding(binding, index) {
|
|
592
|
-
const { elementId, event,
|
|
637
|
+
const { elementId, event, modifiers } = binding, handler = asInvocableStatement(binding.handler), checks = generateModifierChecks(modifiers), options = [];
|
|
593
638
|
if (modifiers.once)
|
|
594
639
|
options.push("once: true");
|
|
595
640
|
if (modifiers.capture)
|
|
@@ -910,19 +955,19 @@ export async function processClientScript(scriptContent, options = {}) {
|
|
|
910
955
|
if (/\btype\s*=\s*["']module["']/i.test(attrs)) {
|
|
911
956
|
const extraAttrs = attrs.replace(/\btype\s*=\s*["']module["']/i, "").trim(), attrStr = `type="module" data-stx-scoped${extraAttrs ? ` ${extraAttrs}` : ""}`;
|
|
912
957
|
return `${vendorStyleTags}<script ${attrStr}>
|
|
913
|
-
${autoImportCode}${code}
|
|
914
|
-
${eventCode}
|
|
958
|
+
${escapeScriptBody(`${autoImportCode}${code}
|
|
959
|
+
${eventCode}`)}
|
|
915
960
|
</script>`;
|
|
916
961
|
}
|
|
917
|
-
const usesSignals =
|
|
962
|
+
const usesSignals = usesReactiveRuntime(scriptContent), isSfcWrapped = /function __stx_setup_/.test(code), alreadyMounts = /\bstx\.mount\s*\(|\bstx\.mountEl\s*\(/.test(scriptContent);
|
|
918
963
|
function wrapInMount(declarations) {
|
|
919
964
|
const returnStmt = declarations.length > 0 ? `
|
|
920
965
|
return { ${declarations.join(", ")} };` : "";
|
|
921
|
-
return `${vendorStyleTags}<script data-stx-scoped>
|
|
966
|
+
return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
|
|
922
967
|
window.stx.mount(function() {
|
|
923
968
|
'use strict';
|
|
924
|
-
${autoImportCode}${code}
|
|
925
|
-
${eventCode}${returnStmt}
|
|
969
|
+
${escapeScriptBody(`${autoImportCode}${code}
|
|
970
|
+
${eventCode}${returnStmt}`)}
|
|
926
971
|
})</script>`;
|
|
927
972
|
}
|
|
928
973
|
if (!alreadyMounts && !isSfcWrapped) {
|
|
@@ -937,10 +982,10 @@ ${eventCode}${returnStmt}
|
|
|
937
982
|
}
|
|
938
983
|
}
|
|
939
984
|
}
|
|
940
|
-
return `${vendorStyleTags}<script data-stx-scoped>
|
|
985
|
+
return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
|
|
941
986
|
;(function() {
|
|
942
987
|
'use strict';
|
|
943
|
-
${autoImportCode}${code}
|
|
944
|
-
${eventCode}
|
|
988
|
+
${escapeScriptBody(`${autoImportCode}${code}
|
|
989
|
+
${eventCode}`)}
|
|
945
990
|
})()</script>`;
|
|
946
991
|
}
|
|
@@ -21,6 +21,37 @@ export declare function generateColorModeBootScript(options?: ColorModeBootConfi
|
|
|
21
21
|
* unchanged, so this is safe on paths that run it more than once.
|
|
22
22
|
*/
|
|
23
23
|
export declare function injectColorModeBootScript(html: string, options?: ColorModeBootConfig): string;
|
|
24
|
+
/**
|
|
25
|
+
* Re-assert the ordering of the two head tags whose position is load-bearing:
|
|
26
|
+
* `<meta charset>` first, then the color-mode boot script, then everything else.
|
|
27
|
+
*
|
|
28
|
+
* Run this LAST, after every other head injection. Ordering the injectors
|
|
29
|
+
* against each other would hold only until the next one is added; re-asserting
|
|
30
|
+
* the result once at the end holds regardless of what ran before.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeCriticalHeadOrder(html: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Move an already-injected boot script back to the top of `<head>`.
|
|
35
|
+
*
|
|
36
|
+
* `injectColorModeBootScript` puts it first, but later stages of the pipeline
|
|
37
|
+
* then insert *ahead* of it and push it down (#1803): the signals runtime is
|
|
38
|
+
* relocated before the first `<script>` on the page — which is this one — and
|
|
39
|
+
* the store and composable bundles anchor themselves to the runtime tag. The
|
|
40
|
+
* boot script ended up fifth, behind all three.
|
|
41
|
+
*
|
|
42
|
+
* That is not merely a paint-timing problem. The bundles that overtook it read
|
|
43
|
+
* `window.__STX_COLOR_MODE__`, which this script publishes, so a `useColorMode()`
|
|
44
|
+
* inside a store saw `undefined` and silently fell back to the default storage
|
|
45
|
+
* key and a null attribute — writing the user's preference somewhere the boot
|
|
46
|
+
* script will never read it back, and never applying the configured attribute.
|
|
47
|
+
*
|
|
48
|
+
* Run this last, after every other head injection. Ordering the injectors
|
|
49
|
+
* against each other would work only until the next one is added; re-asserting
|
|
50
|
+
* the position once at the end holds regardless of what ran before.
|
|
51
|
+
*
|
|
52
|
+
* A no-op when the document has no boot script or it is already in place.
|
|
53
|
+
*/
|
|
54
|
+
export declare function hoistColorModeBootScript(html: string): string;
|
|
24
55
|
/**
|
|
25
56
|
* Pre-paint color-mode bootstrap (stacksjs/stx#1794).
|
|
26
57
|
*
|
|
@@ -53,15 +84,17 @@ export declare function injectColorModeBootScript(html: string, options?: ColorM
|
|
|
53
84
|
* hydration. If they disagree the flash comes back — the boot script would set
|
|
54
85
|
* one thing and hydration would immediately undo it, which is worse than not
|
|
55
86
|
* having a boot script at all. So this mirrors the runtime's `applyDOM` and
|
|
56
|
-
*
|
|
87
|
+
* preference reading precisely:
|
|
57
88
|
*
|
|
58
|
-
* - `attribute` and `darkClass`
|
|
59
|
-
*
|
|
60
|
-
*
|
|
89
|
+
* - `attribute` and `darkClass` both apply when both are configured, and
|
|
90
|
+
* `darkClass: null` opts out of the class (stacksjs/stx#1788)
|
|
91
|
+
* - the accepted vocabulary is `light | dark | auto | system`, with
|
|
92
|
+
* `'system'` an alias for `'auto'` (also #1788)
|
|
61
93
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
94
|
+
* `test/directives/color-mode-boot.test.ts` pins both sides to the same
|
|
95
|
+
* expectations so drift fails the suite rather than shipping a flash. This
|
|
96
|
+
* script never writes to storage — reading is all it needs, and a write here
|
|
97
|
+
* would race the composable's own persistence.
|
|
65
98
|
*
|
|
66
99
|
* The script publishes its resolved configuration on `window.__STX_COLOR_MODE__`
|
|
67
100
|
* so a bare `useColorMode()` call picks up the app's configured storage key and
|
|
@@ -82,7 +115,10 @@ export declare const COLOR_MODE_BOOT_GLOBAL: '__STX_COLOR_MODE__';
|
|
|
82
115
|
export declare interface ColorModeBootConfig {
|
|
83
116
|
storageKey?: string
|
|
84
117
|
initialMode?: ColorModeBootMode
|
|
85
|
-
darkClass?: string
|
|
86
|
-
attribute?: string
|
|
118
|
+
darkClass?: string | null
|
|
119
|
+
attribute?: string | null
|
|
120
|
+
autoValue?: ColorModeBootAutoValue
|
|
87
121
|
}
|
|
88
|
-
export type ColorModeBootMode = 'light' | 'dark' | 'auto';
|
|
122
|
+
export type ColorModeBootMode = 'light' | 'dark' | 'auto' | 'system';
|
|
123
|
+
/** Spelling written to storage for "follow the system". */
|
|
124
|
+
export type ColorModeBootAutoValue = 'auto' | 'system';
|
package/dist/color-mode-boot.js
CHANGED
|
@@ -1,35 +1,75 @@
|
|
|
1
1
|
export const COLOR_MODE_BOOT_MARKER = "data-stx-color-mode-boot", COLOR_MODE_BOOT_GLOBAL = "__STX_COLOR_MODE__";
|
|
2
|
-
const MODES = new Set(["light", "dark", "auto"]), ATTRIBUTE_PATTERN = /^[a-z][\w:-]*$/i, CLASS_PATTERN = /^[\w-]+$/;
|
|
2
|
+
const MODES = new Set(["light", "dark", "auto", "system"]), AUTO_VALUES = new Set(["auto", "system"]), ATTRIBUTE_PATTERN = /^[a-z][\w:-]*$/i, CLASS_PATTERN = /^[\w-]+$/;
|
|
3
3
|
function resolveConfig(options) {
|
|
4
|
-
const storageKey = options.storageKey ?? "stx-color-mode",
|
|
4
|
+
const storageKey = options.storageKey ?? "stx-color-mode", rawInitial = options.initialMode ?? "auto", darkClass = options.darkClass === void 0 ? "dark" : options.darkClass, attribute = options.attribute ?? null, autoValue = options.autoValue ?? null;
|
|
5
5
|
if (typeof storageKey !== "string" || storageKey.length === 0)
|
|
6
6
|
throw Error("colorMode.storageKey must be a non-empty string");
|
|
7
|
-
if (!MODES.has(
|
|
8
|
-
throw Error(`colorMode.initialMode must be one of light, dark, auto \u2014 got ${JSON.stringify(
|
|
9
|
-
if (typeof darkClass !== "string" || !CLASS_PATTERN.test(darkClass))
|
|
10
|
-
throw Error(`colorMode.darkClass must be a single CSS class name \u2014 got ${JSON.stringify(darkClass)}`);
|
|
7
|
+
if (!MODES.has(rawInitial))
|
|
8
|
+
throw Error(`colorMode.initialMode must be one of light, dark, auto, system \u2014 got ${JSON.stringify(rawInitial)}`);
|
|
9
|
+
if (darkClass !== null && (typeof darkClass !== "string" || !CLASS_PATTERN.test(darkClass)))
|
|
10
|
+
throw Error(`colorMode.darkClass must be a single CSS class name, or null to opt out \u2014 got ${JSON.stringify(darkClass)}`);
|
|
11
11
|
if (attribute !== null && (typeof attribute !== "string" || !ATTRIBUTE_PATTERN.test(attribute)))
|
|
12
12
|
throw Error(`colorMode.attribute must be a valid HTML attribute name \u2014 got ${JSON.stringify(attribute)}`);
|
|
13
|
-
|
|
13
|
+
if (autoValue !== null && !AUTO_VALUES.has(autoValue))
|
|
14
|
+
throw Error(`colorMode.autoValue must be 'auto' or 'system' \u2014 got ${JSON.stringify(autoValue)}`);
|
|
15
|
+
if (darkClass === null && attribute === null)
|
|
16
|
+
throw Error("colorMode needs at least one of darkClass or attribute \u2014 with both null nothing would mark the mode on <html>");
|
|
17
|
+
return { storageKey, initialMode: rawInitial === "system" ? "auto" : rawInitial, darkClass, attribute, autoValue };
|
|
14
18
|
}
|
|
15
19
|
function serializeForScript(value) {
|
|
16
20
|
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
17
21
|
}
|
|
18
22
|
export function generateColorModeBootScript(options = {}) {
|
|
19
|
-
const config = resolveConfig(options), body = `(function(){"use strict";var c=${serializeForScript(config)};var p=c.initialMode;try{var v=localStorage.getItem(c.storageKey);if(v==="light"||v==="dark"
|
|
23
|
+
const config = resolveConfig(options), body = `(function(){"use strict";var c=${serializeForScript(config)};var p=c.initialMode;var a=c.autoValue;try{var v=localStorage.getItem(c.storageKey);if(v==="light"||v==="dark"){p=v}else if(v==="auto"||v==="system"){p="auto";if(!a)a=v}}catch(e){}var m=p;if(p==="auto"){try{m=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch(e){m="light"}}var r=document.documentElement;if(c.attribute)r.setAttribute(c.attribute,m);if(c.darkClass){if(m==="dark")r.classList.add(c.darkClass);else r.classList.remove(c.darkClass)}try{window.${COLOR_MODE_BOOT_GLOBAL}={storageKey:c.storageKey,initialMode:c.initialMode,darkClass:c.darkClass,attribute:c.attribute,autoValue:a||"auto",preference:p,mode:m}}catch(e){}}());`;
|
|
20
24
|
return `<script ${COLOR_MODE_BOOT_MARKER}>${body}</script>`;
|
|
21
25
|
}
|
|
22
26
|
export function injectColorModeBootScript(html, options = {}) {
|
|
23
27
|
if (html.includes(COLOR_MODE_BOOT_MARKER))
|
|
24
28
|
return html;
|
|
29
|
+
const insertAt = bootInsertionPoint(html);
|
|
30
|
+
if (insertAt === null)
|
|
31
|
+
return html;
|
|
32
|
+
const script = generateColorModeBootScript(options);
|
|
33
|
+
return `${html.slice(0, insertAt)}
|
|
34
|
+
${script}${html.slice(insertAt)}`;
|
|
35
|
+
}
|
|
36
|
+
function bootInsertionPoint(html) {
|
|
25
37
|
const headOpen = /<head\b[^>]*>/i.exec(html);
|
|
26
38
|
if (!headOpen)
|
|
27
|
-
return
|
|
39
|
+
return null;
|
|
28
40
|
let insertAt = headOpen.index + headOpen[0].length;
|
|
29
|
-
const
|
|
41
|
+
const charset = /^\s*<meta\b[^>]*\bcharset\b[^>]*>/i.exec(html.slice(insertAt));
|
|
30
42
|
if (charset)
|
|
31
43
|
insertAt += charset[0].length;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
return insertAt;
|
|
45
|
+
}
|
|
46
|
+
const BOOT_SCRIPT_TAG = new RegExp(`<script\\b[^>]*\\b${COLOR_MODE_BOOT_MARKER}\\b[^>]*>[\\s\\S]*?<\\/script>`, "i"), CHARSET_META = /<meta\b[^>]*\bcharset\b[^>]*>/i;
|
|
47
|
+
function hoistCharsetMeta(html) {
|
|
48
|
+
const headOpen = /<head\b[^>]*>/i.exec(html);
|
|
49
|
+
if (!headOpen)
|
|
50
|
+
return html;
|
|
51
|
+
const headStart = headOpen.index + headOpen[0].length, headEnd = html.toLowerCase().indexOf("</head>", headStart), head = html.slice(headStart, headEnd === -1 ? html.length : headEnd), meta = CHARSET_META.exec(head);
|
|
52
|
+
if (!meta || head.slice(0, meta.index).trim() === "")
|
|
53
|
+
return html;
|
|
54
|
+
const withoutMeta = head.slice(0, meta.index) + head.slice(meta.index + meta[0].length);
|
|
55
|
+
return html.slice(0, headStart) + `
|
|
56
|
+
${meta[0]}` + withoutMeta + html.slice(headStart + head.length);
|
|
57
|
+
}
|
|
58
|
+
export function normalizeCriticalHeadOrder(html) {
|
|
59
|
+
return hoistColorModeBootScript(hoistCharsetMeta(html));
|
|
60
|
+
}
|
|
61
|
+
export function hoistColorModeBootScript(html) {
|
|
62
|
+
const tag = BOOT_SCRIPT_TAG.exec(html);
|
|
63
|
+
if (!tag)
|
|
64
|
+
return html;
|
|
65
|
+
const target = bootInsertionPoint(html);
|
|
66
|
+
if (target === null)
|
|
67
|
+
return html;
|
|
68
|
+
if (tag.index <= target || html.slice(target, tag.index).trim() === "")
|
|
69
|
+
return html;
|
|
70
|
+
const withoutTag = html.slice(0, tag.index) + html.slice(tag.index + tag[0].length), insertAt = bootInsertionPoint(withoutTag);
|
|
71
|
+
if (insertAt === null)
|
|
72
|
+
return html;
|
|
73
|
+
return `${withoutTag.slice(0, insertAt)}
|
|
74
|
+
${tag[0]}${withoutTag.slice(insertAt)}`;
|
|
35
75
|
}
|
|
@@ -32,10 +32,11 @@ export function injectComponentClientFactories(html, context) {
|
|
|
32
32
|
const repeatedFactories = [...registry].filter(([, factory]) => factory.instances > 1);
|
|
33
33
|
if (repeatedFactories.length === 0)
|
|
34
34
|
return output;
|
|
35
|
-
const prelude = `<script data-stx-scoped data-stx-component-factories>
|
|
35
|
+
const prelude = `<script data-stx-scoped data-stx-run="always" data-stx-component-factories>
|
|
36
36
|
(function() {
|
|
37
37
|
const factories = window.__stxComponentFactories = window.__stxComponentFactories || {};
|
|
38
|
-
${repeatedFactories.map(([id, factory]) => ` factories[${JSON.stringify(id)}] = factories[${JSON.stringify(id)}] || ${factory.body}
|
|
38
|
+
${repeatedFactories.map(([id, factory]) => ` try { factories[${JSON.stringify(id)}] = factories[${JSON.stringify(id)}] || ${factory.body}; }
|
|
39
|
+
catch (e) { console.error('[stx] component factory ' + ${JSON.stringify(id)} + ' failed to register; instances of it will not hydrate.', e); }`).join(`
|
|
39
40
|
`)}
|
|
40
41
|
})();
|
|
41
42
|
</script>`, firstScopedScript = output.search(/<script\b(?=[^>]*\bdata-stx-scoped\b)/i);
|
|
@@ -4,10 +4,12 @@ import { decodeAttributeEntities, decodeStxProp, findComponentTags, parseMultili
|
|
|
4
4
|
import { maskAtElementPosition, matchHtmlComment } from "./html-masking.js";
|
|
5
5
|
import { renderComponentWithSlot, userComponentFileExists } from "./utils.js";
|
|
6
6
|
import { createSafeFunction, isExpressionSafe, safeEvaluateObject } from "./safe-evaluator.js";
|
|
7
|
+
import { BOOLEAN_ATTRIBUTE_SENTINEL } from "./prop-sentinels.js";
|
|
8
|
+
import { COMPONENT_PASSTHROUGH_X_ATTRS } from "./runtime-globals.js";
|
|
7
9
|
import fs from "node:fs";
|
|
8
10
|
import path from "node:path";
|
|
9
11
|
let builtinsRegistered = !1;
|
|
10
|
-
const
|
|
12
|
+
const htmlTags = new Set([
|
|
11
13
|
"a",
|
|
12
14
|
"abbr",
|
|
13
15
|
"address",
|
|
@@ -29,6 +31,7 @@ const BOOLEAN_ATTRIBUTE_SENTINEL = "\x00stx-boolean-attribute", htmlTags = new S
|
|
|
29
31
|
"code",
|
|
30
32
|
"col",
|
|
31
33
|
"colgroup",
|
|
34
|
+
"component",
|
|
32
35
|
"data",
|
|
33
36
|
"datalist",
|
|
34
37
|
"dd",
|
|
@@ -180,24 +183,7 @@ function parseComponentProps(rawProps, context, options) {
|
|
|
180
183
|
serverDynamic: {},
|
|
181
184
|
clientReactive: {},
|
|
182
185
|
events: {}
|
|
183
|
-
}, ALPINE_PASSTHROUGH = new Set(
|
|
184
|
-
"x-cloak",
|
|
185
|
-
"x-show",
|
|
186
|
-
"x-if",
|
|
187
|
-
"x-for",
|
|
188
|
-
"x-data",
|
|
189
|
-
"x-init",
|
|
190
|
-
"x-transition",
|
|
191
|
-
"x-ref",
|
|
192
|
-
"x-effect",
|
|
193
|
-
"x-model",
|
|
194
|
-
"x-text",
|
|
195
|
-
"x-html",
|
|
196
|
-
"x-class",
|
|
197
|
-
"x-style",
|
|
198
|
-
"x-on",
|
|
199
|
-
"x-bind"
|
|
200
|
-
]), kebabToCamel = (key) => {
|
|
186
|
+
}, ALPINE_PASSTHROUGH = new Set(COMPONENT_PASSTHROUGH_X_ATTRS), kebabToCamel = (key) => {
|
|
201
187
|
if (!key.includes("-") || key.startsWith("@") || key.startsWith(":") || key.startsWith("x-") || key.startsWith("__"))
|
|
202
188
|
return key;
|
|
203
189
|
if (key.startsWith("aria-") || key.startsWith("data-"))
|
|
@@ -343,6 +329,9 @@ function parseAllAttributes(attributesStr) {
|
|
|
343
329
|
}
|
|
344
330
|
return props;
|
|
345
331
|
}
|
|
332
|
+
function serialiseForwardedEvents(events) {
|
|
333
|
+
return Object.entries(events).map(([event, handler]) => handler === BOOLEAN_ATTRIBUTE_SENTINEL ? event : `${event}="${escapeAttribute(handler)}"`).join(" ");
|
|
334
|
+
}
|
|
346
335
|
function escapeAttribute(value) {
|
|
347
336
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
348
337
|
}
|
|
@@ -617,7 +606,7 @@ async function processCustomElementTags(template, context, filePath, options, de
|
|
|
617
606
|
if (Object.keys(resolvedProps.clientReactive).length > 0)
|
|
618
607
|
rendered = emitClientReactiveAttrs(rendered, resolvedProps.clientReactive);
|
|
619
608
|
if (Object.keys(resolvedProps.events).length > 0) {
|
|
620
|
-
const eventAttrs =
|
|
609
|
+
const eventAttrs = serialiseForwardedEvents(resolvedProps.events);
|
|
621
610
|
rendered = rendered.replace(/^(\s*<[a-zA-Z][a-zA-Z0-9-]*)/, `$1 ${eventAttrs}`);
|
|
622
611
|
}
|
|
623
612
|
result = result.substring(0, tag.startIndex) + rendered + result.substring(tag.endIndex);
|
|
@@ -636,7 +625,7 @@ async function processCustomElementTags(template, context, filePath, options, de
|
|
|
636
625
|
if (Object.keys(resolvedProps.events).length > 0) {
|
|
637
626
|
const root = findRenderedComponentRoot(finalContent);
|
|
638
627
|
if (root) {
|
|
639
|
-
const eventAttrs =
|
|
628
|
+
const eventAttrs = serialiseForwardedEvents(resolvedProps.events), parentEvents = Object.keys(resolvedProps.events).map((event) => event.replace(/^@/, "").split(".")[0]).join(" ");
|
|
640
629
|
finalContent = finalContent.substring(0, root.insertPos) + ` data-stx-parent-events="${parentEvents}" ${eventAttrs}` + finalContent.substring(root.insertPos);
|
|
641
630
|
}
|
|
642
631
|
}
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
* } from '@stacksjs/stx/composables'
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
// Storage
|
|
19
|
+
// Storage. `useLocalStorage` / `useSessionStorage` return a Signal<T> matching
|
|
20
|
+
// the runtime contract and the ambient stx.d.ts; the old StorageRef shape was
|
|
21
|
+
// removed in #1797 as part of the dual-impl unification. Migrate `.value` reads
|
|
22
|
+
// to `s()` and `.value = x` writes to `s.set(x)` (`.set()` is unchanged); for
|
|
23
|
+
// `.remove()` / `.subscribe()` / custom serializers use `useStorage`, which
|
|
24
|
+
// still returns a StorageRef.
|
|
20
25
|
export {
|
|
21
26
|
useStorage,
|
|
22
27
|
useLocalStorage,
|
|
@@ -340,7 +345,9 @@ export {
|
|
|
340
345
|
goForward,
|
|
341
346
|
useRoute,
|
|
342
347
|
useSearchParams,
|
|
348
|
+
type NavigateOptions,
|
|
343
349
|
type RouteInfo,
|
|
350
|
+
type SearchParamsCommitOptions,
|
|
344
351
|
type SearchParamsRef,
|
|
345
352
|
} from './use-router';
|
|
346
353
|
// Data Fetching (Query / Mutation)
|