nukejs 0.0.26 → 0.0.27
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/build-common.js +75 -19
- package/dist/bundle.js +14 -0
- package/dist/renderer.js +48 -27
- package/package.json +1 -1
package/dist/build-common.js
CHANGED
|
@@ -495,27 +495,83 @@ function buildWrapperAttrString(attrs: Record<string, any>): string {
|
|
|
495
495
|
return parts.length ? ' ' + parts.join(' ') : '';
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
function
|
|
499
|
-
if (typeof
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
498
|
+
function prepareProps(props, hydrated) {
|
|
499
|
+
if (!props || typeof props !== 'object') return Promise.resolve({ real: props, json: props });
|
|
500
|
+
const entries = Object.entries(props);
|
|
501
|
+
return Promise.all(entries.map(([, v]) => prepareValue(v, hydrated))).then((results) => {
|
|
502
|
+
const real = {};
|
|
503
|
+
const json = {};
|
|
504
|
+
entries.forEach(([key], i) => {
|
|
505
|
+
real[key] = results[i].real;
|
|
506
|
+
if (results[i].json !== undefined) json[key] = results[i].json;
|
|
507
|
+
});
|
|
508
|
+
return { real, json };
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function prepareValue(value, hydrated) {
|
|
513
|
+
if (value === null || value === undefined) return { real: value, json: value };
|
|
514
|
+
if (typeof value === 'function') return { real: value, json: undefined };
|
|
515
|
+
if (typeof value !== 'object') return { real: value, json: value };
|
|
516
|
+
|
|
517
|
+
if (Array.isArray(value)) {
|
|
518
|
+
const items = await Promise.all(value.map((v) => prepareValue(v, hydrated)));
|
|
519
|
+
return {
|
|
520
|
+
real: items.map((i) => i.real),
|
|
521
|
+
json: items.map((i) => i.json).filter((i) => i !== undefined),
|
|
522
|
+
};
|
|
515
523
|
}
|
|
524
|
+
|
|
525
|
+
if (value.$$typeof) return prepareElement(value, hydrated);
|
|
526
|
+
|
|
527
|
+
const out = await prepareProps(value, hydrated);
|
|
516
528
|
return out;
|
|
517
529
|
}
|
|
518
530
|
|
|
531
|
+
// Resolves a single React element found inside a client component's props.
|
|
532
|
+
// Native elements and fragments recurse into their own props. Client
|
|
533
|
+
// components are left untouched for 'real' (react-dom/server renders them
|
|
534
|
+
// normally within the boundary's own renderToString call below) and wired
|
|
535
|
+
// through as { __re: 'client', componentId, props } for the browser to
|
|
536
|
+
// mount for real. Server components can't run in the browser at all, so
|
|
537
|
+
// they're rendered once with this same file's renderNode() (handles async
|
|
538
|
+
// components and any nested client boundaries inside them) and wrapped in
|
|
539
|
+
// an inert <span style="display:contents"> carrying that HTML \u2014
|
|
540
|
+
// identically on both 'real' and 'json', so the SSR markup and the
|
|
541
|
+
// browser's reconstructed tree have the same shape and hydrateRoot() can
|
|
542
|
+
// reconcile them without a mismatch.
|
|
543
|
+
async function prepareElement(element, hydrated) {
|
|
544
|
+
const { type, props } = element;
|
|
545
|
+
|
|
546
|
+
if (type === Symbol.for('react.fragment')) {
|
|
547
|
+
const p = await prepareProps(props, hydrated);
|
|
548
|
+
return { real: __createElement__(Symbol.for('react.fragment'), p.real), json: { __re: 'fragment', props: p.json } };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (typeof type === 'string') {
|
|
552
|
+
const p = await prepareProps(props, hydrated);
|
|
553
|
+
return { real: __createElement__(type, p.real), json: { __re: 'html', tag: type, props: p.json } };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (typeof type === 'function') {
|
|
557
|
+
const cid = type.__nukeClientId ?? CLIENT_COMPONENTS[type.name];
|
|
558
|
+
if (cid) {
|
|
559
|
+
const p = await prepareProps(props, hydrated);
|
|
560
|
+
return { real: element, json: { __re: 'client', componentId: cid, props: p.json } };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const html = await renderNode(element, hydrated);
|
|
564
|
+
const wrapperProps = {
|
|
565
|
+
style: { display: 'contents' },
|
|
566
|
+
'data-n-static': true,
|
|
567
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
568
|
+
};
|
|
569
|
+
return { real: __createElement__('span', wrapperProps), json: { __re: 'static', html } };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return { real: element, json: undefined };
|
|
573
|
+
}
|
|
574
|
+
|
|
519
575
|
async function renderNode(node: any, hydrated: Set<string>): Promise<string> {
|
|
520
576
|
if (node == null || typeof node === 'boolean') return '';
|
|
521
577
|
if (typeof node === 'string') return escapeHtml(node);
|
|
@@ -557,10 +613,10 @@ async function renderNode(node: any, hydrated: Set<string>): Promise<string> {
|
|
|
557
613
|
hydrated.add(clientId);
|
|
558
614
|
const { wrapperAttrs, componentProps } = splitWrapperAttrs(props);
|
|
559
615
|
const wrapperAttrStr = buildWrapperAttrString(wrapperAttrs);
|
|
560
|
-
const serializedProps =
|
|
616
|
+
const { real: hydrationSafeProps, json: serializedProps } = await prepareProps(componentProps ?? {}, hydrated);
|
|
561
617
|
let ssrHtml: string;
|
|
562
618
|
try {
|
|
563
|
-
ssrHtml = __renderToString__(__createElement__(type as any,
|
|
619
|
+
ssrHtml = __renderToString__(__createElement__(type as any, hydrationSafeProps || {}));
|
|
564
620
|
} catch {
|
|
565
621
|
ssrHtml = PRERENDERED_HTML[clientId] ?? '';
|
|
566
622
|
}
|
package/dist/bundle.js
CHANGED
|
@@ -50,6 +50,20 @@ async function reconstructElement(node, mods) {
|
|
|
50
50
|
const React = await import("react");
|
|
51
51
|
return React.default.createElement(n.tag, await reconstructProps(n.props, mods));
|
|
52
52
|
}
|
|
53
|
+
if (node.__re === "fragment") {
|
|
54
|
+
const n = node;
|
|
55
|
+
const React = await import("react");
|
|
56
|
+
return React.default.createElement(React.default.Fragment, await reconstructProps(n.props, mods));
|
|
57
|
+
}
|
|
58
|
+
if (node.__re === "static") {
|
|
59
|
+
const n = node;
|
|
60
|
+
const React = await import("react");
|
|
61
|
+
return React.default.createElement("span", {
|
|
62
|
+
style: { display: "contents" },
|
|
63
|
+
"data-n-static": true,
|
|
64
|
+
dangerouslySetInnerHTML: { __html: n.html }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
53
67
|
return node;
|
|
54
68
|
}
|
|
55
69
|
async function reconstructProps(props, mods) {
|
package/dist/renderer.js
CHANGED
|
@@ -90,9 +90,9 @@ async function renderFunctionComponent(type, props, ctx) {
|
|
|
90
90
|
ctx.hydrated.add(id);
|
|
91
91
|
const { wrapperAttrs, componentProps } = splitWrapperAttrs(props);
|
|
92
92
|
const wrapperAttrStr = buildWrapperAttrString(wrapperAttrs);
|
|
93
|
-
const serializedProps =
|
|
93
|
+
const { real: hydrationSafeProps, json: serializedProps } = await prepareProps(componentProps, ctx);
|
|
94
94
|
log.verbose(`Client component rendered for hydration: ${id} (${path.basename(filePath)})`);
|
|
95
|
-
const html = ctx.skipClientSSR ? "" : renderToString(createElement(type,
|
|
95
|
+
const html = ctx.skipClientSSR ? "" : renderToString(createElement(type, hydrationSafeProps));
|
|
96
96
|
return `<span data-hydrate-id="${id}"${wrapperAttrStr} data-hydrate-props="${escapeHtml(
|
|
97
97
|
JSON.stringify(serializedProps)
|
|
98
98
|
)}">${html}</span>`;
|
|
@@ -105,51 +105,72 @@ async function renderFunctionComponent(type, props, ctx) {
|
|
|
105
105
|
const resolved = result?.then ? await result : result;
|
|
106
106
|
return renderElementToHtml(resolved, ctx);
|
|
107
107
|
}
|
|
108
|
-
function
|
|
109
|
-
if (!props || typeof props !== "object") return props;
|
|
110
|
-
const
|
|
108
|
+
async function prepareProps(props, ctx) {
|
|
109
|
+
if (!props || typeof props !== "object") return { real: props, json: props };
|
|
110
|
+
const real = {};
|
|
111
|
+
const json = {};
|
|
111
112
|
for (const [key, value] of Object.entries(props)) {
|
|
112
|
-
const
|
|
113
|
-
|
|
113
|
+
const p = await prepareValue(value, ctx);
|
|
114
|
+
real[key] = p.real;
|
|
115
|
+
if (p.json !== void 0) json[key] = p.json;
|
|
114
116
|
}
|
|
115
|
-
return
|
|
117
|
+
return { real, json };
|
|
116
118
|
}
|
|
117
|
-
function
|
|
118
|
-
if (value === null || value === void 0) return value;
|
|
119
|
-
if (typeof value === "function") return void 0;
|
|
120
|
-
if (typeof value !== "object") return value;
|
|
121
|
-
if (Array.isArray(value))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
async function prepareValue(value, ctx) {
|
|
120
|
+
if (value === null || value === void 0) return { real: value, json: value };
|
|
121
|
+
if (typeof value === "function") return { real: value, json: void 0 };
|
|
122
|
+
if (typeof value !== "object") return { real: value, json: value };
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
const items = await Promise.all(value.map((v) => prepareValue(v, ctx)));
|
|
125
|
+
return {
|
|
126
|
+
real: items.map((i) => i.real),
|
|
127
|
+
json: items.map((i) => i.json).filter((i) => i !== void 0)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (value.$$typeof) return prepareElement(value, ctx);
|
|
131
|
+
const real = {};
|
|
132
|
+
const json = {};
|
|
126
133
|
for (const [k, v] of Object.entries(value)) {
|
|
127
|
-
const
|
|
128
|
-
|
|
134
|
+
const p = await prepareValue(v, ctx);
|
|
135
|
+
real[k] = p.real;
|
|
136
|
+
if (p.json !== void 0) json[k] = p.json;
|
|
129
137
|
}
|
|
130
|
-
return
|
|
138
|
+
return { real, json };
|
|
131
139
|
}
|
|
132
|
-
function
|
|
140
|
+
async function prepareElement(element, ctx) {
|
|
133
141
|
const { type, props } = element;
|
|
142
|
+
if (type === Fragment) {
|
|
143
|
+
const p = await prepareProps(props, ctx);
|
|
144
|
+
return { real: createElement(Fragment, p.real), json: { __re: "fragment", props: p.json } };
|
|
145
|
+
}
|
|
134
146
|
if (typeof type === "string") {
|
|
135
|
-
|
|
147
|
+
const p = await prepareProps(props, ctx);
|
|
148
|
+
return { real: createElement(type, p.real), json: { __re: "html", tag: type, props: p.json } };
|
|
136
149
|
}
|
|
137
150
|
if (typeof type === "function") {
|
|
138
151
|
const componentCache = getComponentCache();
|
|
139
|
-
for (const [id, filePath] of registry.entries()) {
|
|
152
|
+
for (const [id, filePath] of ctx.registry.entries()) {
|
|
140
153
|
const info = componentCache.get(filePath);
|
|
141
154
|
if (!info?.isClientComponent) continue;
|
|
142
155
|
if (type.__nukeClientId === id || info.exportedName && type.name === info.exportedName) {
|
|
143
156
|
type.__nukeClientId = id;
|
|
157
|
+
const p = await prepareProps(props, ctx);
|
|
144
158
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
real: element,
|
|
160
|
+
// untouched — React renders it directly within the outer renderToString call
|
|
161
|
+
json: { __re: "client", componentId: id, props: p.json }
|
|
148
162
|
};
|
|
149
163
|
}
|
|
150
164
|
}
|
|
165
|
+
const html = await renderElementToHtml(element, ctx);
|
|
166
|
+
const wrapperProps = {
|
|
167
|
+
style: { display: "contents" },
|
|
168
|
+
"data-n-static": true,
|
|
169
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
170
|
+
};
|
|
171
|
+
return { real: createElement("span", wrapperProps), json: { __re: "static", html } };
|
|
151
172
|
}
|
|
152
|
-
return void 0;
|
|
173
|
+
return { real: element, json: void 0 };
|
|
153
174
|
}
|
|
154
175
|
export {
|
|
155
176
|
renderElementToHtml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukejs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "A minimal, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|