@cfdez11/vex 0.8.3 → 0.9.1
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/bin/vex.js +3 -0
- package/dist/client/services/cache.js +1 -0
- package/dist/client/services/hmr-client.js +1 -0
- package/dist/client/services/html.js +1 -0
- package/dist/client/services/hydrate-client-components.js +1 -0
- package/dist/client/services/hydrate.js +1 -0
- package/dist/client/services/index.js +1 -0
- package/dist/client/services/navigation/create-layouts.js +1 -0
- package/dist/client/services/navigation/create-navigation.js +1 -0
- package/dist/client/services/navigation/index.js +1 -0
- package/dist/client/services/navigation/link-interceptor.js +1 -0
- package/dist/client/services/navigation/metadata.js +1 -0
- package/dist/client/services/navigation/navigate.js +1 -0
- package/dist/client/services/navigation/prefetch.js +1 -0
- package/dist/client/services/navigation/render-page.js +1 -0
- package/dist/client/services/navigation/render-ssr.js +1 -0
- package/dist/client/services/navigation/router.js +1 -0
- package/dist/client/services/navigation/use-query-params.js +1 -0
- package/dist/client/services/navigation/use-route-params.js +1 -0
- package/dist/client/services/navigation.js +1 -0
- package/dist/client/services/reactive.js +1 -0
- package/dist/server/build-static.js +6 -0
- package/dist/server/index.js +4 -0
- package/dist/server/prebuild.js +1 -0
- package/dist/server/utils/cache.js +1 -0
- package/dist/server/utils/component-processor.js +68 -0
- package/dist/server/utils/data-cache.js +1 -0
- package/dist/server/utils/esbuild-plugin.js +1 -0
- package/dist/server/utils/files.js +28 -0
- package/dist/server/utils/hmr.js +1 -0
- package/dist/server/utils/router.js +11 -0
- package/dist/server/utils/streaming.js +1 -0
- package/dist/server/utils/template.js +1 -0
- package/package.json +8 -7
- package/bin/vex.js +0 -69
- package/client/favicon.ico +0 -0
- package/client/services/cache.js +0 -55
- package/client/services/hmr-client.js +0 -22
- package/client/services/html.js +0 -378
- package/client/services/hydrate-client-components.js +0 -97
- package/client/services/hydrate.js +0 -25
- package/client/services/index.js +0 -9
- package/client/services/navigation/create-layouts.js +0 -172
- package/client/services/navigation/create-navigation.js +0 -103
- package/client/services/navigation/index.js +0 -8
- package/client/services/navigation/link-interceptor.js +0 -39
- package/client/services/navigation/metadata.js +0 -23
- package/client/services/navigation/navigate.js +0 -64
- package/client/services/navigation/prefetch.js +0 -43
- package/client/services/navigation/render-page.js +0 -45
- package/client/services/navigation/render-ssr.js +0 -157
- package/client/services/navigation/router.js +0 -48
- package/client/services/navigation/use-query-params.js +0 -225
- package/client/services/navigation/use-route-params.js +0 -76
- package/client/services/navigation.js +0 -6
- package/client/services/reactive.js +0 -247
- package/server/build-static.js +0 -138
- package/server/index.js +0 -135
- package/server/prebuild.js +0 -13
- package/server/utils/cache.js +0 -89
- package/server/utils/component-processor.js +0 -1631
- package/server/utils/data-cache.js +0 -62
- package/server/utils/delay.js +0 -1
- package/server/utils/esbuild-plugin.js +0 -110
- package/server/utils/files.js +0 -845
- package/server/utils/hmr.js +0 -21
- package/server/utils/router.js +0 -375
- package/server/utils/streaming.js +0 -324
- package/server/utils/template.js +0 -274
- /package/{client → dist/client}/app.webmanifest +0 -0
- /package/{server → dist/server}/root.html +0 -0
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses the URL search string into a plain object.
|
|
3
|
-
*
|
|
4
|
-
* This function represents the **raw URL state**:
|
|
5
|
-
* - Keys and values are always strings
|
|
6
|
-
* - No defaults are applied
|
|
7
|
-
* - No parsing or validation is performed
|
|
8
|
-
*
|
|
9
|
-
* Example:
|
|
10
|
-
* "?page=2&tags=js,spa" → { page: "2", tags: "js,spa" }
|
|
11
|
-
*
|
|
12
|
-
* @param {string} search - window.location.search
|
|
13
|
-
* @returns {Object.<string, string>}
|
|
14
|
-
*/
|
|
15
|
-
function parseRawQuery(search) {
|
|
16
|
-
const out = {};
|
|
17
|
-
const qs = new URLSearchParams(search);
|
|
18
|
-
|
|
19
|
-
for (const [k, v] of qs.entries()) {
|
|
20
|
-
out[k] = v;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Builds a query string from a raw params object.
|
|
28
|
-
*
|
|
29
|
-
* - Values are stringified
|
|
30
|
-
* - `null` and `undefined` values are omitted
|
|
31
|
-
*
|
|
32
|
-
* Example:
|
|
33
|
-
* { page: "2", tags: "js,spa" } → "page=2&tags=js,spa"
|
|
34
|
-
*
|
|
35
|
-
* @param {Object.<string, any>} raw
|
|
36
|
-
* @returns {string} Query string without leading "?"
|
|
37
|
-
*/
|
|
38
|
-
function buildQueryString(raw) {
|
|
39
|
-
const qs = new URLSearchParams();
|
|
40
|
-
|
|
41
|
-
for (const k in raw) {
|
|
42
|
-
if (raw[k] != null) {
|
|
43
|
-
qs.set(k, String(raw[k]));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return qs.toString();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Manages URL query parameters as application state.
|
|
52
|
-
*
|
|
53
|
-
* This hook provides:
|
|
54
|
-
* - Parsing via schema (similar to nuqs)
|
|
55
|
-
* - Default values
|
|
56
|
-
* - URL synchronization (push / replace)
|
|
57
|
-
* - Back/forward navigation support
|
|
58
|
-
*
|
|
59
|
-
* The URL remains the single source of truth.
|
|
60
|
-
*
|
|
61
|
-
* @param {Object} options
|
|
62
|
-
* @param {Object.<string, Function>} [options.schema]
|
|
63
|
-
* Map of query param parsers.
|
|
64
|
-
* Each function receives the raw string value (or undefined)
|
|
65
|
-
* and must return a parsed value with a default fallback.
|
|
66
|
-
*
|
|
67
|
-
* @param {boolean} [options.replace=false]
|
|
68
|
-
* If true, uses history.replaceState instead of pushState.
|
|
69
|
-
*
|
|
70
|
-
* @param {boolean} [options.listen=true]
|
|
71
|
-
* If true, listens to popstate events to keep state in sync.
|
|
72
|
-
*
|
|
73
|
-
* @returns {Object}
|
|
74
|
-
*/
|
|
75
|
-
export function useQueryParams(options = {}) {
|
|
76
|
-
const { schema = {}, replace = false, listen = true } = options;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Compute default values by executing schema parsers
|
|
80
|
-
* with an undefined input.
|
|
81
|
-
*/
|
|
82
|
-
const defaults = {};
|
|
83
|
-
for (const key in schema) {
|
|
84
|
-
defaults[key] = schema[key](undefined);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Raw query params as strings.
|
|
89
|
-
* This mirrors exactly what exists in the URL.
|
|
90
|
-
*/
|
|
91
|
-
let raw = parseRawQuery(window.location.search);
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Parses raw query params using the provided schema.
|
|
95
|
-
*
|
|
96
|
-
* - Schema keys are always present (defaults applied)
|
|
97
|
-
* - Unknown params are passed through as strings
|
|
98
|
-
*
|
|
99
|
-
* @param {Object.<string, string>} raw
|
|
100
|
-
* @returns {Object} Parsed params ready for application use
|
|
101
|
-
*/
|
|
102
|
-
function parseWithSchema(raw) {
|
|
103
|
-
const parsed = {};
|
|
104
|
-
|
|
105
|
-
// Apply schema parsing and defaults
|
|
106
|
-
for (const key in schema) {
|
|
107
|
-
const parser = schema[key];
|
|
108
|
-
parsed[key] = parser(raw[key]);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Preserve non-declared query params
|
|
112
|
-
for (const key in raw) {
|
|
113
|
-
if (!(key in parsed)) {
|
|
114
|
-
parsed[key] = raw[key];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return parsed;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Serializes application-level values into
|
|
123
|
-
* raw URL-safe string values.
|
|
124
|
-
*
|
|
125
|
-
* - Arrays are joined by comma
|
|
126
|
-
* - null / undefined values are omitted
|
|
127
|
-
*
|
|
128
|
-
* @param {Object} next
|
|
129
|
-
* @returns {Object.<string, string>}
|
|
130
|
-
*/
|
|
131
|
-
function serializeWithSchema(next) {
|
|
132
|
-
const out = {};
|
|
133
|
-
|
|
134
|
-
for (const key in next) {
|
|
135
|
-
const value = next[key];
|
|
136
|
-
|
|
137
|
-
if (Array.isArray(value)) {
|
|
138
|
-
out[key] = value.join(",");
|
|
139
|
-
} else if (value != null) {
|
|
140
|
-
out[key] = String(value);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return out;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Synchronizes the internal raw state with the browser URL.
|
|
149
|
-
*
|
|
150
|
-
* @param {Object.<string, string>} nextRaw
|
|
151
|
-
*/
|
|
152
|
-
function sync(nextRaw) {
|
|
153
|
-
raw = nextRaw;
|
|
154
|
-
|
|
155
|
-
const qs = buildQueryString(raw);
|
|
156
|
-
const url =
|
|
157
|
-
window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
|
|
158
|
-
|
|
159
|
-
history[replace ? "replaceState" : "pushState"](null, "", url);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Updates one or more query params.
|
|
164
|
-
*
|
|
165
|
-
* Values are serialized and merged with existing params.
|
|
166
|
-
*
|
|
167
|
-
* @param {Object} next
|
|
168
|
-
*/
|
|
169
|
-
function set(next) {
|
|
170
|
-
const serialized = serializeWithSchema(next);
|
|
171
|
-
sync({ ...raw, ...serialized });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Removes one or more query params.
|
|
176
|
-
*
|
|
177
|
-
* @param {...string} keys
|
|
178
|
-
*/
|
|
179
|
-
function remove(...keys) {
|
|
180
|
-
const next = { ...raw };
|
|
181
|
-
keys.forEach((k) => delete next[k]);
|
|
182
|
-
sync(next);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Removes all query params from the URL.
|
|
187
|
-
*/
|
|
188
|
-
function reset() {
|
|
189
|
-
sync({});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Keeps internal state in sync with browser
|
|
194
|
-
* back/forward navigation.
|
|
195
|
-
*/
|
|
196
|
-
if (listen) {
|
|
197
|
-
window.addEventListener("popstate", () => {
|
|
198
|
-
raw = parseRawQuery(window.location.search);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
/**
|
|
204
|
-
* Parsed query params.
|
|
205
|
-
*
|
|
206
|
-
* This is a getter, so values are always derived
|
|
207
|
-
* from the current raw URL state.
|
|
208
|
-
*/
|
|
209
|
-
get params() {
|
|
210
|
-
return parseWithSchema(raw);
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Raw query params as strings.
|
|
215
|
-
* Exposed mainly for debugging or tooling.
|
|
216
|
-
*/
|
|
217
|
-
get raw() {
|
|
218
|
-
return { ...raw };
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
set,
|
|
222
|
-
remove,
|
|
223
|
-
reset,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { reactive } from "../reactive.js";
|
|
2
|
-
import { routes } from "../_routes.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Reactive store holding the current route params.
|
|
6
|
-
* This object is updated whenever the URL changes.
|
|
7
|
-
*/
|
|
8
|
-
const routeParams = reactive({});
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Extracts dynamic parameters from a pathname based on route definitions.
|
|
12
|
-
*
|
|
13
|
-
* Supported syntax:
|
|
14
|
-
* /posts/:id
|
|
15
|
-
* /users/:userId/:postId
|
|
16
|
-
*
|
|
17
|
-
* @param {string} pathname - URL pathname (no query, no hash)
|
|
18
|
-
* @returns {Object} Extracted params
|
|
19
|
-
*/
|
|
20
|
-
function extractParams(pathname) {
|
|
21
|
-
const pathParts = pathname.split("/").filter(Boolean);
|
|
22
|
-
|
|
23
|
-
for (const route of routes) {
|
|
24
|
-
const routeParts = route.path.split("/").filter(Boolean);
|
|
25
|
-
if (routeParts.length !== pathParts.length) continue;
|
|
26
|
-
|
|
27
|
-
const params = {};
|
|
28
|
-
let match = true;
|
|
29
|
-
|
|
30
|
-
for (let i = 0; i < routeParts.length; i++) {
|
|
31
|
-
const routePart = routeParts[i];
|
|
32
|
-
const pathPart = pathParts[i];
|
|
33
|
-
|
|
34
|
-
if (routePart.startsWith(":")) {
|
|
35
|
-
params[routePart.slice(1)] = pathPart;
|
|
36
|
-
} else if (routePart !== pathPart) {
|
|
37
|
-
match = false;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (match) return params;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return {};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Updates the reactive route params based on the current URL.
|
|
50
|
-
* @param {string} [path=window.location.pathname] - URL pathname to extract params from
|
|
51
|
-
* @example
|
|
52
|
-
* updateRouteParams(); // Updates params from current URL
|
|
53
|
-
* updateRouteParams("/posts/42"); // Updates params from given path
|
|
54
|
-
*/
|
|
55
|
-
export function updateRouteParams(path = window.location.pathname) {
|
|
56
|
-
const newParams = extractParams(path);
|
|
57
|
-
|
|
58
|
-
Object.keys(routeParams).forEach((k) => delete routeParams[k]);
|
|
59
|
-
Object.assign(routeParams, newParams);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Composition function returning reactive route params.
|
|
64
|
-
*
|
|
65
|
-
* @returns {Object} Reactive route params
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* const params = useRouteParams();
|
|
69
|
-
*
|
|
70
|
-
* effect(() => {
|
|
71
|
-
* console.log(params.id);
|
|
72
|
-
* });
|
|
73
|
-
*/
|
|
74
|
-
export function useRouteParams() {
|
|
75
|
-
return routeParams;
|
|
76
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// Barrel file for vex/navigation imports.
|
|
2
|
-
// Components import { useRouteParams } from "vex/navigation" which esbuild
|
|
3
|
-
// rewrites to /_vexjs/services/navigation.js (external). All re-exports go
|
|
4
|
-
// through navigation/index.js so the browser module cache ensures the same
|
|
5
|
-
// runtime instance is shared with the index.js bootstrap.
|
|
6
|
-
export { useRouteParams, useQueryParams, navigate } from "./navigation/index.js";
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tracks the currently executing effect function for dependency collection.
|
|
3
|
-
* This global variable allows the reactive system to know which effect
|
|
4
|
-
* is currently running and should be notified of reactive property changes.
|
|
5
|
-
*/
|
|
6
|
-
let activeEffect = null;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Adapts primitive values (string, number, boolean, null) to work with the reactive system.
|
|
10
|
-
* Wraps primitive values in an object with a 'value' property and marks them as primitive.
|
|
11
|
-
* Objects are returned as-is since they can already have reactive properties.
|
|
12
|
-
*
|
|
13
|
-
* @param {any} input - The value to adapt for reactivity
|
|
14
|
-
* @returns {object} Object with either the original object or wrapped primitive
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* adaptPrimitiveValue(42) // → { value: 42, __isPrimitive: true }
|
|
18
|
-
* adaptPrimitiveValue("hello") // → { value: "hello", __isPrimitive: true }
|
|
19
|
-
* adaptPrimitiveValue({x: 1}) // → {x: 1} (unchanged)
|
|
20
|
-
*/
|
|
21
|
-
function adaptPrimitiveValue(input) {
|
|
22
|
-
if (input === null || typeof input !== "object") {
|
|
23
|
-
return { value: input, __isPrimitive: true };
|
|
24
|
-
}
|
|
25
|
-
return input;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Creates a reactive proxy that automatically tracks dependencies and triggers effects.
|
|
30
|
-
* The core of the reactivity system - makes any object or primitive reactive.
|
|
31
|
-
*
|
|
32
|
-
* Features:
|
|
33
|
-
* - Automatic dependency tracking when properties are accessed during effects
|
|
34
|
-
* - Automatic effect triggering when properties change
|
|
35
|
-
* - Support for primitive values through value wrapping
|
|
36
|
-
* - Memory cleanup to prevent leaks
|
|
37
|
-
*
|
|
38
|
-
* @param {any} obj - Object or primitive to make reactive
|
|
39
|
-
* @returns {Proxy} Reactive proxy that tracks dependencies and triggers effects
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* With objects
|
|
43
|
-
* const state = reactive({ count: 0, name: "John" });
|
|
44
|
-
* state.count++; // Triggers any effects that used state.count
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* With primitives
|
|
48
|
-
* const counter = reactive(0);
|
|
49
|
-
* counter.value++; // Triggers effects that used counter.value
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* In components
|
|
53
|
-
* class Counter {
|
|
54
|
-
* count = reactive(0);
|
|
55
|
-
*
|
|
56
|
-
* increment() {
|
|
57
|
-
* this.count.value++; // Automatically re-renders component
|
|
58
|
-
* }
|
|
59
|
-
*
|
|
60
|
-
* effect(() => this.render());
|
|
61
|
-
*
|
|
62
|
-
* render() {
|
|
63
|
-
* return html`<button @click="${this.increment}">Count: ${this.count.value}</button>`;
|
|
64
|
-
* }
|
|
65
|
-
* }
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* In components with Component base class doesn't require manual effect()
|
|
69
|
-
* class Counter extends Component {
|
|
70
|
-
* count = reactive(0);
|
|
71
|
-
*
|
|
72
|
-
* increment() {
|
|
73
|
-
* this.count.value++; // Automatically re-renders component
|
|
74
|
-
* }
|
|
75
|
-
*
|
|
76
|
-
* render() {
|
|
77
|
-
* return html`<button @click="${this.increment}">Count: ${this.count.value}</button>`;
|
|
78
|
-
* }
|
|
79
|
-
* }
|
|
80
|
-
*
|
|
81
|
-
*/
|
|
82
|
-
export function reactive(obj) {
|
|
83
|
-
obj = adaptPrimitiveValue(obj);
|
|
84
|
-
|
|
85
|
-
// Map to store dependencies for each property
|
|
86
|
-
const depsMap = new Map();
|
|
87
|
-
|
|
88
|
-
const proxy = new Proxy(obj, {
|
|
89
|
-
get(target, prop) {
|
|
90
|
-
// Handle primitive value conversion (for template literals, etc.)
|
|
91
|
-
if (target.__isPrimitive && prop === Symbol.toPrimitive) {
|
|
92
|
-
// Track "value" dependency so effects using ${counter} re-run on change
|
|
93
|
-
if (activeEffect) {
|
|
94
|
-
if (!depsMap.has("value")) depsMap.set("value", new Set());
|
|
95
|
-
const depSet = depsMap.get("value");
|
|
96
|
-
depSet.add(activeEffect);
|
|
97
|
-
if (!activeEffect.deps) activeEffect.deps = [];
|
|
98
|
-
activeEffect.deps.push(depSet);
|
|
99
|
-
}
|
|
100
|
-
return () => target.value;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Use 'value' key for primitives, actual property name for objects
|
|
104
|
-
const key = target.__isPrimitive ? "value" : prop;
|
|
105
|
-
|
|
106
|
-
// Dependency tracking: if an effect is running, register it as dependent on this property
|
|
107
|
-
if (activeEffect) {
|
|
108
|
-
if (!depsMap.has(key)) depsMap.set(key, new Set());
|
|
109
|
-
const depSet = depsMap.get(key);
|
|
110
|
-
depSet.add(activeEffect);
|
|
111
|
-
|
|
112
|
-
// Track dependencies on the effect for cleanup
|
|
113
|
-
if (!activeEffect.deps) activeEffect.deps = [];
|
|
114
|
-
activeEffect.deps.push(depSet);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return target[key];
|
|
118
|
-
},
|
|
119
|
-
set(target, prop, value) {
|
|
120
|
-
const key = target.__isPrimitive ? "value" : prop;
|
|
121
|
-
target[key] = value;
|
|
122
|
-
|
|
123
|
-
// Trigger all effects that depend on this property
|
|
124
|
-
if (depsMap.has(key)) {
|
|
125
|
-
depsMap.get(key).forEach((effect) => effect());
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return true;
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
return proxy;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Creates a reactive effect that automatically re-runs when its dependencies change.
|
|
137
|
-
* This is the foundation of the reactivity system - it tracks which reactive properties
|
|
138
|
-
* are accessed during execution and re-runs the function when any of them change.
|
|
139
|
-
*
|
|
140
|
-
* @param {function} fn - Function to run reactively. Will re-execute when dependencies change
|
|
141
|
-
* @returns {function} Cleanup function to stop the effect and remove all dependencies
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* Basic usage
|
|
145
|
-
* const count = reactive(0);
|
|
146
|
-
* const cleanup = effect(() => {
|
|
147
|
-
* console.log(`Count is: ${count.value}`); // Logs immediately and on changes
|
|
148
|
-
* });
|
|
149
|
-
*
|
|
150
|
-
* count.value++; // Logs: "Count is: 1"
|
|
151
|
-
* cleanup(); // Stops the effect
|
|
152
|
-
*/
|
|
153
|
-
export function effect(fn) {
|
|
154
|
-
const wrapped = () => {
|
|
155
|
-
activeEffect = wrapped;
|
|
156
|
-
fn();
|
|
157
|
-
activeEffect = null;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Run the effect immediately to collect initial dependencies
|
|
161
|
-
wrapped();
|
|
162
|
-
|
|
163
|
-
// Return cleanup function
|
|
164
|
-
return () => {
|
|
165
|
-
if (wrapped.deps) {
|
|
166
|
-
wrapped.deps.forEach((depSet) => depSet.delete(wrapped));
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Creates a computed reactive value that automatically updates when its dependencies change.
|
|
173
|
-
* @param {Function} getter
|
|
174
|
-
* @returns {{ value: Object }} Computed reactive value
|
|
175
|
-
*
|
|
176
|
-
* @example
|
|
177
|
-
* Basic usage
|
|
178
|
-
* const count = reactive(1);
|
|
179
|
-
* const doubleCount = computed(() => count.value * 2);
|
|
180
|
-
* console.log(doubleCount.value); // 2
|
|
181
|
-
* count.value = 3;
|
|
182
|
-
* console.log(doubleCount.value); // 6
|
|
183
|
-
*/
|
|
184
|
-
export function computed(getter) {
|
|
185
|
-
let value;
|
|
186
|
-
|
|
187
|
-
effect(() => {
|
|
188
|
-
value = getter();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
return new Proxy({}, {
|
|
192
|
-
get(_, prop) {
|
|
193
|
-
if (prop === Symbol.toPrimitive) {
|
|
194
|
-
return () => value;
|
|
195
|
-
}
|
|
196
|
-
if (prop === "value") {
|
|
197
|
-
return value;
|
|
198
|
-
}
|
|
199
|
-
// Delegate any other access (e.g. .map, .length) to the underlying value
|
|
200
|
-
const v = value?.[prop];
|
|
201
|
-
return typeof v === "function" ? v.bind(value) : v;
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Watches a reactive source and runs a callback when its value changes.
|
|
208
|
-
*
|
|
209
|
-
* @template T
|
|
210
|
-
* @param {() => T} source - A getter function returning the reactive value to watch.
|
|
211
|
-
* @param {(newValue: T, oldValue: T | undefined, onCleanup: (fn: () => void) => void) => void} callback
|
|
212
|
-
* @param {{ immediate?: boolean }} [options]
|
|
213
|
-
*/
|
|
214
|
-
export function watch(source, callback, options = {}) {
|
|
215
|
-
let oldValue;
|
|
216
|
-
let cleanupFn;
|
|
217
|
-
|
|
218
|
-
const onCleanup = (fn) => {
|
|
219
|
-
cleanupFn = fn;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const runner = () => {
|
|
223
|
-
const newValue = source();
|
|
224
|
-
|
|
225
|
-
// Skip first run if not immediate
|
|
226
|
-
if (oldValue === undefined && !options.immediate) {
|
|
227
|
-
oldValue = newValue;
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Avoid unnecessary executions
|
|
232
|
-
if (Object.is(newValue, oldValue)) return;
|
|
233
|
-
|
|
234
|
-
// Cleanup previous effect
|
|
235
|
-
if (cleanupFn) {
|
|
236
|
-
cleanupFn();
|
|
237
|
-
cleanupFn = null;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
callback(newValue, oldValue, onCleanup);
|
|
241
|
-
oldValue = newValue;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Track dependencies reactively
|
|
245
|
-
effect(runner);
|
|
246
|
-
}
|
|
247
|
-
|
package/server/build-static.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { build } from "./utils/component-processor.js";
|
|
5
|
-
import {
|
|
6
|
-
initializeDirectories,
|
|
7
|
-
CLIENT_DIR,
|
|
8
|
-
PROJECT_ROOT,
|
|
9
|
-
getRootTemplate,
|
|
10
|
-
generateComponentId,
|
|
11
|
-
USER_GENERATED_DIR,
|
|
12
|
-
} from "./utils/files.js";
|
|
13
|
-
|
|
14
|
-
const GENERATED_DIR = path.join(PROJECT_ROOT, ".vexjs");
|
|
15
|
-
const DIST_DIR = path.join(PROJECT_ROOT, "dist");
|
|
16
|
-
|
|
17
|
-
console.log("🔨 Starting static build...");
|
|
18
|
-
|
|
19
|
-
// Step 1: Prebuild (components + routes)
|
|
20
|
-
console.log("📁 Initializing directories...");
|
|
21
|
-
await initializeDirectories();
|
|
22
|
-
|
|
23
|
-
console.log("⚙️ Generating components and routes...");
|
|
24
|
-
const { serverRoutes } = await build();
|
|
25
|
-
|
|
26
|
-
// Step 2: Create dist/ structure (clean start)
|
|
27
|
-
console.log("🗂️ Creating dist/ structure...");
|
|
28
|
-
await fs.rm(DIST_DIR, { recursive: true, force: true });
|
|
29
|
-
await fs.mkdir(path.join(DIST_DIR, "_vexjs", "_components"), { recursive: true });
|
|
30
|
-
await fs.mkdir(path.join(DIST_DIR, "_vexjs", "user"), { recursive: true });
|
|
31
|
-
|
|
32
|
-
// Step 3: Generate dist/index.html shell
|
|
33
|
-
console.log("📄 Generating index.html shell...");
|
|
34
|
-
const rootTemplate = await getRootTemplate();
|
|
35
|
-
let shell = rootTemplate
|
|
36
|
-
.replace(/\{\{metadata\.title\}\}/g, "App")
|
|
37
|
-
.replace(/\{\{metadata\.description\}\}/g, "")
|
|
38
|
-
.replace(/\{\{props\.children\}\}/g, "");
|
|
39
|
-
|
|
40
|
-
const frameworkScripts = [
|
|
41
|
-
`<style>vex-root { display: contents; }</style>`,
|
|
42
|
-
`<script type="module" src="/_vexjs/services/index.js"></script>`,
|
|
43
|
-
`<script src="/_vexjs/services/hydrate-client-components.js"></script>`,
|
|
44
|
-
`<script src="/_vexjs/services/hydrate.js" id="hydrate-script"></script>`,
|
|
45
|
-
].join("\n ");
|
|
46
|
-
|
|
47
|
-
shell = shell.replace("</head>", ` ${frameworkScripts}\n</head>`);
|
|
48
|
-
await fs.writeFile(path.join(DIST_DIR, "index.html"), shell, "utf-8");
|
|
49
|
-
|
|
50
|
-
// Step 4: Copy static framework assets (favicon.ico, app.webmanifest) → dist/_vexjs/
|
|
51
|
-
// JS runtime files live in .vexjs/services/ (copied in step 5) — no need to
|
|
52
|
-
// copy CLIENT_DIR/services/ separately.
|
|
53
|
-
console.log("📦 Copying framework assets...");
|
|
54
|
-
for (const asset of ["favicon.ico", "app.webmanifest"]) {
|
|
55
|
-
try {
|
|
56
|
-
await fs.copyFile(
|
|
57
|
-
path.join(CLIENT_DIR, asset),
|
|
58
|
-
path.join(DIST_DIR, "_vexjs", asset)
|
|
59
|
-
);
|
|
60
|
-
} catch {
|
|
61
|
-
// asset not present — skip
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Step 5: Copy generated services → dist/_vexjs/services/
|
|
66
|
-
// .vexjs/services/ already contains both framework JS (copied by initializeDirectories)
|
|
67
|
-
// and generated files (_routes.js). One copy covers everything.
|
|
68
|
-
console.log("📦 Copying services...");
|
|
69
|
-
await fs.cp(
|
|
70
|
-
path.join(GENERATED_DIR, "services"),
|
|
71
|
-
path.join(DIST_DIR, "_vexjs", "services"),
|
|
72
|
-
{ recursive: true }
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// Step 6: Copy generated component bundles → dist/_vexjs/_components/
|
|
76
|
-
console.log("📦 Copying component bundles...");
|
|
77
|
-
await fs.cp(
|
|
78
|
-
path.join(GENERATED_DIR, "_components"),
|
|
79
|
-
path.join(DIST_DIR, "_vexjs", "_components"),
|
|
80
|
-
{ recursive: true }
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
// Step 7: Copy pre-bundled user JS files → dist/_vexjs/user/
|
|
84
|
-
// build() already ran esbuild on every user .js file → USER_GENERATED_DIR.
|
|
85
|
-
// npm packages are bundled inline; vex/*, @/*, relative imports stay external.
|
|
86
|
-
console.log("📦 Copying pre-bundled user JS files...");
|
|
87
|
-
try {
|
|
88
|
-
await fs.cp(USER_GENERATED_DIR, path.join(DIST_DIR, "_vexjs", "user"), { recursive: true });
|
|
89
|
-
} catch {
|
|
90
|
-
// no user JS files — that's fine
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Step 8: Copy public/ → dist/
|
|
94
|
-
console.log("📦 Copying public assets...");
|
|
95
|
-
const publicDir = path.join(PROJECT_ROOT, "public");
|
|
96
|
-
try {
|
|
97
|
-
await fs.cp(publicDir, DIST_DIR, { recursive: true });
|
|
98
|
-
} catch {
|
|
99
|
-
// no public/ directory — that's fine
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Step 9: Copy pre-rendered SSG pages
|
|
103
|
-
const CACHE_DIR = path.join(GENERATED_DIR, "_cache");
|
|
104
|
-
const ssgRoutes = serverRoutes.filter(
|
|
105
|
-
(r) => r.meta.revalidate === "never" || r.meta.revalidate === false
|
|
106
|
-
);
|
|
107
|
-
if (ssgRoutes.length > 0) {
|
|
108
|
-
console.log("📄 Copying pre-rendered SSG pages...");
|
|
109
|
-
for (const route of ssgRoutes) {
|
|
110
|
-
const cacheFile = path.join(CACHE_DIR, `${generateComponentId(route.serverPath)}.html`);
|
|
111
|
-
try {
|
|
112
|
-
const html = await fs.readFile(cacheFile, "utf-8");
|
|
113
|
-
const routeSegment = route.serverPath === "/" ? "" : route.serverPath;
|
|
114
|
-
const destPath = path.join(DIST_DIR, routeSegment, "index.html");
|
|
115
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
116
|
-
await fs.writeFile(destPath, html, "utf-8");
|
|
117
|
-
console.log(` ✓ ${route.serverPath}`);
|
|
118
|
-
} catch {
|
|
119
|
-
console.warn(` ✗ ${route.serverPath} (no cached HTML found)`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Step 10: Report SSR-only routes (skipped in static build)
|
|
125
|
-
const ssrOnlyRoutes = serverRoutes.filter((r) => r.meta.ssr);
|
|
126
|
-
if (ssrOnlyRoutes.length > 0) {
|
|
127
|
-
console.warn("\n⚠️ The following routes require a server and were NOT included in the static build:");
|
|
128
|
-
for (const r of ssrOnlyRoutes) {
|
|
129
|
-
console.warn(` ${r.path} (SSR)`);
|
|
130
|
-
}
|
|
131
|
-
console.warn(" These routes will show a 404 in the static build.\n");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log("✅ Static build complete! Output: dist/");
|
|
135
|
-
console.log("\nTo serve locally: npx serve dist");
|
|
136
|
-
console.log("Static host note: configure your host to serve dist/index.html for all 404s (SPA fallback).");
|
|
137
|
-
|
|
138
|
-
|