@cfdez11/vex 0.1.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 (35) hide show
  1. package/README.md +1383 -0
  2. package/client/app.webmanifest +14 -0
  3. package/client/favicon.ico +0 -0
  4. package/client/services/cache.js +55 -0
  5. package/client/services/hmr-client.js +22 -0
  6. package/client/services/html.js +377 -0
  7. package/client/services/hydrate-client-components.js +97 -0
  8. package/client/services/hydrate.js +25 -0
  9. package/client/services/index.js +9 -0
  10. package/client/services/navigation/create-layouts.js +172 -0
  11. package/client/services/navigation/create-navigation.js +103 -0
  12. package/client/services/navigation/index.js +8 -0
  13. package/client/services/navigation/link-interceptor.js +39 -0
  14. package/client/services/navigation/metadata.js +23 -0
  15. package/client/services/navigation/navigate.js +64 -0
  16. package/client/services/navigation/prefetch.js +43 -0
  17. package/client/services/navigation/render-page.js +45 -0
  18. package/client/services/navigation/render-ssr.js +157 -0
  19. package/client/services/navigation/router.js +48 -0
  20. package/client/services/navigation/use-query-params.js +225 -0
  21. package/client/services/navigation/use-route-params.js +76 -0
  22. package/client/services/reactive.js +231 -0
  23. package/package.json +24 -0
  24. package/server/index.js +115 -0
  25. package/server/prebuild.js +12 -0
  26. package/server/root.html +15 -0
  27. package/server/utils/cache.js +89 -0
  28. package/server/utils/component-processor.js +1526 -0
  29. package/server/utils/data-cache.js +62 -0
  30. package/server/utils/delay.js +1 -0
  31. package/server/utils/files.js +723 -0
  32. package/server/utils/hmr.js +21 -0
  33. package/server/utils/router.js +373 -0
  34. package/server/utils/streaming.js +315 -0
  35. package/server/utils/template.js +263 -0
@@ -0,0 +1,115 @@
1
+ import express from "express";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import { handlePageRequest, revalidatePath } from "./utils/router.js";
5
+ import { initializeDirectories, CLIENT_DIR } from "./utils/files.js";
6
+
7
+ await initializeDirectories();
8
+
9
+ let serverRoutes;
10
+
11
+ if (process.env.NODE_ENV === "production") {
12
+ try {
13
+ const routesPath = path.join(process.cwd(), ".vexjs", "_routes.js");
14
+ const { routes } = await import(pathToFileURL(routesPath).href);
15
+ serverRoutes = routes;
16
+ console.log("Routes loaded.");
17
+ } catch {
18
+ console.error("ERROR: No build found. Run 'pnpm build' before starting in production.");
19
+ process.exit(1);
20
+ }
21
+ } else {
22
+ const { build } = await import("./utils/component-processor.js");
23
+ const result = await build();
24
+ console.log("Components and routes generated.");
25
+ serverRoutes = result.serverRoutes;
26
+ }
27
+
28
+ const app = express();
29
+
30
+ // Serve generated client components at /_vexjs/_components/ (before broader /_vexjs mount)
31
+ app.use(
32
+ "/_vexjs/_components",
33
+ express.static(path.join(process.cwd(), ".vexjs", "_components"), {
34
+ setHeaders(res, filePath) {
35
+ if (filePath.endsWith(".js")) {
36
+ res.setHeader("Content-Type", "application/javascript");
37
+ }
38
+ },
39
+ })
40
+ );
41
+
42
+ // Serve generated services (e.g. _routes.js) at /_vexjs/services/ (before broader /_vexjs mount)
43
+ app.use(
44
+ "/_vexjs/services",
45
+ express.static(path.join(process.cwd(), ".vexjs", "services"), {
46
+ setHeaders(res, filePath) {
47
+ if (filePath.endsWith(".js")) {
48
+ res.setHeader("Content-Type", "application/javascript");
49
+ }
50
+ },
51
+ })
52
+ );
53
+
54
+ // Serve framework client files at /_vexjs/
55
+ app.use(
56
+ "/_vexjs",
57
+ express.static(CLIENT_DIR, {
58
+ setHeaders(res, filePath) {
59
+ if (filePath.endsWith(".js")) {
60
+ res.setHeader("Content-Type", "application/javascript");
61
+ }
62
+ },
63
+ })
64
+ );
65
+
66
+ // Serve user's public directory at /
67
+ app.use("/", express.static(path.join(process.cwd(), "public")));
68
+
69
+ app.get("/revalidate", revalidatePath);
70
+
71
+ // HMR SSE endpoint — dev only
72
+ if (process.env.NODE_ENV !== "production") {
73
+ const { hmrEmitter } = await import("./utils/hmr.js");
74
+
75
+ app.get("/_vexjs/hmr", (req, res) => {
76
+ res.setHeader("Content-Type", "text/event-stream");
77
+ res.setHeader("Cache-Control", "no-cache");
78
+ res.setHeader("Connection", "keep-alive");
79
+ res.flushHeaders();
80
+
81
+ const onReload = (filename) => {
82
+ res.write(`event: reload\ndata: ${filename}\n\n`);
83
+ };
84
+
85
+ hmrEmitter.on("reload", onReload);
86
+ req.on("close", () => hmrEmitter.off("reload", onReload));
87
+ });
88
+ }
89
+
90
+ const registerSSRRoutes = (app, routes) => {
91
+ routes.forEach((route) => {
92
+ app.get(
93
+ route.serverPath,
94
+ async (req, res) => await handlePageRequest(req, res, route)
95
+ );
96
+ });
97
+ };
98
+
99
+ registerSSRRoutes(app, serverRoutes);
100
+
101
+ app.use(async (req, res) => {
102
+ const notFoundRoute = serverRoutes.find((r) => r.isNotFound);
103
+ if (notFoundRoute) {
104
+ return handlePageRequest(req, res, notFoundRoute);
105
+ }
106
+
107
+ res.status(404).send("Page not found");
108
+ });
109
+
110
+ const PORT = process.env.PORT || 3001;
111
+ app.listen(PORT, () => {
112
+ console.log(`Server running on port ${PORT}`);
113
+ });
114
+
115
+ export default app;
@@ -0,0 +1,12 @@
1
+ import { build } from "./utils/component-processor.js";
2
+ import { initializeDirectories } from "./utils/files.js";
3
+
4
+ console.log("🔨 Starting prebuild...");
5
+
6
+ console.log("📁 Creating directories...");
7
+ await initializeDirectories();
8
+
9
+ console.log("⚙️ Generating components and routes...");
10
+ await build();
11
+
12
+ console.log("✅ Prebuild complete!");
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>{{metadata.title}}</title>
7
+ <meta name="description" content="{{metadata.description}}">
8
+ </head>
9
+
10
+ <body>
11
+ <div id="app-root">
12
+ {{props.children}}
13
+ </div>
14
+ </body>
15
+ </html>
@@ -0,0 +1,89 @@
1
+ import { getComponentHtmlDisk, markComponentHtmlStale, saveComponentHtmlDisk } from "./files.js";
2
+
3
+ /**
4
+ * Retrieves cached HTML for a component or page from disk and determines if it is stale.
5
+ *
6
+ * This function supports Incremental Static Regeneration (ISR) semantics:
7
+ * - If the cache does not exist, `html` will be null.
8
+ * - If the cache exists, `isStale` indicates whether the cached HTML should be regenerated.
9
+ *
10
+ * @async
11
+ * @param {Object} options
12
+ * @param {string} options.componentPath - Unique identifier or path of the component/page to retrieve.
13
+ * @param {number} [options.revalidateSeconds=0] - Number of seconds before the cached HTML is considered stale.
14
+ * - `-1` indicates the cache never becomes stale (always fresh).
15
+ * - `0` indicates the cache is always stale (regenerate on every request).
16
+ * @returns {Promise<{ html: string|null, isStale: boolean }>}
17
+ * - `html`: The cached HTML content, or null if not cached.
18
+ * - `isStale`: True if the cache is stale or explicitly invalidated, false otherwise.
19
+ */
20
+ export async function getCachedComponentHtml({ componentPath, revalidateSeconds = 0 }) {
21
+ const { html, meta } = await getComponentHtmlDisk({ componentPath, revalidateSeconds });
22
+
23
+ if (!html) {
24
+ return { html: null };
25
+ }
26
+
27
+ let staleByTime = false;
28
+ if (revalidateSeconds !== -1) {
29
+ staleByTime = Date.now() - meta.generatedAt > revalidateSeconds * 1000;
30
+ }
31
+
32
+ const isStale = meta.isStale === true || staleByTime;
33
+
34
+ return { html, isStale };
35
+ }
36
+
37
+ /**
38
+ * Stores HTML content of a component/page on disk along with metadata.
39
+ *
40
+ * Metadata includes:
41
+ * - `generatedAt`: Timestamp of when the HTML was generated
42
+ * - `isStale`: Indicates if the cache is stale (always false when saving)
43
+ *
44
+ * @async
45
+ * @param {Object} options
46
+ * @param {string} options.componentPath - Unique identifier or path of the component/page to cache.
47
+ * @param {string} options.html - The HTML content to store in the cache.
48
+ * @returns {Promise<void>} Resolves when the HTML and metadata have been successfully saved.
49
+ */
50
+ export async function saveCachedComponentHtml({ componentPath, html }) {
51
+ await saveComponentHtmlDisk({ componentPath, html });
52
+ }
53
+
54
+ /**
55
+ * Marks a cached component as stale without regenerating it.
56
+ *
57
+ * This is useful for manual revalidation or triggering ISR.
58
+ *
59
+ * @async
60
+ * @param {string} componentPath - Unique identifier or path of the component/page to invalidate.
61
+ * @returns {Promise<void>} Resolves when the cache metadata has been updated.
62
+ */
63
+ export async function revalidateCachedComponentHtml(componentPath) {
64
+ await markComponentHtmlStale({ componentPath });
65
+ }
66
+
67
+ /**
68
+ * Converts a revalidation setting into seconds for ISR purposes.
69
+ *
70
+ * @param {number|boolean|string} revalidate - Revalidation setting.
71
+ * - If a number, it represents the number of seconds before the cache becomes stale.
72
+ * - If `true`, defaults to 60 seconds.
73
+ * - If `0`, cache is always stale.
74
+ * - If `false` or `"never"`, cache never becomes stale.
75
+ * @returns {number} Number of seconds for revalidation, or -1 for no revalidation.
76
+ */
77
+ export function getRevalidateSeconds(revalidate) {
78
+ if (typeof revalidate === "number") {
79
+ return revalidate;
80
+ } if (typeof revalidate === "string" && !Number.isNaN(Number(revalidate))) {
81
+ return Number(revalidate);
82
+ }else if (revalidate === true) {
83
+ return 60; // default to 60 seconds
84
+ } else if (revalidate === 'never' || revalidate === false) {
85
+ return -1; // never revalidate
86
+ } else {
87
+ return 0; // always revalidate
88
+ }
89
+ }