@cfdez11/vex 0.5.0 → 0.6.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.
- package/client/services/hydrate-client-components.js +5 -5
- package/package.json +2 -1
- package/server/build-static.js +1 -0
- package/server/index.js +2 -1
- package/server/prebuild.js +1 -0
- package/server/utils/component-processor.js +7 -4
- package/server/utils/files.js +6 -1
- package/server/utils/streaming.js +2 -2
|
@@ -78,12 +78,12 @@
|
|
|
78
78
|
// Start observing the document for new nodes
|
|
79
79
|
observer.observe(document, { childList: true, subtree: true });
|
|
80
80
|
|
|
81
|
-
// Hydrate existing components on DOMContentLoaded or immediately if already interactive
|
|
81
|
+
// Hydrate existing components on DOMContentLoaded or immediately if already interactive.
|
|
82
|
+
// The observer is intentionally NOT disconnected here — it must stay active to catch
|
|
83
|
+
// components inserted after DOMContentLoaded (nested CSR components, Suspense streaming,
|
|
84
|
+
// SPA navigations). The `data-hydrated` guard in hydrateMarker prevents double-hydration.
|
|
82
85
|
if (document.readyState === "loading") {
|
|
83
|
-
document.addEventListener("DOMContentLoaded", () =>
|
|
84
|
-
hydrateComponents();
|
|
85
|
-
observer.disconnect();
|
|
86
|
-
});
|
|
86
|
+
document.addEventListener("DOMContentLoaded", () => hydrateComponents());
|
|
87
87
|
} else {
|
|
88
88
|
hydrateComponents();
|
|
89
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfdez11/vex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A vanilla JavaScript meta-framework with file-based routing, SSR/CSR/SSG/ISR and Vue-like reactivity",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"dom-serializer": "^2.0.0",
|
|
25
|
+
"dotenv": "^16.0.0",
|
|
25
26
|
"esbuild": "^0.25.0",
|
|
26
27
|
"express": "^5.2.1",
|
|
27
28
|
"htmlparser2": "^10.0.0"
|
package/server/build-static.js
CHANGED
package/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
1
2
|
import express from "express";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import { pathToFileURL } from "url";
|
|
@@ -115,7 +116,7 @@ app.use(async (req, res) => {
|
|
|
115
116
|
res.status(404).send("Page not found");
|
|
116
117
|
});
|
|
117
118
|
|
|
118
|
-
const PORT = process.env.PORT || 3001;
|
|
119
|
+
const PORT = process.env.VEX_PORT || process.env.PORT || 3001;
|
|
119
120
|
app.listen(PORT, () => {
|
|
120
121
|
console.log(`Server running on port ${PORT}`);
|
|
121
122
|
});
|
package/server/prebuild.js
CHANGED
|
@@ -177,7 +177,7 @@ const getScriptImports = async (script, isClientSide = false, filePath = null) =
|
|
|
177
177
|
while ((match = importRegex.exec(script)) !== null) {
|
|
178
178
|
const [importStatement, defaultImport, namedImports, modulePath] = match;
|
|
179
179
|
|
|
180
|
-
const { path, fileUrl } = await getImportData(modulePath);
|
|
180
|
+
const { path, fileUrl } = await getImportData(modulePath, filePath);
|
|
181
181
|
|
|
182
182
|
if (path.endsWith(".vex")) {
|
|
183
183
|
// Recursively process HTML component
|
|
@@ -989,7 +989,7 @@ async function generateServerComponentHTML(componentPath) {
|
|
|
989
989
|
* and runtime interpolations (e.g., `${variable}`).
|
|
990
990
|
*
|
|
991
991
|
* @param {string} componentName - The logical name of the component.
|
|
992
|
-
* @param {string}
|
|
992
|
+
* @param {string} componentAbsPath - The absolute file path of the component (resolved by getImportData).
|
|
993
993
|
* @param {Record<string, any>} [props={}] - An object of props to pass to the component.
|
|
994
994
|
* Values can be literals or template
|
|
995
995
|
* interpolations (`${…}`) for dynamic evaluation.
|
|
@@ -997,10 +997,13 @@ async function generateServerComponentHTML(componentPath) {
|
|
|
997
997
|
* @returns {Promise<string>} A promise that resolves to a string containing
|
|
998
998
|
* the `<template>` HTML for hydration.
|
|
999
999
|
*/
|
|
1000
|
-
export async function processClientComponent(componentName,
|
|
1000
|
+
export async function processClientComponent(componentName, componentAbsPath, props = {}) {
|
|
1001
1001
|
const targetId = `client-${componentName}-${Date.now()}`;
|
|
1002
1002
|
|
|
1003
|
-
|
|
1003
|
+
// componentAbsPath is the absolute resolved path — generateComponentId strips ROOT_DIR
|
|
1004
|
+
// internally, so this produces the same hash as the bundle filename written by
|
|
1005
|
+
// generateComponentAndFillCache (which also calls generateComponentId with the abs path).
|
|
1006
|
+
const componentImport = generateComponentId(componentAbsPath);
|
|
1004
1007
|
const propsJson = serializeClientComponentProps(props);
|
|
1005
1008
|
const html = `<template id="${targetId}" data-client:component="${componentImport}" data-client:props='${propsJson}'></template>`;
|
|
1006
1009
|
|
package/server/utils/files.js
CHANGED
|
@@ -811,7 +811,7 @@ export async function saveClientComponentModule(componentName, jsModuleCode) {
|
|
|
811
811
|
* importPath: string
|
|
812
812
|
* }}
|
|
813
813
|
*/
|
|
814
|
-
export async function getImportData(importPath) {
|
|
814
|
+
export async function getImportData(importPath, callerFilePath = null) {
|
|
815
815
|
let resolvedPath;
|
|
816
816
|
if (importPath.startsWith("vex/server/")) {
|
|
817
817
|
resolvedPath = path.resolve(FRAMEWORK_DIR, importPath.replace("vex/server/", "server/"));
|
|
@@ -821,6 +821,11 @@ export async function getImportData(importPath) {
|
|
|
821
821
|
resolvedPath = path.resolve(FRAMEWORK_DIR, importPath.replace(".app/server/", "server/"));
|
|
822
822
|
} else if (importPath.startsWith(".app/")) {
|
|
823
823
|
resolvedPath = path.resolve(FRAMEWORK_DIR, importPath.replace(".app/", ""));
|
|
824
|
+
} else if ((importPath.startsWith("./") || importPath.startsWith("../")) && callerFilePath) {
|
|
825
|
+
// Relative import — resolve against the caller component's directory, not ROOT_DIR.
|
|
826
|
+
// Without this, `import Foo from './foo.vex'` inside a nested component would be
|
|
827
|
+
// resolved from the project root instead of from the file that contains the import.
|
|
828
|
+
resolvedPath = path.resolve(path.dirname(callerFilePath), importPath);
|
|
824
829
|
} else {
|
|
825
830
|
resolvedPath = path.resolve(ROOT_DIR, importPath);
|
|
826
831
|
}
|
|
@@ -128,7 +128,7 @@ async function renderClientComponents(html, clientComponents) {
|
|
|
128
128
|
let processedHtml = html;
|
|
129
129
|
const allScripts = [];
|
|
130
130
|
|
|
131
|
-
for (const [componentName, {
|
|
131
|
+
for (const [componentName, { path: componentAbsPath }] of clientComponents.entries()) {
|
|
132
132
|
const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
133
133
|
|
|
134
134
|
const componentRegex = new RegExp(
|
|
@@ -156,7 +156,7 @@ async function renderClientComponents(html, clientComponents) {
|
|
|
156
156
|
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
157
157
|
const { start, end, attrs } = replacements[i];
|
|
158
158
|
|
|
159
|
-
const htmlComponent = await processClientComponent(componentName,
|
|
159
|
+
const htmlComponent = await processClientComponent(componentName, componentAbsPath, attrs);
|
|
160
160
|
|
|
161
161
|
processedHtml =
|
|
162
162
|
processedHtml.slice(0, start) +
|