@stacksjs/stx 0.2.146 → 0.2.148
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 +617 -560
- package/dist/client-script.d.ts +5 -0
- package/dist/client-script.js +49 -5
- package/dist/color-mode-boot.d.ts +88 -0
- package/dist/color-mode-boot.js +35 -0
- package/dist/composables/use-color-mode.js +5 -5
- package/dist/composables/use-storage.js +6 -1
- package/dist/craft.js +330 -273
- package/dist/dev-server/crosswind.js +1 -1
- package/dist/document-shell.d.ts +8 -0
- package/dist/document-shell.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/process.js +7 -1
- package/dist/production-builder.js +6 -2
- package/dist/pwa.js +322 -265
- package/dist/script-classifier.d.ts +14 -8
- package/dist/script-classifier.js +33 -14
- package/dist/signals.js +66 -15
- package/dist/ssg.js +299 -242
- package/dist/stx.d.ts +32 -4
- package/dist/types/config-types.d.ts +2 -0
- package/package.json +6 -6
- package/stx.d.ts +32 -4
|
@@ -11,6 +11,20 @@ export declare function classifyScript(attrs: string, content: string, fullTag:
|
|
|
11
11
|
/**
|
|
12
12
|
* Classify all `<script>` tags in a template.
|
|
13
13
|
*
|
|
14
|
+
* The scan walks the template rather than running a global regex so that HTML
|
|
15
|
+
* comments are inert. A comment whose prose merely mentions a script tag — e.g.
|
|
16
|
+
* `<!-- the <script server> block owner-scopes the switcher -->` — used to be
|
|
17
|
+
* matched as a REAL opening tag, and the non-greedy regex then ran on to the
|
|
18
|
+
* next `</script>`. The resulting bogus `fullTag` spanned from inside the comment
|
|
19
|
+
* to the end of an unrelated script, so callers that delete `fullTag`
|
|
20
|
+
* (app-shell's `cleanTemplate`, serve-app's `templateContent`) erased the
|
|
21
|
+
* comment's `-->` terminator AND every real script in between — producing a
|
|
22
|
+
* runaway comment node that swallowed the rest of the document.
|
|
23
|
+
*
|
|
24
|
+
* `<template>` contents are deliberately still scanned: stx SFC files wrap their
|
|
25
|
+
* whole markup in `<template>`, and callers rely on scripts authored inside it
|
|
26
|
+
* being classified so they can be stripped from the rendered output.
|
|
27
|
+
*
|
|
14
28
|
* Returns arrays of scripts grouped by type.
|
|
15
29
|
*/
|
|
16
30
|
export declare function classifyAllScripts(content: string): {
|
|
@@ -24,12 +38,4 @@ export declare interface ScriptClassification {
|
|
|
24
38
|
content: string
|
|
25
39
|
attrs: string
|
|
26
40
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Script Classification
|
|
29
|
-
*
|
|
30
|
-
* Single source of truth for classifying `<script>` tags in `.stx` files.
|
|
31
|
-
* Used by bun-plugin, dev-server, and process.ts to ensure consistent behavior.
|
|
32
|
-
*
|
|
33
|
-
* @module script-classifier
|
|
34
|
-
*/
|
|
35
41
|
export type ScriptType = 'signals' | 'client' | 'server';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchHtmlComment, matchScriptElement } from "./html-masking.js";
|
|
1
2
|
const SIGNALS_PATTERN = /\b(?:state|derived|effect|batch|ref|reactive|computed|watch|watchEffect)\s*(?:<[^>]*>)?\s*\(/;
|
|
2
3
|
export function classifyScript(attrs, content, fullTag) {
|
|
3
4
|
const result = { fullTag, content, attrs };
|
|
@@ -11,22 +12,40 @@ export function classifyScript(attrs, content, fullTag) {
|
|
|
11
12
|
return { ...result, type: "client" };
|
|
12
13
|
return { ...result, type: "server" };
|
|
13
14
|
}
|
|
15
|
+
const SCRIPT_PARTS = /^<script\b([^>]*)>([\s\S]*)<\/script\s*>$/i;
|
|
14
16
|
export function classifyAllScripts(content) {
|
|
15
|
-
const signals = [], client = [], server = []
|
|
16
|
-
let
|
|
17
|
-
while (
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
break;
|
|
26
|
-
case "server":
|
|
27
|
-
server.push(classification);
|
|
28
|
-
break;
|
|
17
|
+
const signals = [], client = [], server = [];
|
|
18
|
+
let i = 0;
|
|
19
|
+
while (i < content.length) {
|
|
20
|
+
const lt = content.indexOf("<", i);
|
|
21
|
+
if (lt === -1)
|
|
22
|
+
break;
|
|
23
|
+
const commentEnd = matchHtmlComment(content, lt);
|
|
24
|
+
if (commentEnd > lt) {
|
|
25
|
+
i = commentEnd;
|
|
26
|
+
continue;
|
|
29
27
|
}
|
|
28
|
+
const scriptEnd = matchScriptElement(content, lt);
|
|
29
|
+
if (scriptEnd > lt) {
|
|
30
|
+
const fullTag = content.slice(lt, scriptEnd), parts = SCRIPT_PARTS.exec(fullTag);
|
|
31
|
+
if (parts) {
|
|
32
|
+
const classification = classifyScript(parts[1], parts[2], fullTag);
|
|
33
|
+
switch (classification.type) {
|
|
34
|
+
case "signals":
|
|
35
|
+
signals.push(classification);
|
|
36
|
+
break;
|
|
37
|
+
case "client":
|
|
38
|
+
client.push(classification);
|
|
39
|
+
break;
|
|
40
|
+
case "server":
|
|
41
|
+
server.push(classification);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
i = scriptEnd;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
i = lt + (content.startsWith("<!--", lt) ? 4 : 1);
|
|
30
49
|
}
|
|
31
50
|
return { signals, client, server };
|
|
32
51
|
}
|
package/dist/signals.js
CHANGED
|
@@ -4402,10 +4402,18 @@ else if (timer === null) {
|
|
|
4402
4402
|
|
|
4403
4403
|
function useColorMode(options) {
|
|
4404
4404
|
options = options || {};
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4405
|
+
// The pre-paint boot script (stacksjs/stx#1794) publishes the app's
|
|
4406
|
+
// configured options here. Reading them makes a bare useColorMode() agree
|
|
4407
|
+
// with whatever already landed on <html> before first paint \u2014 without this
|
|
4408
|
+
// the composable would fall back to its own defaults, read a different
|
|
4409
|
+
// storage key, and undo the theme on hydration, reintroducing the very
|
|
4410
|
+
// flash the boot script exists to prevent.
|
|
4411
|
+
var boot = null;
|
|
4412
|
+
try { boot = window.__STX_COLOR_MODE__ || null; } catch (e) {}
|
|
4413
|
+
var storageKey = options.storageKey || (boot && boot.storageKey) || 'stx-color-mode';
|
|
4414
|
+
var initialMode = options.initialMode || (boot && boot.initialMode) || 'auto';
|
|
4415
|
+
var darkClass = options.darkClass || (boot && boot.darkClass) || 'dark';
|
|
4416
|
+
var attribute = options.attribute || (boot && boot.attribute) || null;
|
|
4409
4417
|
var disableTransitions = options.disableTransitions !== false;
|
|
4410
4418
|
var preference = initialMode;
|
|
4411
4419
|
var resolved = 'light';
|
|
@@ -4571,15 +4579,60 @@ catch (e) {}
|
|
|
4571
4579
|
var watch = $watch;
|
|
4572
4580
|
var watchEffect = function(fn) { return effect(fn); };
|
|
4573
4581
|
|
|
4582
|
+
// Storage access is guarded end to end (stacksjs/stx#1793). Every one of
|
|
4583
|
+
// these can throw, and each throw took down the whole client script for the
|
|
4584
|
+
// page rather than just the composable:
|
|
4585
|
+
//
|
|
4586
|
+
// getItem - SecurityError in a sandboxed iframe or with storage disabled
|
|
4587
|
+
// parse - any non-JSON value already sitting at the key
|
|
4588
|
+
// setItem - QuotaExceededError, Safari private mode
|
|
4589
|
+
//
|
|
4590
|
+
// The parse fallback is deliberately LENIENT: a value that isn't JSON is
|
|
4591
|
+
// returned as the raw string rather than discarded for defaultValue. A key
|
|
4592
|
+
// that previously held a bare 'list' keeps reading back 'list' and is
|
|
4593
|
+
// rewritten as JSON on the next set, so migrating an existing key onto this
|
|
4594
|
+
// composable no longer needs the key to be renamed. This also matches
|
|
4595
|
+
// composables/use-storage.ts, whose default serializer already returns the
|
|
4596
|
+
// raw string on parse failure \u2014 the two implementations have to agree.
|
|
4597
|
+
function stxParseStored(raw, fallback, key, label) {
|
|
4598
|
+
if (raw === null || raw === undefined) return fallback;
|
|
4599
|
+
try { return JSON.parse(raw); }
|
|
4600
|
+
catch (e) {
|
|
4601
|
+
console.warn('[stx] ' + label + ': "' + key + '" holds a value that is not JSON (' + String(raw).slice(0, 64) + '); using it as a raw string. Write it with the same composable to store it as JSON.');
|
|
4602
|
+
return raw;
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
function stxStorageRead(store, key, fallback, label) {
|
|
4606
|
+
var raw;
|
|
4607
|
+
try { raw = store.getItem(key); }
|
|
4608
|
+
catch (e) {
|
|
4609
|
+
console.warn('[stx] ' + label + ': cannot read "' + key + '" (' + (e && e.message ? e.message : e) + '); using the default.');
|
|
4610
|
+
return fallback;
|
|
4611
|
+
}
|
|
4612
|
+
return stxParseStored(raw, fallback, key, label);
|
|
4613
|
+
}
|
|
4614
|
+
function stxStorageWrite(store, key, value, label) {
|
|
4615
|
+
try { store.setItem(key, JSON.stringify(value)); }
|
|
4616
|
+
catch (e) {
|
|
4617
|
+
console.warn('[stx] ' + label + ': cannot persist "' + key + '" (' + (e && e.message ? e.message : e) + '); the value stays in memory only.');
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4574
4621
|
function useLocalStorage(key, defaultValue) {
|
|
4575
|
-
var
|
|
4576
|
-
var initial = stored !== null ? JSON.parse(stored) : defaultValue;
|
|
4577
|
-
var s = state(initial);
|
|
4622
|
+
var s = state(stxStorageRead(localStorage, key, defaultValue, 'useLocalStorage'));
|
|
4578
4623
|
effect(function() {
|
|
4579
|
-
localStorage
|
|
4624
|
+
stxStorageWrite(localStorage, key, s(), 'useLocalStorage');
|
|
4580
4625
|
});
|
|
4581
4626
|
var handler = function(e) {
|
|
4582
|
-
|
|
4627
|
+
// Fires from ANOTHER tab at an arbitrary later time, so an unguarded
|
|
4628
|
+
// parse here throws inside a window listener with a stack pointing at
|
|
4629
|
+
// the runtime rather than at the consumer.
|
|
4630
|
+
// storageArea is also matched so a sessionStorage write to the same key
|
|
4631
|
+
// name can't clobber this signal \u2014 useSessionStorage already filtered,
|
|
4632
|
+
// this side didn't. Synthetic events (tests) leave storageArea null and
|
|
4633
|
+
// are still accepted; real browsers always populate it.
|
|
4634
|
+
if (e.key === key && (!e.storageArea || e.storageArea === localStorage))
|
|
4635
|
+
s.set(stxParseStored(e.newValue, defaultValue, key, 'useLocalStorage'));
|
|
4583
4636
|
};
|
|
4584
4637
|
window.addEventListener('storage', handler);
|
|
4585
4638
|
onDestroy(function() { window.removeEventListener('storage', handler); });
|
|
@@ -4593,15 +4646,13 @@ catch (e) {}
|
|
|
4593
4646
|
// event (filtered to sessionStorage's storageArea \u2014 localStorage events
|
|
4594
4647
|
// share the same listener but fire from a different area).
|
|
4595
4648
|
function useSessionStorage(key, defaultValue) {
|
|
4596
|
-
var
|
|
4597
|
-
var initial = stored !== null ? JSON.parse(stored) : defaultValue;
|
|
4598
|
-
var s = state(initial);
|
|
4649
|
+
var s = state(stxStorageRead(sessionStorage, key, defaultValue, 'useSessionStorage'));
|
|
4599
4650
|
effect(function() {
|
|
4600
|
-
sessionStorage
|
|
4651
|
+
stxStorageWrite(sessionStorage, key, s(), 'useSessionStorage');
|
|
4601
4652
|
});
|
|
4602
4653
|
var handler = function(e) {
|
|
4603
|
-
if (e.key === key && e.storageArea === sessionStorage)
|
|
4604
|
-
s.set(e.newValue
|
|
4654
|
+
if (e.key === key && (!e.storageArea || e.storageArea === sessionStorage))
|
|
4655
|
+
s.set(stxParseStored(e.newValue, defaultValue, key, 'useSessionStorage'));
|
|
4605
4656
|
};
|
|
4606
4657
|
window.addEventListener('storage', handler);
|
|
4607
4658
|
onDestroy(function() { window.removeEventListener('storage', handler); });
|