@stacksjs/stx 0.2.153 → 0.2.155
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 +1 -27
- 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 +1481 -1231
- package/dist/client-script-bundler.js +2 -4
- package/dist/client-script.d.ts +29 -0
- package/dist/client-script.js +140 -95
- package/dist/color-mode-boot.d.ts +31 -0
- package/dist/color-mode-boot.js +41 -5
- package/dist/component-renderer.js +10 -21
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/use-clipboard.d.ts +1 -5
- package/dist/composables/use-clipboard.js +57 -46
- package/dist/composables/use-color-mode.js +13 -5
- package/dist/composables/use-router.d.ts +28 -11
- package/dist/composables/use-router.js +63 -9
- package/dist/config.js +22 -7
- package/dist/craft.js +1029 -789
- package/dist/dev-server/crosswind.js +8 -6
- package/dist/dev-server/serve-app.js +4 -2
- package/dist/dev-server/serve-markdown.js +4 -1
- package/dist/dev-server/serve-multi.js +32 -20
- package/dist/document-shell.d.ts +65 -0
- package/dist/document-shell.js +59 -12
- 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/hot-reload.js +8 -2
- package/dist/includes.js +2 -2
- 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 +40 -10
- 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 +966 -726
- 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 +258 -47
- 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 +986 -746
- 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 +111 -16
- 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 +111 -16
package/dist/state-management.js
CHANGED
|
@@ -505,7 +505,10 @@ else {
|
|
|
505
505
|
});
|
|
506
506
|
};
|
|
507
507
|
|
|
508
|
-
window.stx
|
|
508
|
+
// Merge, never replace (#1804). s was seeded from window.stx above, so this
|
|
509
|
+
// keeps both any earlier keys and the object identity the waitForStore
|
|
510
|
+
// closure captured.
|
|
511
|
+
window.stx = Object.assign(window.stx || {}, s);
|
|
509
512
|
})();
|
|
510
513
|
`;
|
|
511
514
|
}
|
package/dist/store-loader.d.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discover store files from storesDir and bundle them into a single
|
|
3
|
-
* script that can be injected into the page.
|
|
4
|
-
*
|
|
5
|
-
* The bundle runs `defineStore(...)` for each store — since the signals
|
|
6
|
-
* runtime is already loaded, `defineStore`/`state`/`derived` are globals.
|
|
7
|
-
*
|
|
8
|
-
* Pass an absolute `storesDir` to look up stores there. When omitted, falls
|
|
9
|
-
* back to reading `storesDir` from the stx config at the current working
|
|
10
|
-
* directory — which is only correct when run from the app's own root.
|
|
11
|
-
*/
|
|
12
1
|
export declare function getStoreScript(storesDir?: string): Promise<string | null>;
|
|
13
2
|
/**
|
|
14
3
|
* Clear the cached store script (for dev mode hot reload).
|
package/dist/store-loader.js
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { loadStxConfig } from "./config.js";
|
|
3
3
|
import { getPublicEnvDefine } from "./public-env.js";
|
|
4
|
+
import { STX_RUNTIME_GLOBALS } from "./runtime-globals.js";
|
|
5
|
+
import { transformStoreImports } from "./store-imports.js";
|
|
4
6
|
const _cachedStoreScripts = new Map;
|
|
7
|
+
function collectDeclaredNames(code, into) {
|
|
8
|
+
for (const match of code.matchAll(/\b(?:const|let|var|function|class)\s+([A-Z_a-z$][\w$]*)/g))
|
|
9
|
+
if (match[1])
|
|
10
|
+
into.add(match[1]);
|
|
11
|
+
for (const match of code.matchAll(/\b(?:const|let|var)\s*\{([^{}]*)\}\s*=/g))
|
|
12
|
+
for (const part of (match[1] ?? "").split(",")) {
|
|
13
|
+
const name = ((part.includes(":") ? part.split(":").pop() : part) ?? "").replace(/=.*$/s, "").replace(/^\s*\.\.\./, "").trim();
|
|
14
|
+
if (/^[A-Z_a-z$][\w$]*$/.test(name))
|
|
15
|
+
into.add(name);
|
|
16
|
+
}
|
|
17
|
+
for (const match of code.matchAll(/\b(?:const|let|var)\s*\[([^\]]*)\]\s*=/g))
|
|
18
|
+
for (const part of (match[1] ?? "").split(",")) {
|
|
19
|
+
const name = part.replace(/=.*$/s, "").replace(/^\s*\.\.\./, "").trim();
|
|
20
|
+
if (/^[A-Z_a-z$][\w$]*$/.test(name))
|
|
21
|
+
into.add(name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
5
24
|
export async function getStoreScript(storesDir) {
|
|
6
25
|
let resolvedDir;
|
|
7
26
|
if (storesDir)
|
|
@@ -44,11 +63,13 @@ export async function getStoreScript(storesDir) {
|
|
|
44
63
|
return 0;
|
|
45
64
|
return a.hasUseStore ? 1 : -1;
|
|
46
65
|
});
|
|
47
|
-
const sortedFiles = filesWithDeps.map((f) => f.file), chunks = [], transpiler = new Bun.Transpiler({ loader: "ts", target: "browser", define: getPublicEnvDefine() });
|
|
66
|
+
const sortedFiles = filesWithDeps.map((f) => f.file), chunks = [], declaredNames = new Set, transpiler = new Bun.Transpiler({ loader: "ts", target: "browser", define: getPublicEnvDefine() });
|
|
48
67
|
for (const file of sortedFiles)
|
|
49
68
|
try {
|
|
50
69
|
let code = await Bun.file(file).text();
|
|
51
70
|
const storeName = path.basename(file, ".ts");
|
|
71
|
+
collectDeclaredNames(code, declaredNames);
|
|
72
|
+
code = transformStoreImports(code);
|
|
52
73
|
code = code.replace(/^import\s+.*from\s+['"][^'"]+['"]\s*;?\s*$/gm, "");
|
|
53
74
|
code = code.replace(/^export\s+(default\s+)?/gm, "");
|
|
54
75
|
code = transpiler.transformSync(code);
|
|
@@ -61,10 +82,13 @@ ${code.trim()}`);
|
|
|
61
82
|
_cachedStoreScripts.set(resolvedDir, "");
|
|
62
83
|
return null;
|
|
63
84
|
}
|
|
64
|
-
const
|
|
65
|
-
${chunks.join(`
|
|
85
|
+
const joinedChunks = chunks.join(`
|
|
66
86
|
|
|
87
|
+
`), code = `;(function(){
|
|
88
|
+
var __stx = window.stx || {};
|
|
89
|
+
${STX_RUNTIME_GLOBALS.filter((name) => !declaredNames.has(name) && new RegExp(`\\b${name}\\b`).test(joinedChunks)).map((name) => `var ${name} = __stx[${JSON.stringify(name)}];`).join(`
|
|
67
90
|
`)}
|
|
91
|
+
${joinedChunks}
|
|
68
92
|
})();`;
|
|
69
93
|
_cachedStoreScripts.set(resolvedDir, code);
|
|
70
94
|
return code;
|
package/dist/strict-lint.d.ts
CHANGED
|
@@ -33,17 +33,20 @@ export declare interface StxStrictOptions {
|
|
|
33
33
|
* with low false-positive rate).
|
|
34
34
|
*
|
|
35
35
|
* Phase 1 covers the five highest-hit footguns from the issue:
|
|
36
|
-
* 1. stx/no-bare-function-ref-in-event `@click="
|
|
37
|
-
* 2. stx/
|
|
38
|
-
* 3. stx/
|
|
39
|
-
* 4. stx/no-
|
|
40
|
-
*
|
|
36
|
+
* 1. stx/no-bare-function-ref-in-event `@click="store.method"` (member path)
|
|
37
|
+
* 2. stx/store-value-imports-must-be-local value import escaping resources/stores/
|
|
38
|
+
* 3. stx/no-signal-call-in-for-iteration-var calling the `:for` loop var like a signal
|
|
39
|
+
* 4. stx/no-backticks-in-html-comments a backtick inside an HTML comment
|
|
40
|
+
*
|
|
41
|
+
* `stx/no-view-level-script-client` was removed in #1815: it asserted at error
|
|
42
|
+
* severity that a view-level `<script client>` is silently dropped, which is
|
|
43
|
+
* false — it is the primary documented authoring pattern, and its symbols reach
|
|
44
|
+
* the built page. The rule told people to break working code.
|
|
41
45
|
*
|
|
42
46
|
* Remaining footguns (web-components, template tags, @include loop-var
|
|
43
47
|
* forwarding, etc.) are Phase 2.
|
|
44
48
|
*/
|
|
45
49
|
export type StxStrictRuleId = | 'stx/no-bare-function-ref-in-event'
|
|
46
|
-
| 'stx/no-view-level-script-client'
|
|
47
50
|
| 'stx/store-value-imports-must-be-local'
|
|
48
51
|
| 'stx/no-signal-call-in-for-iteration-var'
|
|
49
52
|
| 'stx/no-backticks-in-html-comments';
|
package/dist/strict-lint.js
CHANGED
|
@@ -117,11 +117,11 @@ function ruleBareFunctionRef(src, spans, out) {
|
|
|
117
117
|
if (DIRECTIVE_ATTR_NAMES.has(base))
|
|
118
118
|
continue;
|
|
119
119
|
const value = m[3].trim();
|
|
120
|
-
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)
|
|
120
|
+
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value)) {
|
|
121
121
|
const at = src.indexOf(m[3], m.index), { line, column } = offsetToLineCol(src, at < 0 ? m.index : at);
|
|
122
122
|
out.push({
|
|
123
123
|
ruleId: "stx/no-bare-function-ref-in-event",
|
|
124
|
-
message: `Event handler @${m[1]}="${value}" is a
|
|
124
|
+
message: `Event handler @${m[1]}="${value}" is a member path, which the runtime does not invoke \u2014 it only auto-calls a plain identifier. Call it: @${m[1]}="${value}($event)".`,
|
|
125
125
|
line,
|
|
126
126
|
column,
|
|
127
127
|
severity: "error"
|
|
@@ -129,23 +129,6 @@ function ruleBareFunctionRef(src, spans, out) {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
function ruleViewLevelScriptClient(src, fileKind, out) {
|
|
133
|
-
if (fileKind !== "view")
|
|
134
|
-
return;
|
|
135
|
-
const re = /<script\b([^>]*)>/gi;
|
|
136
|
-
let m;
|
|
137
|
-
while ((m = re.exec(src)) !== null)
|
|
138
|
-
if (/(?:^|\s)client(?=[\s/=]|$)/i.test(m[1])) {
|
|
139
|
-
const { line, column } = offsetToLineCol(src, m.index);
|
|
140
|
-
out.push({
|
|
141
|
-
ruleId: "stx/no-view-level-script-client",
|
|
142
|
-
message: "<script client> at the top level of a view is silently dropped \u2014 only components get client scopes. Move it into a component, or use <script> (server).",
|
|
143
|
-
line,
|
|
144
|
-
column,
|
|
145
|
-
severity: "error"
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
132
|
function normalizePath(p) {
|
|
150
133
|
const parts = p.split("/"), out = [];
|
|
151
134
|
for (const seg of parts) {
|
|
@@ -268,8 +251,6 @@ export function lintStxStrict(source, options = {}) {
|
|
|
268
251
|
const out = [], spans = rawTextSpans(source), filePath = (options.filePath ?? "").replace(/\\/g, "/"), fileKind = options.fileKind ?? (/\/components?\//i.test(filePath) ? "component" : /\/layouts?\//i.test(filePath) ? "layout" : "view");
|
|
269
252
|
if (isRuleOn(options.rules, "stx/no-bare-function-ref-in-event"))
|
|
270
253
|
ruleBareFunctionRef(source, spans, out);
|
|
271
|
-
if (isRuleOn(options.rules, "stx/no-view-level-script-client"))
|
|
272
|
-
ruleViewLevelScriptClient(source, fileKind, out);
|
|
273
254
|
if (filePath && isRuleOn(options.rules, "stx/store-value-imports-must-be-local"))
|
|
274
255
|
ruleStoreValueImports(source, filePath, out);
|
|
275
256
|
if (isRuleOn(options.rules, "stx/no-signal-call-in-for-iteration-var"))
|
package/dist/stx.d.ts
CHANGED
|
@@ -82,23 +82,49 @@ declare function onMount(_fn: () => void | StxCleanup): void
|
|
|
82
82
|
declare function onDestroy(_fn: () => void): void
|
|
83
83
|
declare function onBeforeMount(_fn: () => void): void
|
|
84
84
|
declare function onMounted(_fn: () => void | StxCleanup): void
|
|
85
|
-
declare function onBeforeUpdate(_fn: () => void): void
|
|
86
|
-
declare function onUpdated(_fn: () => void): void
|
|
87
85
|
declare function onBeforeUnmount(_fn: () => void): void
|
|
88
86
|
declare function onUnmounted(_fn: () => void): void
|
|
89
|
-
|
|
87
|
+
// onBeforeUpdate / onUpdated / onErrorCaptured are NOT declared: they exist in
|
|
88
|
+
// reactivity.ts and composition-api.ts but have no client-runtime counterpart,
|
|
89
|
+
// so declaring them promised a global that resolved to undefined (#1804).
|
|
90
90
|
|
|
91
91
|
// ============================================================================
|
|
92
92
|
// Template refs
|
|
93
93
|
// ============================================================================
|
|
94
94
|
|
|
95
|
+
/** Stable per-component id, unique across instances. */
|
|
96
|
+
declare function useId(_prefix?: string): string
|
|
97
|
+
/**
|
|
98
|
+
* Signal tracking a parent-driven `:prop="signal()"` attribute on the
|
|
99
|
+
* component's root element. One-way: parent to child.
|
|
100
|
+
*/
|
|
101
|
+
declare function useReactiveProp<T = unknown>(
|
|
102
|
+
_name: string,
|
|
103
|
+
_defaultValue?: T,
|
|
104
|
+
_options?: { parse?: (_value: string) => T },
|
|
105
|
+
): StxSignal<T>
|
|
95
106
|
declare function useRef<T = HTMLElement>(_name: string): { current: T | null }
|
|
96
107
|
|
|
97
108
|
// ============================================================================
|
|
98
109
|
// Routing / navigation
|
|
99
110
|
// ============================================================================
|
|
100
111
|
|
|
101
|
-
|
|
112
|
+
interface StxNavigateOptions {
|
|
113
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
114
|
+
replace?: boolean
|
|
115
|
+
/** Bypass the SPA router and perform a full document load. */
|
|
116
|
+
reload?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Navigate via the SPA router.
|
|
121
|
+
*
|
|
122
|
+
* The second argument is deliberately NOT typed to also accept a boolean. The
|
|
123
|
+
* legacy positional `forceReload` boolean still works at runtime — it was the
|
|
124
|
+
* shape that actually shipped — but keeping it out of the type means nothing
|
|
125
|
+
* that type-checks today changes meaning (#1807).
|
|
126
|
+
*/
|
|
127
|
+
declare function navigate(_url: string, _options?: StxNavigateOptions): void
|
|
102
128
|
declare function goBack(): void
|
|
103
129
|
declare function goForward(): void
|
|
104
130
|
declare function useRoute(): {
|
|
@@ -108,11 +134,39 @@ declare function useRoute(): {
|
|
|
108
134
|
hash: string
|
|
109
135
|
}
|
|
110
136
|
declare function setRouteParams(_params: Record<string, string>): void
|
|
137
|
+
/** How a search-param mutation is written to session history (#1825). */
|
|
138
|
+
interface StxSearchParamsCommitOptions {
|
|
139
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
140
|
+
replace?: boolean
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reactive access to the URL query string.
|
|
145
|
+
*
|
|
146
|
+
* The declaration was wrong in both directions (#1806): it promised `delete`
|
|
147
|
+
* and `has`, which did not exist at runtime, and omitted `setAll` and `data`,
|
|
148
|
+
* which did. `delete`/`has` are now implemented rather than dropped; `get`
|
|
149
|
+
* returns `undefined` for a missing key, not `null`, which is what the runtime
|
|
150
|
+
* and the docs have always said.
|
|
151
|
+
*/
|
|
111
152
|
declare function useSearchParams(): {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
has: (
|
|
153
|
+
/** The backing signal — read it to make a binding reactive to the query. */
|
|
154
|
+
data: StxSignal<Record<string, string>>
|
|
155
|
+
get: (_key: string) => string | undefined
|
|
156
|
+
has: (_key: string) => boolean
|
|
157
|
+
/** Set a param. Pushes a history entry unless `{ replace: true }` is passed. */
|
|
158
|
+
set: (_key: string, _value: string, _options?: StxSearchParamsCommitOptions) => void
|
|
159
|
+
/**
|
|
160
|
+
* Remove a param. Pushes a history entry unless `{ replace: true }` is passed.
|
|
161
|
+
*
|
|
162
|
+
* CONSUMING a one-shot param — an OAuth callback result, `?checkout=success`,
|
|
163
|
+
* a flash token — needs `{ replace: true }`. Under the default push, the URL
|
|
164
|
+
* that still carries the param becomes the previous history entry, so Back
|
|
165
|
+
* replays the callback and re-runs whatever consuming it triggered (#1825).
|
|
166
|
+
*/
|
|
167
|
+
delete: (_key: string, _options?: StxSearchParamsCommitOptions) => void
|
|
168
|
+
/** Set several params. Pushes a history entry unless `{ replace: true }` is passed. */
|
|
169
|
+
setAll: (_values: Record<string, string>, _options?: StxSearchParamsCommitOptions) => void
|
|
116
170
|
}
|
|
117
171
|
|
|
118
172
|
// ============================================================================
|
|
@@ -160,7 +214,6 @@ declare function useEventListener(
|
|
|
160
214
|
declare function useScrollLock(
|
|
161
215
|
_target?: HTMLElement | null | { current?: HTMLElement | null, value?: HTMLElement | null } | (() => HTMLElement | null | undefined),
|
|
162
216
|
): StxSignal<boolean>
|
|
163
|
-
declare function useMeta(_meta: Record<string, string>): void
|
|
164
217
|
declare function useClickOutside(_target: HTMLElement | StxRef<HTMLElement | null> | string | null, _handler: (event: MouseEvent) => void): StxCleanup
|
|
165
218
|
declare function useFocus(_target: HTMLElement | StxRef<HTMLElement | null> | string | null): { focused: StxSignal<boolean>, focus: () => void, blur: () => void }
|
|
166
219
|
|
|
@@ -209,6 +262,44 @@ declare function useAsync<T>(_fn: () => Promise<T>): {
|
|
|
209
262
|
declare function useLocalStorage<T>(_key: string, _defaultValue: T): StxSignal<T>
|
|
210
263
|
declare function useSessionStorage<T>(_key: string, _defaultValue: T): StxSignal<T>
|
|
211
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Options accepted by the auto-imported `useCookie` global.
|
|
267
|
+
*
|
|
268
|
+
* This describes the CLIENT RUNTIME's cookie composable, which is what a bare
|
|
269
|
+
* `useCookie(...)` in a `<script client>` block resolves to. The module export
|
|
270
|
+
* `@stacksjs/stx/composables` accepts a slightly wider `expires` (Date, epoch ms
|
|
271
|
+
* or parseable string); the runtime calls `.toUTCString()` on it, so only a Date
|
|
272
|
+
* works there. See #1710.
|
|
273
|
+
*/
|
|
274
|
+
interface StxCookieOptions {
|
|
275
|
+
/** Max age in seconds. */
|
|
276
|
+
maxAge?: number
|
|
277
|
+
/** Expiration date. */
|
|
278
|
+
expires?: Date
|
|
279
|
+
/** Cookie path. Defaults to '/'. */
|
|
280
|
+
path?: string
|
|
281
|
+
/** Cookie domain. */
|
|
282
|
+
domain?: string
|
|
283
|
+
/** Secure flag. Defaults to true when location.protocol is https:. */
|
|
284
|
+
secure?: boolean
|
|
285
|
+
/** SameSite attribute. Defaults to 'Lax'. */
|
|
286
|
+
sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none'
|
|
287
|
+
/** Value returned when the cookie is absent. Defaults to ''. */
|
|
288
|
+
defaultValue?: string
|
|
289
|
+
/** Custom encoder. Defaults to encodeURIComponent. */
|
|
290
|
+
encode?: (_value: string) => string
|
|
291
|
+
/** Custom decoder. Defaults to decodeURIComponent. */
|
|
292
|
+
decode?: (_value: string) => string
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reactive binding to a single cookie (#1808).
|
|
297
|
+
*
|
|
298
|
+
* Returns a signal, like `useLocalStorage` — writes serialise straight to
|
|
299
|
+
* `document.cookie`, and `.set('')` deletes by emitting `max-age=0`.
|
|
300
|
+
*/
|
|
301
|
+
declare function useCookie(_name: string, _options?: StxCookieOptions): StxSignal<string>
|
|
302
|
+
|
|
212
303
|
// ============================================================================
|
|
213
304
|
// WebSocket
|
|
214
305
|
// ============================================================================
|
|
@@ -317,6 +408,8 @@ interface StxSeoMetaConfig {
|
|
|
317
408
|
|
|
318
409
|
declare function useHead(_config: StxHeadConfig): void
|
|
319
410
|
declare function useSeoMeta(_config: StxSeoMetaConfig): void
|
|
411
|
+
/** No-op on the client; the real one runs at SSR/SSG time. */
|
|
412
|
+
declare function definePageMeta(_meta: Record<string, unknown>): void
|
|
320
413
|
|
|
321
414
|
// ============================================================================
|
|
322
415
|
// Vue-style reactivity (alternative API)
|
|
@@ -348,9 +441,9 @@ declare function defineExpose<T extends Record<string, any>>(_exposed: T): void
|
|
|
348
441
|
declare function defineSlots<T extends Record<string, (..._args: any[]) => any> = Record<string, (..._args: any[]) => any>>(): T
|
|
349
442
|
declare function provide<T>(_key: string | symbol, _value: T): void
|
|
350
443
|
declare function inject<T>(_key: string | symbol, _defaultValue?: T): T | undefined
|
|
351
|
-
declare function getCurrentInstance(): any
|
|
352
444
|
declare function useSlots(): Record<string, any>
|
|
353
|
-
|
|
445
|
+
// getCurrentInstance / useAttrs are NOT declared — composition-api.ts exports
|
|
446
|
+
// them for server use; no client-runtime counterpart exists (#1804).
|
|
354
447
|
declare function $computed<T>(_getter: () => T): StxRef<T>
|
|
355
448
|
declare function $watch<T>(
|
|
356
449
|
_source: (() => T) | StxRef<T> | StxSignal<T>,
|
|
@@ -462,16 +555,18 @@ declare function defineStore<S extends Record<string, any>, G extends Record<str
|
|
|
462
555
|
},
|
|
463
556
|
): () => S & G & A
|
|
464
557
|
declare function useStore<T = any>(_id: string): T
|
|
465
|
-
declare function
|
|
466
|
-
|
|
467
|
-
|
|
558
|
+
declare function registerStoresClient(_stores: Record<string, unknown>): void
|
|
559
|
+
// createStore / createSelector are exported by state-management.ts for server
|
|
560
|
+
// use and `action` has no implementation anywhere; none reaches window.stx, so
|
|
561
|
+
// none is declared as an ambient global (#1804).
|
|
468
562
|
|
|
469
563
|
// ============================================================================
|
|
470
564
|
// JSX runtime (Vue/React style)
|
|
471
565
|
// ============================================================================
|
|
472
566
|
|
|
473
|
-
|
|
474
|
-
|
|
567
|
+
// h / Fragment belong to the JSX runtime (jsx-runtime.ts) and are resolved by
|
|
568
|
+
// the transform, not destructured off window.stx — so they are not ambient
|
|
569
|
+
// globals (#1804). Import them explicitly if you call them directly.
|
|
475
570
|
|
|
476
571
|
// ============================================================================
|
|
477
572
|
// window.stx registry
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find identifiers a script CALLS that resolve to nothing in the browser.
|
|
3
|
+
*
|
|
4
|
+
* Restricted to names we can name a cause for — exports of `composables/` with
|
|
5
|
+
* no runtime counterpart. A broader "looks like a hook and is not in scope"
|
|
6
|
+
* heuristic would fire on legitimate local helpers and app composables, and a
|
|
7
|
+
* warning people learn to ignore is worse than no warning.
|
|
8
|
+
*
|
|
9
|
+
* @param source the script body, after imports have been transformed
|
|
10
|
+
* @param provided names already in scope (runtime globals, locals, imports)
|
|
11
|
+
*/
|
|
12
|
+
export declare function findUnresolvedIdentifiers(source: string, provided: Iterable<string>): UnresolvedIdentifier[];
|
|
13
|
+
/**
|
|
14
|
+
* Report unresolved identifiers against a file, once each.
|
|
15
|
+
*
|
|
16
|
+
* Returns the diagnostics so callers can assert on them without capturing
|
|
17
|
+
* console output.
|
|
18
|
+
*/
|
|
19
|
+
export declare function reportUnresolvedIdentifiers(source: string, provided: Iterable<string>, filePath: string): UnresolvedIdentifier[];
|
|
20
|
+
/**
|
|
21
|
+
* Diagnose an identifier a client script calls that will not exist in the browser.
|
|
22
|
+
*
|
|
23
|
+
* `packages/stx/src/composables/` exports 149 functions. Only 29 of them reach
|
|
24
|
+
* the client runtime; the other 120 are module exports with no counterpart on
|
|
25
|
+
* `window.stx`. They type-check, they autocomplete, and calling one from a
|
|
26
|
+
* `<script client>` block throws `ReferenceError` — which used to take down the
|
|
27
|
+
* whole page (fixed in the DOMContentLoaded handler, stacksjs/stx#1805).
|
|
28
|
+
*
|
|
29
|
+
* Containing the throw stops one bad name costing the document, but the author
|
|
30
|
+
* still has to work out WHY a documented, exported, type-checking composable is
|
|
31
|
+
* not defined. This turns that into a message at compile time naming the
|
|
32
|
+
* identifier, the file, and the fix.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately a warning, not a build failure. #1810 is a report of the dev
|
|
35
|
+
* server aborting on one bad view with no filename; adding a second way to abort
|
|
36
|
+
* the build would be solving one complaint by creating another.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Functions `composables/index.ts` exports that the client runtime does NOT
|
|
40
|
+
* provide.
|
|
41
|
+
*
|
|
42
|
+
* Generated — `test/unresolved-identifiers.test.ts` recomputes it from the real
|
|
43
|
+
* module surface and fails when this drifts, so it cannot silently rot the way
|
|
44
|
+
* the auto-import list did (#1804).
|
|
45
|
+
*/
|
|
46
|
+
export declare const SERVER_ONLY_COMPOSABLES: readonly string[];
|
|
47
|
+
export declare interface UnresolvedIdentifier {
|
|
48
|
+
name: string
|
|
49
|
+
message: string
|
|
50
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const SERVER_ONLY_COMPOSABLES = [
|
|
2
|
+
"broadcast",
|
|
3
|
+
"calculateDistance",
|
|
4
|
+
"canNotify",
|
|
5
|
+
"clearCookies",
|
|
6
|
+
"clearFetchCache",
|
|
7
|
+
"clearStorage",
|
|
8
|
+
"copyToClipboard",
|
|
9
|
+
"createShareableFile",
|
|
10
|
+
"getBatteryLevel",
|
|
11
|
+
"getCookie",
|
|
12
|
+
"getCurrentPosition",
|
|
13
|
+
"getStorageKeys",
|
|
14
|
+
"getStorageSize",
|
|
15
|
+
"getVoices",
|
|
16
|
+
"hasBattery",
|
|
17
|
+
"hasCameraPermission",
|
|
18
|
+
"hasGeolocationPermission",
|
|
19
|
+
"hasMicrophonePermission",
|
|
20
|
+
"hasNotificationPermission",
|
|
21
|
+
"hasResizeObserver",
|
|
22
|
+
"hexToHsl",
|
|
23
|
+
"hexToRgb",
|
|
24
|
+
"isBroadcastChannelSupported",
|
|
25
|
+
"isCharging",
|
|
26
|
+
"isDeviceMotionSupported",
|
|
27
|
+
"isDeviceOrientationSupported",
|
|
28
|
+
"isEventSourceSupported",
|
|
29
|
+
"isEyeDropperSupported",
|
|
30
|
+
"isInFullscreen",
|
|
31
|
+
"isMutationObserverSupported",
|
|
32
|
+
"isPermissionGranted",
|
|
33
|
+
"isSpeechRecognitionSupported",
|
|
34
|
+
"isSpeechSynthesisSupported",
|
|
35
|
+
"isWakeLockSupported",
|
|
36
|
+
"isWebSocketSupported",
|
|
37
|
+
"notify",
|
|
38
|
+
"parseCookies",
|
|
39
|
+
"pickColor",
|
|
40
|
+
"prefetch",
|
|
41
|
+
"removeCookie",
|
|
42
|
+
"requestMediaPermissions",
|
|
43
|
+
"requestMotionPermission",
|
|
44
|
+
"requestNotificationPermission",
|
|
45
|
+
"requestOrientationPermission",
|
|
46
|
+
"setCookie",
|
|
47
|
+
"share",
|
|
48
|
+
"shareCurrentPage",
|
|
49
|
+
"shareFiles",
|
|
50
|
+
"shareText",
|
|
51
|
+
"shareURL",
|
|
52
|
+
"shareWithFallback",
|
|
53
|
+
"speak",
|
|
54
|
+
"stopSpeaking",
|
|
55
|
+
"toggleFullscreen",
|
|
56
|
+
"useAsyncData",
|
|
57
|
+
"useAttributeObserver",
|
|
58
|
+
"useAutoLogout",
|
|
59
|
+
"useAutoWakeLock",
|
|
60
|
+
"useBattery",
|
|
61
|
+
"useBreakpoint",
|
|
62
|
+
"useBroadcastChannel",
|
|
63
|
+
"useChannel",
|
|
64
|
+
"useChildListObserver",
|
|
65
|
+
"useClipboard",
|
|
66
|
+
"useColorHistory",
|
|
67
|
+
"useConditionalWakeLock",
|
|
68
|
+
"useCookies",
|
|
69
|
+
"useCopySelection",
|
|
70
|
+
"useDeviceMotion",
|
|
71
|
+
"useDeviceOrientation",
|
|
72
|
+
"useElementSize",
|
|
73
|
+
"useElementTextSelection",
|
|
74
|
+
"useElementVisibility",
|
|
75
|
+
"useEventSource",
|
|
76
|
+
"useEyeDropper",
|
|
77
|
+
"useFavicon",
|
|
78
|
+
"useFullscreen",
|
|
79
|
+
"useGeolocation",
|
|
80
|
+
"useGeolocationWatch",
|
|
81
|
+
"useHotkey",
|
|
82
|
+
"useIdle",
|
|
83
|
+
"useIdleState",
|
|
84
|
+
"useInfiniteScroll",
|
|
85
|
+
"useIntersectionObserver",
|
|
86
|
+
"useIntersectionObserverMultiple",
|
|
87
|
+
"useIsDesktop",
|
|
88
|
+
"useIsMobile",
|
|
89
|
+
"useIsTablet",
|
|
90
|
+
"useKeyPressed",
|
|
91
|
+
"useKeySequence",
|
|
92
|
+
"useKeyboard",
|
|
93
|
+
"useLastActive",
|
|
94
|
+
"useLazyLoad",
|
|
95
|
+
"useMeta",
|
|
96
|
+
"useMouse",
|
|
97
|
+
"useMouseInElement",
|
|
98
|
+
"useMutationObserver",
|
|
99
|
+
"useNetwork",
|
|
100
|
+
"useNotification",
|
|
101
|
+
"useOnline",
|
|
102
|
+
"useParallax",
|
|
103
|
+
"usePermission",
|
|
104
|
+
"usePermissions",
|
|
105
|
+
"usePointer",
|
|
106
|
+
"usePost",
|
|
107
|
+
"useResizeObserver",
|
|
108
|
+
"useResizeObserverMultiple",
|
|
109
|
+
"useSSE",
|
|
110
|
+
"useScroll",
|
|
111
|
+
"useSelectionPopup",
|
|
112
|
+
"useShare",
|
|
113
|
+
"useSpeechRecognition",
|
|
114
|
+
"useSpeechSynthesis",
|
|
115
|
+
"useStorage",
|
|
116
|
+
"useTextObserver",
|
|
117
|
+
"useTextSelection",
|
|
118
|
+
"useTitle",
|
|
119
|
+
"useVisibility",
|
|
120
|
+
"useWakeLock",
|
|
121
|
+
"useWindowSize"
|
|
122
|
+
];
|
|
123
|
+
const SERVER_ONLY = new Set(SERVER_ONLY_COMPOSABLES);
|
|
124
|
+
export function findUnresolvedIdentifiers(source, provided) {
|
|
125
|
+
const inScope = provided instanceof Set ? provided : new Set(provided), found = new Map;
|
|
126
|
+
for (const match of source.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
127
|
+
const name = match[1];
|
|
128
|
+
if (found.has(name) || inScope.has(name) || !SERVER_ONLY.has(name))
|
|
129
|
+
continue;
|
|
130
|
+
if (new RegExp(`(?:const|let|var|function|class)\\s+${name}\\b`).test(source))
|
|
131
|
+
continue;
|
|
132
|
+
if (new RegExp(`import\\s[^;]*\\b${name}\\b[^;]*from`).test(source))
|
|
133
|
+
continue;
|
|
134
|
+
found.set(name, {
|
|
135
|
+
name,
|
|
136
|
+
message: `"${name}" is exported by @stacksjs/stx/composables but is not available in the browser \u2014 ` + `it is a server-only module export, so this call throws ReferenceError at runtime. Import it explicitly (import { ${name} } from '@stacksjs/stx/composables') so the bundler inlines it, or use a runtime equivalent from window.stx.`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return [...found.values()];
|
|
140
|
+
}
|
|
141
|
+
export function reportUnresolvedIdentifiers(source, provided, filePath) {
|
|
142
|
+
const unresolved = findUnresolvedIdentifiers(source, provided);
|
|
143
|
+
for (const item of unresolved)
|
|
144
|
+
console.warn(`[stx] ${filePath}: ${item.message}`);
|
|
145
|
+
return unresolved;
|
|
146
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { extractAndStripCssImports, extractBridgeData, injectBrowserCoreAutoImports, processClientScript, renderVendorStyleTags } from "./client-script.js";
|
|
4
4
|
import { COMPONENT_CLIENT_FACTORIES_CONTEXT_KEY, registerComponentClientFactory } from "./component-client-factories.js";
|
|
5
|
-
import { interpolateScriptExpressions, processExpressions } from "./expressions.js";
|
|
5
|
+
import { interpolateScriptExpressions, processExpressions, usesSignalsInScript } from "./expressions.js";
|
|
6
6
|
import { COMPONENT_SCOPE_LOCAL_GLOBALS, buildRuntimeGlobalsDestructure } from "./runtime-globals.js";
|
|
7
7
|
import { transformStoreImports } from "./store-imports.js";
|
|
8
8
|
import { LRUCache } from "./performance-utils.js";
|
|
@@ -576,7 +576,7 @@ ${triedPaths.map((p) => ` - ${p}`).join(`
|
|
|
576
576
|
$slots,
|
|
577
577
|
__processedComponents: branchComponents,
|
|
578
578
|
__sections: {},
|
|
579
|
-
|
|
579
|
+
__stx_signals_gate: usesSignalsInScript(componentContent) || slotContentHasExpressions(slotContent)
|
|
580
580
|
};
|
|
581
581
|
applyDestructuredPropDefaults(componentContent, componentContext);
|
|
582
582
|
let workingContent = componentContent;
|
|
@@ -725,7 +725,7 @@ finally {
|
|
|
725
725
|
}`, factoryId = hydrateTrigger ? null : registerComponentClientFactory(parentContext, factoryBody), wrappedContent = factoryId ? `window.__stxComponentFactories[${JSON.stringify(factoryId)}](${JSON.stringify(scopeId)});` : `(${factoryBody})(${JSON.stringify(scopeId)});`;
|
|
726
726
|
if (hydrateTrigger)
|
|
727
727
|
return `${vendorStyleTags}<script type="stx/island" data-stx-island="${scopeId}" data-stx-scoped${attrs}>${wrappedContent}</script>`;
|
|
728
|
-
return `${vendorStyleTags}<script data-stx-scoped${attrs}>${wrappedContent}</script>`;
|
|
728
|
+
return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always"${attrs}>${wrappedContent}</script>`;
|
|
729
729
|
}));
|
|
730
730
|
output = output + `
|
|
731
731
|
` + scopedScripts.join(`
|
|
@@ -307,7 +307,13 @@ export async function extractVariables(scriptContent, context, filePath, options
|
|
|
307
307
|
}
|
|
308
308
|
return cached;
|
|
309
309
|
};
|
|
310
|
-
}, effect = (_fn) => {}, batch = (fn) => fn(), onMount = (_fn) => {}, onDestroy = (_fn) => {}
|
|
310
|
+
}, effect = (_fn) => {}, batch = (fn) => fn(), onMount = (_fn) => {}, onDestroy = (_fn) => {};
|
|
311
|
+
function definePageMeta(meta) {
|
|
312
|
+
const config = meta || {};
|
|
313
|
+
if (config.title !== void 0 || config.description !== void 0)
|
|
314
|
+
useSeoMeta({ title: config.title, description: config.description });
|
|
315
|
+
}
|
|
316
|
+
const useRoute = () => {
|
|
311
317
|
const serveCtx = context.__stxServeContext, search = context.__stxServeSearch ?? serveCtx?.search ?? "", path = serveCtx?.path ?? "";
|
|
312
318
|
return {
|
|
313
319
|
params: context.params ?? {},
|
|
@@ -537,6 +543,7 @@ export function convertToCommonJS(scriptContent, filePath) {
|
|
|
537
543
|
}
|
|
538
544
|
if (line.startsWith("definePageMeta(")) {
|
|
539
545
|
let depth = 0, done = !1;
|
|
546
|
+
const callStart = i;
|
|
540
547
|
for (let j = i;j < lines.length && !done; j++) {
|
|
541
548
|
let inString = null, escaped = !1;
|
|
542
549
|
const text = lines[j];
|
|
@@ -573,6 +580,15 @@ export function convertToCommonJS(scriptContent, filePath) {
|
|
|
573
580
|
}
|
|
574
581
|
if (!done)
|
|
575
582
|
i = lines.length;
|
|
583
|
+
const callText = lines.slice(callStart, i).join(`
|
|
584
|
+
`), lifted = [];
|
|
585
|
+
for (const key of ["title", "description"]) {
|
|
586
|
+
const match = new RegExp(`\\b${key}\\s*:\\s*(['"\`])((?:\\\\.|(?!\\1)[^\\\\])*)\\1`).exec(callText);
|
|
587
|
+
if (match)
|
|
588
|
+
lifted.push(`${key}: ${JSON.stringify(match[2])}`);
|
|
589
|
+
}
|
|
590
|
+
if (lifted.length > 0)
|
|
591
|
+
convertedLines.push(`useSeoMeta({ ${lifted.join(", ")} });`);
|
|
576
592
|
continue;
|
|
577
593
|
}
|
|
578
594
|
if (line.startsWith("import ") || line === "import" || line.startsWith("import{")) {
|
package/dist/x-element.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/stx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.155",
|
|
5
5
|
"description": "A performant UI Framework. Powered by Bun.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.org>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -117,12 +117,12 @@
|
|
|
117
117
|
"dependencies": {
|
|
118
118
|
"@cwcss/crosswind": "^0.2.14",
|
|
119
119
|
"@stacksjs/clapp": "^0.2.0",
|
|
120
|
-
"@stacksjs/desktop": "0.2.
|
|
121
|
-
"@stacksjs/sanitizer": "0.2.
|
|
120
|
+
"@stacksjs/desktop": "0.2.155",
|
|
121
|
+
"@stacksjs/sanitizer": "0.2.155",
|
|
122
122
|
"@stacksjs/ts-i18n": "^0.1.11",
|
|
123
|
-
"bun-plugin-stx": "0.2.
|
|
123
|
+
"bun-plugin-stx": "0.2.155",
|
|
124
124
|
"bunfig": "^0.15.17",
|
|
125
|
-
"stx-router": "0.2.
|
|
125
|
+
"stx-router": "0.2.155",
|
|
126
126
|
"ts-broadcasting": "^0.0.9",
|
|
127
127
|
"ts-images": "^0.1.9",
|
|
128
128
|
"ts-syntax-highlighter": "^0.2.1"
|