@stacksjs/stx 0.2.170 → 0.2.172
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 +1263 -1168
- 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.d.ts +1 -24
- package/dist/codemod.js +61 -9
- package/dist/compression.d.ts +11 -4
- package/dist/compression.js +52 -13
- package/dist/craft.js +489 -415
- 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 +8 -0
- package/dist/index.js +2 -0
- package/dist/page-action.d.ts +83 -0
- package/dist/page-action.js +64 -0
- package/dist/production-builder.js +1 -0
- package/dist/production-server.js +10 -3
- package/dist/pwa.js +398 -324
- package/dist/reactive-tracking.d.ts +81 -0
- package/dist/reactive-tracking.js +49 -0
- package/dist/reactivity.js +29 -23
- package/dist/render.js +5 -18
- package/dist/runtime-injection.js +25 -39
- package/dist/script-validation.d.ts +0 -7
- package/dist/script-validation.js +6 -4
- package/dist/signals-api.d.ts +18 -0
- package/dist/signals-api.js +26 -41
- package/dist/signals.js +93 -15
- package/dist/ssg.d.ts +8 -0
- package/dist/ssg.js +515 -441
- 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 +103 -1
- package/dist/stx-virtual-ts.js +124 -7
- 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 +2 -14
- package/dist/typecheck.js +133 -11
- package/dist/types/config-types.d.ts +10 -3
- package/dist/variable-extractor.js +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one piece of state that decides what is reactive to what.
|
|
3
|
+
*
|
|
4
|
+
* stx shipped two independent reactive systems in the same package entry, and
|
|
5
|
+
* the only thing keeping them apart was a variable name: `signals-api.ts` had
|
|
6
|
+
* `activeEffect`, `reactivity.ts` had `currentEffect`, and each recorded its
|
|
7
|
+
* dependencies against its own. Both are the same shape — the nullary function
|
|
8
|
+
* to re-run — so which system a value belonged to was decided entirely by
|
|
9
|
+
* which module happened to declare the variable that was set when it was read.
|
|
10
|
+
*
|
|
11
|
+
* Measured before this module existed, on `import { … } from 'stx'`:
|
|
12
|
+
*
|
|
13
|
+
* reactive() + effect() INERT reactive() + watchEffect() REACTIVE
|
|
14
|
+
* ref() + effect() INERT ref() + watchEffect() REACTIVE
|
|
15
|
+
* state() + effect() REACTIVE state() + watchEffect() INERT
|
|
16
|
+
*
|
|
17
|
+
* Four of six silently dead. Not an error, not a warning — the effect ran once
|
|
18
|
+
* and never again, which reads as "the value never changed". All eight names
|
|
19
|
+
* come from the same entry point with nothing to tell an author which half of
|
|
20
|
+
* the matrix they are standing in (stacksjs/stx#1885).
|
|
21
|
+
*
|
|
22
|
+
* Owning the state here rather than in either module is what makes the two
|
|
23
|
+
* interoperate without one importing the other, which would be a cycle. It is
|
|
24
|
+
* also why this file is small and has no opinions: everything else about a
|
|
25
|
+
* signal — how it stores its value, when it considers itself changed, what it
|
|
26
|
+
* exposes — stays where it was.
|
|
27
|
+
*
|
|
28
|
+
* ## What this does NOT bridge
|
|
29
|
+
*
|
|
30
|
+
* The client runtime in `signals.ts` is generated as a template literal and
|
|
31
|
+
* owns a third copy of this state inside that string. No module can import it,
|
|
32
|
+
* so the runtime is unaffected by this file and stays a separate system. See
|
|
33
|
+
* CLAUDE.md item 40; the parity suites are what hold those two in step.
|
|
34
|
+
*
|
|
35
|
+
* @module reactive-tracking
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* The effect currently collecting dependencies.
|
|
39
|
+
*
|
|
40
|
+
* A reactive read calls this and, when it is not null, records the result as a
|
|
41
|
+
* subscriber. That is the whole of dependency tracking.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getActiveSubscriber(): (() => void) | null;
|
|
44
|
+
/**
|
|
45
|
+
* Make `subscriber` the one collecting dependencies, and return the previous.
|
|
46
|
+
*
|
|
47
|
+
* Returning the previous value rather than requiring the caller to read it
|
|
48
|
+
* first is deliberate: the restore is the step that gets forgotten, and a
|
|
49
|
+
* forgotten restore leaks the inner effect's identity into the outer scope, so
|
|
50
|
+
* every subsequent read subscribes the wrong function. Pair with
|
|
51
|
+
* {@link restoreActiveSubscriber} in a `finally`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function setActiveSubscriber(subscriber: () => void): (() => void) | null;
|
|
54
|
+
/** Undo a {@link setActiveSubscriber}, restoring what was listening before. */
|
|
55
|
+
export declare function restoreActiveSubscriber(previous: (() => void) | null): void;
|
|
56
|
+
/** How deeply effects are currently nested. */
|
|
57
|
+
export declare function subscriberDepth(): number;
|
|
58
|
+
/** Whether updates are currently being collected rather than applied. */
|
|
59
|
+
export declare function isBatching(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Defer an effect to the end of the current batch.
|
|
62
|
+
*
|
|
63
|
+
* A `Set` rather than an array, so a signal written five times in one batch
|
|
64
|
+
* still runs its subscribers once.
|
|
65
|
+
*/
|
|
66
|
+
export declare function enqueueEffect(effect: () => void): void;
|
|
67
|
+
/**
|
|
68
|
+
* Collect updates rather than applying them, and run each affected effect once
|
|
69
|
+
* when the outermost batch ends.
|
|
70
|
+
*
|
|
71
|
+
* Nested calls do not flush early — a `batch` inside a `batch` would otherwise
|
|
72
|
+
* run the outer batch's pending effects against half-applied state.
|
|
73
|
+
*/
|
|
74
|
+
export declare function runBatched(fn: () => void): void;
|
|
75
|
+
/**
|
|
76
|
+
* Run `fn` with dependency tracking suspended.
|
|
77
|
+
*
|
|
78
|
+
* Reads inside are not recorded against the enclosing effect, which is what
|
|
79
|
+
* `untrack`/`peek` are for in both modules.
|
|
80
|
+
*/
|
|
81
|
+
export declare function withoutTracking<T>(fn: () => T): T;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
let activeSubscriber = null;
|
|
2
|
+
const subscriberStack = [];
|
|
3
|
+
let batchDepth = 0;
|
|
4
|
+
const pendingEffects = new Set;
|
|
5
|
+
export function getActiveSubscriber() {
|
|
6
|
+
return activeSubscriber;
|
|
7
|
+
}
|
|
8
|
+
export function setActiveSubscriber(subscriber) {
|
|
9
|
+
const previous = activeSubscriber;
|
|
10
|
+
subscriberStack.push(subscriber);
|
|
11
|
+
activeSubscriber = subscriber;
|
|
12
|
+
return previous;
|
|
13
|
+
}
|
|
14
|
+
export function restoreActiveSubscriber(previous) {
|
|
15
|
+
subscriberStack.pop();
|
|
16
|
+
activeSubscriber = previous;
|
|
17
|
+
}
|
|
18
|
+
export function subscriberDepth() {
|
|
19
|
+
return subscriberStack.length;
|
|
20
|
+
}
|
|
21
|
+
export function isBatching() {
|
|
22
|
+
return batchDepth > 0;
|
|
23
|
+
}
|
|
24
|
+
export function enqueueEffect(effect) {
|
|
25
|
+
pendingEffects.add(effect);
|
|
26
|
+
}
|
|
27
|
+
export function runBatched(fn) {
|
|
28
|
+
batchDepth++;
|
|
29
|
+
try {
|
|
30
|
+
fn();
|
|
31
|
+
} finally {
|
|
32
|
+
batchDepth--;
|
|
33
|
+
if (batchDepth === 0) {
|
|
34
|
+
const queued = [...pendingEffects];
|
|
35
|
+
pendingEffects.clear();
|
|
36
|
+
for (const effect of queued)
|
|
37
|
+
effect();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function withoutTracking(fn) {
|
|
42
|
+
const previous = activeSubscriber;
|
|
43
|
+
activeSubscriber = null;
|
|
44
|
+
try {
|
|
45
|
+
return fn();
|
|
46
|
+
} finally {
|
|
47
|
+
activeSubscriber = previous;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/reactivity.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { enqueueEffect, getActiveSubscriber, isBatching, restoreActiveSubscriber, setActiveSubscriber } from "./reactive-tracking.js";
|
|
2
|
+
let currentInstance = null;
|
|
2
3
|
function structuralEqual(a, b, seen = new WeakSet) {
|
|
3
4
|
if (Object.is(a, b))
|
|
4
5
|
return !0;
|
|
@@ -86,8 +87,9 @@ export function ref(initialValue) {
|
|
|
86
87
|
let value = initialValue;
|
|
87
88
|
const id = Symbol("ref"), refObj = {
|
|
88
89
|
get value() {
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const subscriber = getActiveSubscriber();
|
|
91
|
+
if (subscriber)
|
|
92
|
+
effectSubscribers.add(subscriber);
|
|
91
93
|
return value;
|
|
92
94
|
},
|
|
93
95
|
set value(newValue) {
|
|
@@ -97,7 +99,10 @@ export function ref(initialValue) {
|
|
|
97
99
|
for (const callback of subscribers)
|
|
98
100
|
callback(newValue, oldValue);
|
|
99
101
|
for (const effect of effectSubscribers)
|
|
100
|
-
|
|
102
|
+
if (isBatching())
|
|
103
|
+
enqueueEffect(effect);
|
|
104
|
+
else
|
|
105
|
+
effect();
|
|
101
106
|
}
|
|
102
107
|
},
|
|
103
108
|
subscribe(callback) {
|
|
@@ -118,8 +123,12 @@ export function reactive(target) {
|
|
|
118
123
|
return existing;
|
|
119
124
|
const subscribers = new Map, notify = (key) => {
|
|
120
125
|
const subs = subscribers.get(key);
|
|
121
|
-
if (subs)
|
|
122
|
-
|
|
126
|
+
if (!subs)
|
|
127
|
+
return;
|
|
128
|
+
for (const callback of [...subs])
|
|
129
|
+
if (isBatching())
|
|
130
|
+
enqueueEffect(callback);
|
|
131
|
+
else
|
|
123
132
|
callback();
|
|
124
133
|
}, subscribe = (key, callback) => {
|
|
125
134
|
if (!subscribers.has(key))
|
|
@@ -130,9 +139,9 @@ export function reactive(target) {
|
|
|
130
139
|
};
|
|
131
140
|
}, proxy = new Proxy(target, {
|
|
132
141
|
get(obj, prop, receiver) {
|
|
133
|
-
const value = Reflect.get(obj, prop, receiver);
|
|
134
|
-
if (
|
|
135
|
-
subscribe(prop,
|
|
142
|
+
const value = Reflect.get(obj, prop, receiver), subscriber = getActiveSubscriber();
|
|
143
|
+
if (subscriber)
|
|
144
|
+
subscribe(prop, subscriber);
|
|
136
145
|
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
137
146
|
const proto = Object.getPrototypeOf(value);
|
|
138
147
|
if (proto === Object.prototype || proto === null)
|
|
@@ -162,10 +171,9 @@ export function computed(getter) {
|
|
|
162
171
|
update();
|
|
163
172
|
}, update = () => {
|
|
164
173
|
if (dirty) {
|
|
165
|
-
const oldValue = cachedValue, prevEffect =
|
|
166
|
-
currentEffect = markDirty;
|
|
174
|
+
const oldValue = cachedValue, prevEffect = setActiveSubscriber(markDirty);
|
|
167
175
|
cachedValue = getter();
|
|
168
|
-
|
|
176
|
+
restoreActiveSubscriber(prevEffect);
|
|
169
177
|
dirty = !1;
|
|
170
178
|
if (!Object.is(cachedValue, oldValue)) {
|
|
171
179
|
for (const callback of subscribers)
|
|
@@ -174,15 +182,15 @@ export function computed(getter) {
|
|
|
174
182
|
effect();
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
|
-
}, prevEffect =
|
|
178
|
-
currentEffect = markDirty;
|
|
185
|
+
}, prevEffect = setActiveSubscriber(markDirty);
|
|
179
186
|
cachedValue = getter();
|
|
180
187
|
dirty = !1;
|
|
181
|
-
|
|
188
|
+
restoreActiveSubscriber(prevEffect);
|
|
182
189
|
return {
|
|
183
190
|
get value() {
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
const subscriber = getActiveSubscriber();
|
|
192
|
+
if (subscriber)
|
|
193
|
+
effectSubscribers.add(subscriber);
|
|
186
194
|
update();
|
|
187
195
|
return cachedValue;
|
|
188
196
|
},
|
|
@@ -243,12 +251,11 @@ export function watch(source, callback, options = {}) {
|
|
|
243
251
|
});
|
|
244
252
|
}, getValueTracking = () => {
|
|
245
253
|
if (typeof source === "function") {
|
|
246
|
-
const prevEffect =
|
|
247
|
-
currentEffect = onChange;
|
|
254
|
+
const prevEffect = setActiveSubscriber(onChange);
|
|
248
255
|
try {
|
|
249
256
|
return source();
|
|
250
257
|
} finally {
|
|
251
|
-
|
|
258
|
+
restoreActiveSubscriber(prevEffect);
|
|
252
259
|
}
|
|
253
260
|
}
|
|
254
261
|
return source.value;
|
|
@@ -299,10 +306,9 @@ export function watchEffect(effect) {
|
|
|
299
306
|
return;
|
|
300
307
|
if (cleanup)
|
|
301
308
|
cleanup();
|
|
302
|
-
const prevEffect =
|
|
303
|
-
currentEffect = run;
|
|
309
|
+
const prevEffect = setActiveSubscriber(run);
|
|
304
310
|
cleanup = effect();
|
|
305
|
-
|
|
311
|
+
restoreActiveSubscriber(prevEffect);
|
|
306
312
|
};
|
|
307
313
|
run();
|
|
308
314
|
if (currentInstance)
|
package/dist/render.js
CHANGED
|
@@ -27,24 +27,11 @@ function parseTemplate(content) {
|
|
|
27
27
|
let sMatch;
|
|
28
28
|
const removeRanges = [];
|
|
29
29
|
while ((sMatch = serverScriptOpenRe.exec(templateContent)) !== null) {
|
|
30
|
-
const tagEnd = sMatch.index + sMatch[0].length;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
break;
|
|
36
|
-
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
37
|
-
sDepth++;
|
|
38
|
-
sPos = nextOpen + 7;
|
|
39
|
-
} else {
|
|
40
|
-
sDepth--;
|
|
41
|
-
if (sDepth === 0) {
|
|
42
|
-
removeRanges.push({ start: sMatch.index, end: nextClose + 9 });
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
sPos = nextClose + 9;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
30
|
+
const tagEnd = sMatch.index + sMatch[0].length, closeAt = templateContent.indexOf("</script>", tagEnd);
|
|
31
|
+
if (closeAt === -1)
|
|
32
|
+
continue;
|
|
33
|
+
removeRanges.push({ start: sMatch.index, end: closeAt + 9 });
|
|
34
|
+
serverScriptOpenRe.lastIndex = closeAt + 9;
|
|
48
35
|
}
|
|
49
36
|
for (let ri = removeRanges.length - 1;ri >= 0; ri--)
|
|
50
37
|
templateContent = templateContent.substring(0, removeRanges[ri].start) + templateContent.substring(removeRanges[ri].end);
|
|
@@ -76,51 +76,37 @@ export function outputNeedsSignalsRuntime(html) {
|
|
|
76
76
|
}
|
|
77
77
|
return !1;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return template.slice(0, idx) + runtimeScript + `
|
|
90
|
-
` + template.slice(idx);
|
|
91
|
-
}
|
|
92
|
-
return runtimeScript + `
|
|
93
|
-
` + template;
|
|
94
|
-
}
|
|
95
|
-
if (options.buildMode === "compile") {
|
|
96
|
-
const runtimeScript = '<script data-stx-runtime src="/__stx/runtime.__STX_HASH__.js"></script>', firstScriptInDoc = template.indexOf("<script");
|
|
97
|
-
if (firstScriptInDoc !== -1)
|
|
98
|
-
return template.slice(0, firstScriptInDoc) + runtimeScript + `
|
|
99
|
-
` + template.slice(firstScriptInDoc);
|
|
100
|
-
if (template.includes("</head>")) {
|
|
101
|
-
const idx = template.indexOf("</head>");
|
|
102
|
-
return template.slice(0, idx) + runtimeScript + `
|
|
103
|
-
` + template.slice(idx);
|
|
104
|
-
}
|
|
105
|
-
return runtimeScript + `
|
|
106
|
-
` + template;
|
|
79
|
+
function placeRuntimeTag(template, runtimeScript) {
|
|
80
|
+
const firstScript = template.indexOf("<script"), doctype = template.search(/<!doctype\b/i);
|
|
81
|
+
if (firstScript !== -1 && (doctype === -1 || firstScript > doctype))
|
|
82
|
+
return template.slice(0, firstScript) + runtimeScript + `
|
|
83
|
+
` + template.slice(firstScript);
|
|
84
|
+
const headOpen = /<head\b[^>]*>/i.exec(template);
|
|
85
|
+
if (headOpen) {
|
|
86
|
+
const at = headOpen.index + headOpen[0].length;
|
|
87
|
+
return `${template.slice(0, at)}
|
|
88
|
+
${runtimeScript}${template.slice(at)}`;
|
|
107
89
|
}
|
|
108
|
-
const { getCachedSignalsRuntime } = await import("./caching.js"), runtimeScript = `<script data-stx-scoped data-stx-runtime>${await getCachedSignalsRuntime(options.debug)}</script>`, firstScriptInDoc = template.indexOf("<script");
|
|
109
|
-
if (firstScriptInDoc !== -1)
|
|
110
|
-
return template.slice(0, firstScriptInDoc) + runtimeScript + `
|
|
111
|
-
` + template.slice(firstScriptInDoc);
|
|
112
90
|
if (template.includes("</head>")) {
|
|
113
91
|
const idx = template.indexOf("</head>");
|
|
114
92
|
return template.slice(0, idx) + runtimeScript + `
|
|
115
93
|
` + template.slice(idx);
|
|
116
94
|
}
|
|
117
|
-
if (template
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
95
|
+
if (findBodyOpenTag(template))
|
|
96
|
+
return replaceBodyOpenTag(template, (tag) => `${tag}
|
|
97
|
+
${runtimeScript}`);
|
|
98
|
+
return `${runtimeScript}
|
|
99
|
+
${template}`;
|
|
100
|
+
}
|
|
101
|
+
export async function injectSignalsRuntime(template, options) {
|
|
102
|
+
if (template.includes("data-stx-runtime") || template.includes("window.stx.state") || template.includes("STX Signals Runtime"))
|
|
103
|
+
return template;
|
|
104
|
+
if (options.buildMode === "serve")
|
|
105
|
+
return placeRuntimeTag(template, '<script data-stx-runtime src="/_stx/runtime.js"></script>');
|
|
106
|
+
if (options.buildMode === "compile")
|
|
107
|
+
return placeRuntimeTag(template, '<script data-stx-runtime src="/__stx/runtime.__STX_HASH__.js"></script>');
|
|
108
|
+
const { getCachedSignalsRuntime } = await import("./caching.js"), runtime = await getCachedSignalsRuntime(options.debug);
|
|
109
|
+
return placeRuntimeTag(template, `<script data-stx-scoped data-stx-runtime>${runtime}</script>`);
|
|
124
110
|
}
|
|
125
111
|
export async function injectRouterScript(template, options) {
|
|
126
112
|
if (!template.includes("</body>"))
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Script validation module for STX templates.
|
|
3
|
-
*
|
|
4
|
-
* Validates client-side script content against prohibited DOM API patterns,
|
|
5
|
-
* enforcing the use of STX composables and Vue-style alternatives instead
|
|
6
|
-
* of raw browser APIs.
|
|
7
|
-
*/
|
|
8
1
|
import type { StrictModeConfig } from './types';
|
|
9
2
|
/**
|
|
10
3
|
* Prohibited DOM API patterns in client scripts.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripCommentsAndLiterals } from "./strip-literals.js";
|
|
1
2
|
export const PROHIBITED_DOM_PATTERNS = [
|
|
2
3
|
{
|
|
3
4
|
pattern: /document\.getElementById\s*\(/g,
|
|
@@ -126,21 +127,22 @@ export const PROHIBITED_DOM_PATTERNS = [
|
|
|
126
127
|
}
|
|
127
128
|
];
|
|
128
129
|
export function validateClientScript(content, filePath, strict, originFilePath) {
|
|
129
|
-
const strictConfig = typeof strict === "boolean" ? { enabled: strict, failOnViolation: strict } : strict ?? { enabled: !1 }, allowPatterns = strictConfig.allowPatterns ?? [], errors = [];
|
|
130
|
+
const strictConfig = typeof strict === "boolean" ? { enabled: strict, failOnViolation: strict } : strict ?? { enabled: !1 }, allowPatterns = strictConfig.allowPatterns ?? [], errors = [], scannable = stripCommentsAndLiterals(content.replace(/\{\{--[\s\S]*?--\}\}/g, (match) => match.replace(/[^\n]/g, " ")));
|
|
130
131
|
for (const { pattern, message, suggestion } of PROHIBITED_DOM_PATTERNS) {
|
|
131
132
|
if (allowPatterns.some((allowed) => /\w/.test(allowed) && message.includes(allowed)))
|
|
132
133
|
continue;
|
|
133
134
|
pattern.lastIndex = 0;
|
|
134
|
-
const matches =
|
|
135
|
+
const matches = scannable.match(pattern);
|
|
135
136
|
if (matches && matches.length > 0) {
|
|
136
|
-
const lines =
|
|
137
|
+
const lines = scannable.split(`
|
|
138
|
+
`), sourceLines = content.split(`
|
|
137
139
|
`), lineNumbers = [];
|
|
138
140
|
lines.forEach((line, index) => {
|
|
139
141
|
pattern.lastIndex = 0;
|
|
140
142
|
if (pattern.test(line))
|
|
141
143
|
lineNumbers.push(index + 1);
|
|
142
144
|
});
|
|
143
|
-
const locationInfo = lineNumbers.length > 0 ? ` (script line${lineNumbers.length > 1 ? "s" : ""} ${lineNumbers.join(", ")})` : "", quoted = lineNumbers.slice(0, 3).map((n) => ` ${n} | ${(
|
|
145
|
+
const locationInfo = lineNumbers.length > 0 ? ` (script line${lineNumbers.length > 1 ? "s" : ""} ${lineNumbers.join(", ")})` : "", quoted = lineNumbers.slice(0, 3).map((n) => ` ${n} | ${(sourceLines[n - 1] ?? "").trim().slice(0, 100)}`).join(`
|
|
144
146
|
`), snippet = quoted ? `
|
|
145
147
|
${quoted}` : "";
|
|
146
148
|
errors.push(` \u2717 ${message}${locationInfo}
|
package/dist/signals-api.d.ts
CHANGED
|
@@ -43,6 +43,24 @@
|
|
|
43
43
|
*
|
|
44
44
|
* @module signals-api
|
|
45
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* ## The canonical set (stacksjs/stx#1885 asks 3 and 4)
|
|
48
|
+
*
|
|
49
|
+
* `state` / `derived` / `effect`, exported here, are the primitives to reach
|
|
50
|
+
* for. They are what the client runtime puts on `window.stx`, what a
|
|
51
|
+
* `<script client>` block sees as bare identifiers, and what stx's own
|
|
52
|
+
* composables and stores are written against — the only set that reads the
|
|
53
|
+
* same in a module, in a component, and in a template.
|
|
54
|
+
*
|
|
55
|
+
* `reactivity.ts` exports a Vue-shaped family beside them — `ref`, `reactive`,
|
|
56
|
+
* `computed`, `watch`, `watchEffect` — from the same package entry. Both are
|
|
57
|
+
* backed by one dependency tracker now (`reactive-tracking.ts`), so mixing
|
|
58
|
+
* them works; four of the six pairings used to be silently inert. They are
|
|
59
|
+
* kept for code ported from Vue, not recommended for new code.
|
|
60
|
+
*
|
|
61
|
+
* Note `ref()` returns a `.value` object while `state()` returns a callable
|
|
62
|
+
* signal, so the two are not interchangeable at the call site.
|
|
63
|
+
*/
|
|
46
64
|
/**
|
|
47
65
|
* A reactive state signal.
|
|
48
66
|
*
|
package/dist/signals-api.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
enqueueEffect,
|
|
3
|
+
getActiveSubscriber,
|
|
4
|
+
isBatching,
|
|
5
|
+
restoreActiveSubscriber,
|
|
6
|
+
runBatched,
|
|
7
|
+
setActiveSubscriber,
|
|
8
|
+
withoutTracking
|
|
9
|
+
} from "./reactive-tracking.js";
|
|
10
|
+
const mountCallbacks = [], destroyCallbacks = [];
|
|
5
11
|
export function state(initialValue) {
|
|
6
12
|
let value = initialValue;
|
|
7
13
|
const subscribers = new Set, effects = new Set, signal = () => {
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
const subscriber = getActiveSubscriber();
|
|
15
|
+
if (subscriber)
|
|
16
|
+
effects.add(subscriber);
|
|
10
17
|
return value;
|
|
11
18
|
};
|
|
12
19
|
signal.set = (newValue) => {
|
|
@@ -14,8 +21,8 @@ export function state(initialValue) {
|
|
|
14
21
|
const prev = value;
|
|
15
22
|
value = newValue;
|
|
16
23
|
subscribers.forEach((cb) => cb(value, prev));
|
|
17
|
-
if (isBatching)
|
|
18
|
-
effects.forEach((effect) =>
|
|
24
|
+
if (isBatching())
|
|
25
|
+
effects.forEach((effect) => enqueueEffect(effect));
|
|
19
26
|
else
|
|
20
27
|
effects.forEach((effect) => effect());
|
|
21
28
|
}
|
|
@@ -40,23 +47,21 @@ export function derived(compute) {
|
|
|
40
47
|
const effects = new Set, markDirty = () => {
|
|
41
48
|
if (!isDirty) {
|
|
42
49
|
isDirty = !0;
|
|
43
|
-
if (isBatching)
|
|
44
|
-
effects.forEach((effect) =>
|
|
50
|
+
if (isBatching())
|
|
51
|
+
effects.forEach((effect) => enqueueEffect(effect));
|
|
45
52
|
else
|
|
46
53
|
effects.forEach((effect) => effect());
|
|
47
54
|
}
|
|
48
55
|
}, signal = () => {
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
const subscriber = getActiveSubscriber();
|
|
57
|
+
if (subscriber)
|
|
58
|
+
effects.add(subscriber);
|
|
51
59
|
if (isDirty) {
|
|
52
|
-
const prevEffect =
|
|
53
|
-
activeEffect = markDirty;
|
|
54
|
-
effectStack.push(markDirty);
|
|
60
|
+
const prevEffect = setActiveSubscriber(markDirty);
|
|
55
61
|
try {
|
|
56
62
|
cachedValue = compute();
|
|
57
63
|
} finally {
|
|
58
|
-
|
|
59
|
-
activeEffect = prevEffect;
|
|
64
|
+
restoreActiveSubscriber(prevEffect);
|
|
60
65
|
}
|
|
61
66
|
isDirty = !1;
|
|
62
67
|
}
|
|
@@ -78,15 +83,12 @@ export function effect(fn, options = {}) {
|
|
|
78
83
|
cleanup();
|
|
79
84
|
cleanup = void 0;
|
|
80
85
|
}
|
|
81
|
-
const prevEffect =
|
|
82
|
-
activeEffect = runEffect;
|
|
83
|
-
effectStack.push(runEffect);
|
|
86
|
+
const prevEffect = setActiveSubscriber(runEffect);
|
|
84
87
|
try {
|
|
85
88
|
const result = fn();
|
|
86
89
|
cleanup = typeof result === "function" ? result : void 0;
|
|
87
90
|
} finally {
|
|
88
|
-
|
|
89
|
-
activeEffect = prevEffect;
|
|
91
|
+
restoreActiveSubscriber(prevEffect);
|
|
90
92
|
}
|
|
91
93
|
};
|
|
92
94
|
if (options.immediate !== !1)
|
|
@@ -98,18 +100,7 @@ export function effect(fn, options = {}) {
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
export function batch(fn) {
|
|
101
|
-
|
|
102
|
-
fn();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
isBatching = !0;
|
|
106
|
-
try {
|
|
107
|
-
fn();
|
|
108
|
-
} finally {
|
|
109
|
-
isBatching = !1;
|
|
110
|
-
pendingEffects.forEach((effect) => effect());
|
|
111
|
-
pendingEffects.clear();
|
|
112
|
-
}
|
|
103
|
+
runBatched(fn);
|
|
113
104
|
}
|
|
114
105
|
export function onMount(callback) {
|
|
115
106
|
mountCallbacks.push(callback);
|
|
@@ -149,13 +140,7 @@ export function untrack(value) {
|
|
|
149
140
|
return value;
|
|
150
141
|
}
|
|
151
142
|
export function peek(fn) {
|
|
152
|
-
|
|
153
|
-
activeEffect = null;
|
|
154
|
-
try {
|
|
155
|
-
return fn();
|
|
156
|
-
} finally {
|
|
157
|
-
activeEffect = prevEffect;
|
|
158
|
-
}
|
|
143
|
+
return withoutTracking(fn);
|
|
159
144
|
}
|
|
160
145
|
export function useOptimistic(base, reducer) {
|
|
161
146
|
const readBase = typeof base === "function" ? base : () => base, pending = state([]), optimistic = derived(() => pending().reduce((acc, entry) => reducer(acc, entry.action), readBase()));
|