@cfdez11/vex 0.8.0 → 0.8.2
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/README.md +5 -0
- package/package.json +1 -1
- package/server/build-static.js +1 -0
- package/server/utils/component-processor.js +11 -9
- package/server/utils/streaming.js +11 -3
- package/server/utils/template.js +12 -4
package/README.md
CHANGED
|
@@ -697,6 +697,11 @@ sequenceDiagram
|
|
|
697
697
|
- [x] `vex.config.json` — configurable `srcDir` and `watchIgnore`
|
|
698
698
|
- [x] Published to npm as `@cfdez11/vex`
|
|
699
699
|
- [x] VS Code extension with syntax highlighting and go-to-definition
|
|
700
|
+
- [ ] Refactor client component prop pipeline: evaluate `:props` expressions directly in `streaming.js` with the page scope instead of going through `template.js` → `String()` → `JSON.stringify` → `JSON.parse`. Eliminates the unnecessary serialization round-trip for array/object props.
|
|
701
|
+
- [ ] esbuild minification: enable `minify: true` in `generateClientBundle` and `buildUserFile` for production builds. Zero-cost win — esbuild is already in the pipeline.
|
|
702
|
+
- [ ] esbuild source maps: enable `sourcemap: "inline"` in dev mode so browser devtools show original source instead of the bundle.
|
|
703
|
+
- [ ] esbuild browser target: add `target: ["es2020"]` (or configurable) to transpile modern syntax for broader browser compatibility.
|
|
704
|
+
- [ ] esbuild code splitting: shared npm packages (e.g. `date-fns`) are currently inlined into every component bundle separately. Code splitting would emit a shared chunk, reducing total download size when multiple components share the same dependency.
|
|
700
705
|
- [ ] Devtools
|
|
701
706
|
- [ ] Typescript in framework
|
|
702
707
|
- [ ] Allow typescript to devs
|
package/package.json
CHANGED
package/server/build-static.js
CHANGED
|
@@ -38,6 +38,7 @@ let shell = rootTemplate
|
|
|
38
38
|
.replace(/\{\{props\.children\}\}/g, "");
|
|
39
39
|
|
|
40
40
|
const frameworkScripts = [
|
|
41
|
+
`<style>vex-root { display: contents; }</style>`,
|
|
41
42
|
`<script type="module" src="/_vexjs/services/index.js"></script>`,
|
|
42
43
|
`<script src="/_vexjs/services/hydrate-client-components.js"></script>`,
|
|
43
44
|
`<script src="/_vexjs/services/hydrate.js" id="hydrate-script"></script>`,
|
|
@@ -564,6 +564,7 @@ async function renderLayouts(pagePath, pageContent, pageHead = {}) {
|
|
|
564
564
|
// so users don't need to reference framework scripts in their root.html
|
|
565
565
|
const devMode = process.env.NODE_ENV !== "production";
|
|
566
566
|
const frameworkScripts = [
|
|
567
|
+
`<style>vex-root { display: contents; }</style>`,
|
|
567
568
|
`<script type="module" src="/_vexjs/services/index.js"></script>`,
|
|
568
569
|
`<script src="/_vexjs/services/hydrate-client-components.js"></script>`,
|
|
569
570
|
`<script src="/_vexjs/services/hydrate.js" id="hydrate-script"></script>`,
|
|
@@ -832,6 +833,11 @@ export async function generateClientComponentModule({
|
|
|
832
833
|
// ── 5. Assemble the esbuild entry source ────────────────────────────────────
|
|
833
834
|
// This is a valid ESM module that esbuild will bundle. Imports at the top,
|
|
834
835
|
// hydrateClientComponent exported as a named function.
|
|
836
|
+
// Add persistent wrapper element anchors the component in the DOM so that
|
|
837
|
+
// re-renders always have a stable target to replace children into.
|
|
838
|
+
// Using a plain Element (never a DocumentFragment) avoids the fragment-drain
|
|
839
|
+
// problem: after marker.replaceWith(fragment) the fragment empties and
|
|
840
|
+
// disconnects, making subsequent root.replaceWith() calls silently no-op.
|
|
835
841
|
const entrySource = `
|
|
836
842
|
${[...importLines].join("\n")}
|
|
837
843
|
|
|
@@ -840,20 +846,16 @@ export const metadata = ${JSON.stringify(metadata)};
|
|
|
840
846
|
export function hydrateClientComponent(marker, incomingProps = {}) {
|
|
841
847
|
${cleanClientCode}
|
|
842
848
|
|
|
843
|
-
|
|
849
|
+
const wrapper = document.createElement("vex-root");
|
|
850
|
+
marker.replaceWith(wrapper);
|
|
851
|
+
|
|
844
852
|
function render() {
|
|
845
853
|
const node = html\`${processedHtml}\`;
|
|
846
|
-
|
|
847
|
-
root = node;
|
|
848
|
-
marker.replaceWith(node);
|
|
849
|
-
} else {
|
|
850
|
-
root.replaceWith(node);
|
|
851
|
-
root = node;
|
|
852
|
-
}
|
|
854
|
+
wrapper.replaceChildren(node);
|
|
853
855
|
}
|
|
854
856
|
|
|
855
857
|
effect(() => render());
|
|
856
|
-
return
|
|
858
|
+
return wrapper;
|
|
857
859
|
}
|
|
858
860
|
`.trim();
|
|
859
861
|
|
|
@@ -28,12 +28,20 @@ import {
|
|
|
28
28
|
* // id: 'my-component'
|
|
29
29
|
* // }
|
|
30
30
|
*/
|
|
31
|
+
function decodeAttrValue(raw) {
|
|
32
|
+
const decoded = raw.replace(/"/g, '"');
|
|
33
|
+
if (decoded.startsWith("[") || decoded.startsWith("{")) {
|
|
34
|
+
try { return JSON.parse(decoded); } catch {}
|
|
35
|
+
}
|
|
36
|
+
return decoded;
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
function parseAttributes(rawAttrs) {
|
|
32
40
|
const attrs = {};
|
|
33
41
|
const regex =
|
|
34
42
|
/:([\w-]+)=(?:"([^"]*)"|'([^']*)')|@([\w-]+)=(?:"([^"]*)"|'([^']*)')|([\w:-]+)=(?:"([^"]*)"|'([^']*)')/g;
|
|
35
43
|
let match;
|
|
36
|
-
|
|
44
|
+
|
|
37
45
|
while ((match = regex.exec(rawAttrs)) !== null) {
|
|
38
46
|
if (match[1]) {
|
|
39
47
|
// Dynamic prop :prop
|
|
@@ -42,8 +50,8 @@ function parseAttributes(rawAttrs) {
|
|
|
42
50
|
// Event handler @event
|
|
43
51
|
attrs[match[4]] = match[5] ?? match[6] ?? "";
|
|
44
52
|
} else if (match[7]) {
|
|
45
|
-
// Static prop
|
|
46
|
-
attrs[match[7]] = match[8] ?? match[9] ?? "";
|
|
53
|
+
// Static prop — decode " and recover arrays/objects serialized by template.js
|
|
54
|
+
attrs[match[7]] = decodeAttrValue(match[8] ?? match[9] ?? "");
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
|
package/server/utils/template.js
CHANGED
|
@@ -154,15 +154,23 @@ function processNode(node, scope, previousRendered = false) {
|
|
|
154
154
|
const isSuspenseFallback =
|
|
155
155
|
name === ":fallback" && node.name === "Suspense";
|
|
156
156
|
const realName = name.slice(1);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
if (!isSuspenseFallback) {
|
|
158
|
+
const val = getDataValue(value, scope);
|
|
159
|
+
attrs[realName] = (val !== null && val !== undefined && typeof val === "object")
|
|
160
|
+
? JSON.stringify(val)
|
|
161
|
+
: String(val ?? "");
|
|
162
|
+
} else {
|
|
163
|
+
attrs[realName] = value;
|
|
164
|
+
}
|
|
160
165
|
delete attrs[name];
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
if (name.startsWith("x-bind:")) {
|
|
164
169
|
const realName = name.slice(7);
|
|
165
|
-
|
|
170
|
+
const val = getDataValue(value, scope);
|
|
171
|
+
attrs[realName] = (val !== null && val !== undefined && typeof val === "object")
|
|
172
|
+
? JSON.stringify(val)
|
|
173
|
+
: String(val ?? "");
|
|
166
174
|
delete attrs[name];
|
|
167
175
|
}
|
|
168
176
|
}
|