@cfdez11/vex 0.8.2 → 0.9.0

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.
Files changed (71) hide show
  1. package/dist/bin/vex.js +3 -0
  2. package/dist/client/services/cache.js +1 -0
  3. package/dist/client/services/hmr-client.js +1 -0
  4. package/dist/client/services/html.js +1 -0
  5. package/dist/client/services/hydrate-client-components.js +1 -0
  6. package/dist/client/services/hydrate.js +1 -0
  7. package/dist/client/services/index.js +1 -0
  8. package/dist/client/services/navigation/create-layouts.js +1 -0
  9. package/dist/client/services/navigation/create-navigation.js +1 -0
  10. package/dist/client/services/navigation/index.js +1 -0
  11. package/dist/client/services/navigation/link-interceptor.js +1 -0
  12. package/dist/client/services/navigation/metadata.js +1 -0
  13. package/dist/client/services/navigation/navigate.js +1 -0
  14. package/dist/client/services/navigation/prefetch.js +1 -0
  15. package/dist/client/services/navigation/render-page.js +1 -0
  16. package/dist/client/services/navigation/render-ssr.js +1 -0
  17. package/dist/client/services/navigation/router.js +1 -0
  18. package/dist/client/services/navigation/use-query-params.js +1 -0
  19. package/dist/client/services/navigation/use-route-params.js +1 -0
  20. package/dist/client/services/navigation.js +1 -0
  21. package/dist/client/services/reactive.js +1 -0
  22. package/dist/server/build-static.js +6 -0
  23. package/dist/server/index.js +4 -0
  24. package/dist/server/prebuild.js +1 -0
  25. package/dist/server/utils/cache.js +1 -0
  26. package/dist/server/utils/component-processor.js +68 -0
  27. package/dist/server/utils/data-cache.js +1 -0
  28. package/dist/server/utils/esbuild-plugin.js +1 -0
  29. package/dist/server/utils/files.js +28 -0
  30. package/dist/server/utils/hmr.js +1 -0
  31. package/dist/server/utils/router.js +11 -0
  32. package/dist/server/utils/streaming.js +1 -0
  33. package/dist/server/utils/template.js +1 -0
  34. package/package.json +8 -7
  35. package/bin/vex.js +0 -69
  36. package/client/favicon.ico +0 -0
  37. package/client/services/cache.js +0 -55
  38. package/client/services/hmr-client.js +0 -22
  39. package/client/services/html.js +0 -377
  40. package/client/services/hydrate-client-components.js +0 -97
  41. package/client/services/hydrate.js +0 -25
  42. package/client/services/index.js +0 -9
  43. package/client/services/navigation/create-layouts.js +0 -172
  44. package/client/services/navigation/create-navigation.js +0 -103
  45. package/client/services/navigation/index.js +0 -8
  46. package/client/services/navigation/link-interceptor.js +0 -39
  47. package/client/services/navigation/metadata.js +0 -23
  48. package/client/services/navigation/navigate.js +0 -64
  49. package/client/services/navigation/prefetch.js +0 -43
  50. package/client/services/navigation/render-page.js +0 -45
  51. package/client/services/navigation/render-ssr.js +0 -157
  52. package/client/services/navigation/router.js +0 -48
  53. package/client/services/navigation/use-query-params.js +0 -225
  54. package/client/services/navigation/use-route-params.js +0 -76
  55. package/client/services/navigation.js +0 -6
  56. package/client/services/reactive.js +0 -247
  57. package/server/build-static.js +0 -138
  58. package/server/index.js +0 -135
  59. package/server/prebuild.js +0 -13
  60. package/server/utils/cache.js +0 -89
  61. package/server/utils/component-processor.js +0 -1631
  62. package/server/utils/data-cache.js +0 -62
  63. package/server/utils/delay.js +0 -1
  64. package/server/utils/esbuild-plugin.js +0 -110
  65. package/server/utils/files.js +0 -845
  66. package/server/utils/hmr.js +0 -21
  67. package/server/utils/router.js +0 -375
  68. package/server/utils/streaming.js +0 -324
  69. package/server/utils/template.js +0 -274
  70. /package/{client → dist/client}/app.webmanifest +0 -0
  71. /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
-
@@ -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
-