@barefootjs/client 0.2.0 → 0.3.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/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/index.js +41 -12
- package/dist/runtime/map-array.d.ts.map +1 -1
- package/dist/runtime/render.d.ts.map +1 -1
- package/dist/runtime/standalone.js +41 -12
- package/package.json +3 -4
- package/src/runtime/component.ts +31 -20
- package/src/runtime/hydrate.ts +6 -0
- package/src/runtime/map-array.ts +11 -0
- package/src/runtime/render.ts +32 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,CAoIb;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE3F;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAE7G;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR;AA+DD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAcjF"}
|
package/dist/runtime/index.js
CHANGED
|
@@ -916,6 +916,8 @@ function hydrateCommentScope(comment) {
|
|
|
916
916
|
const proxyEl = nextElementSibling2(comment) ?? comment.parentElement;
|
|
917
917
|
if (!proxyEl)
|
|
918
918
|
return;
|
|
919
|
+
if (hydratedScopes.has(proxyEl))
|
|
920
|
+
return;
|
|
919
921
|
commentScopeRegistry.set(proxyEl, { commentNode: comment, scopeId });
|
|
920
922
|
const parsed = parseProps(propsJson || null, `comment scope ${scopeId}`);
|
|
921
923
|
const props = parsed[name] ?? {};
|
|
@@ -970,9 +972,14 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
970
972
|
}
|
|
971
973
|
return result;
|
|
972
974
|
});
|
|
975
|
+
const def = getRegisteredDef(name);
|
|
976
|
+
const isCommentWrapper = def?.comment === true;
|
|
977
|
+
const scopeId = isCommentWrapper ? null : `${name}_${generateId()}`;
|
|
973
978
|
const prevParentScopeId = _parentScopeId;
|
|
974
979
|
if (slot?.parent) {
|
|
975
980
|
_parentScopeId = slot.parent;
|
|
981
|
+
} else if (scopeId) {
|
|
982
|
+
_parentScopeId = scopeId;
|
|
976
983
|
}
|
|
977
984
|
let html;
|
|
978
985
|
try {
|
|
@@ -985,10 +992,8 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
985
992
|
console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`);
|
|
986
993
|
return createPlaceholder(name, key);
|
|
987
994
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
if (!isCommentWrapper) {
|
|
991
|
-
element.setAttribute(BF_SCOPE, `${name}_${generateId()}`);
|
|
995
|
+
if (scopeId) {
|
|
996
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
992
997
|
}
|
|
993
998
|
if (slot) {
|
|
994
999
|
if (slot.parent)
|
|
@@ -1633,6 +1638,17 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
|
|
|
1633
1638
|
scopes.set(key, scope);
|
|
1634
1639
|
insertScope(scope, container, anchor);
|
|
1635
1640
|
}
|
|
1641
|
+
for (let i = items.length;i < existingRanges.length; i++) {
|
|
1642
|
+
const range = existingRanges[i];
|
|
1643
|
+
if (range.startMarker?.parentNode)
|
|
1644
|
+
range.startMarker.remove();
|
|
1645
|
+
if (range.primaryEl.parentNode)
|
|
1646
|
+
range.primaryEl.remove();
|
|
1647
|
+
for (const ex of range.extras) {
|
|
1648
|
+
if (ex.parentNode)
|
|
1649
|
+
ex.remove();
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1636
1652
|
return;
|
|
1637
1653
|
}
|
|
1638
1654
|
}
|
|
@@ -2075,17 +2091,30 @@ function render(container, nameOrDef, props = {}) {
|
|
|
2075
2091
|
}
|
|
2076
2092
|
const tpl = document.createElement("template");
|
|
2077
2093
|
tpl.innerHTML = html;
|
|
2078
|
-
const
|
|
2079
|
-
if (
|
|
2094
|
+
const rootElements = Array.from(tpl.content.childNodes).filter((n) => n.nodeType === Node.ELEMENT_NODE);
|
|
2095
|
+
if (rootElements.length === 0) {
|
|
2080
2096
|
throw new Error("[BarefootJS] render(): template returned empty HTML");
|
|
2081
2097
|
}
|
|
2082
|
-
if (!element.getAttribute(BF_SCOPE)) {
|
|
2083
|
-
element.setAttribute(BF_SCOPE, scopeId);
|
|
2084
|
-
}
|
|
2085
2098
|
container.innerHTML = "";
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2099
|
+
if (rootElements.length === 1) {
|
|
2100
|
+
const element = rootElements[0];
|
|
2101
|
+
if (!element.getAttribute(BF_SCOPE)) {
|
|
2102
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
2103
|
+
}
|
|
2104
|
+
container.appendChild(element);
|
|
2105
|
+
init(element, props);
|
|
2106
|
+
hydratedScopes.add(element);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const commentNode = document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`);
|
|
2110
|
+
container.appendChild(commentNode);
|
|
2111
|
+
for (const node of Array.from(tpl.content.childNodes)) {
|
|
2112
|
+
container.appendChild(node);
|
|
2113
|
+
}
|
|
2114
|
+
const proxyEl = rootElements[0];
|
|
2115
|
+
commentScopeRegistry.set(proxyEl, { commentNode, scopeId });
|
|
2116
|
+
init(proxyEl, props);
|
|
2117
|
+
hydratedScopes.add(proxyEl);
|
|
2089
2118
|
}
|
|
2090
2119
|
// src/runtime/streaming.ts
|
|
2091
2120
|
function __bf_swap(id) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA4LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,
|
|
1
|
+
{"version":3,"file":"map-array.d.ts","sourceRoot":"","sources":["../../src/runtime/map-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA4LH;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,QAAQ,EAAE,MAAM,CAAC,EAAE,EACnB,SAAS,EAAE,WAAW,GAAG,IAAI,EAC7B,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,IAAI,EACnD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,KAAK,WAAW,EACjF,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAwKN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/runtime/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/runtime/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAU,MAAM,SAAS,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,MAAM,CACpB,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAiFN"}
|
|
@@ -1107,6 +1107,8 @@ function hydrateCommentScope(comment) {
|
|
|
1107
1107
|
const proxyEl = nextElementSibling2(comment) ?? comment.parentElement;
|
|
1108
1108
|
if (!proxyEl)
|
|
1109
1109
|
return;
|
|
1110
|
+
if (hydratedScopes.has(proxyEl))
|
|
1111
|
+
return;
|
|
1110
1112
|
commentScopeRegistry.set(proxyEl, { commentNode: comment, scopeId });
|
|
1111
1113
|
const parsed = parseProps(propsJson || null, `comment scope ${scopeId}`);
|
|
1112
1114
|
const props = parsed[name] ?? {};
|
|
@@ -1160,9 +1162,14 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
1160
1162
|
}
|
|
1161
1163
|
return result;
|
|
1162
1164
|
});
|
|
1165
|
+
const def = getRegisteredDef(name);
|
|
1166
|
+
const isCommentWrapper = def?.comment === true;
|
|
1167
|
+
const scopeId = isCommentWrapper ? null : `${name}_${generateId()}`;
|
|
1163
1168
|
const prevParentScopeId = _parentScopeId;
|
|
1164
1169
|
if (slot?.parent) {
|
|
1165
1170
|
_parentScopeId = slot.parent;
|
|
1171
|
+
} else if (scopeId) {
|
|
1172
|
+
_parentScopeId = scopeId;
|
|
1166
1173
|
}
|
|
1167
1174
|
let html;
|
|
1168
1175
|
try {
|
|
@@ -1175,10 +1182,8 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
1175
1182
|
console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`);
|
|
1176
1183
|
return createPlaceholder(name, key);
|
|
1177
1184
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
if (!isCommentWrapper) {
|
|
1181
|
-
element.setAttribute(BF_SCOPE, `${name}_${generateId()}`);
|
|
1185
|
+
if (scopeId) {
|
|
1186
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
1182
1187
|
}
|
|
1183
1188
|
if (slot) {
|
|
1184
1189
|
if (slot.parent)
|
|
@@ -1822,6 +1827,17 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
|
|
|
1822
1827
|
scopes.set(key, scope);
|
|
1823
1828
|
insertScope(scope, container, anchor);
|
|
1824
1829
|
}
|
|
1830
|
+
for (let i = items.length;i < existingRanges.length; i++) {
|
|
1831
|
+
const range = existingRanges[i];
|
|
1832
|
+
if (range.startMarker?.parentNode)
|
|
1833
|
+
range.startMarker.remove();
|
|
1834
|
+
if (range.primaryEl.parentNode)
|
|
1835
|
+
range.primaryEl.remove();
|
|
1836
|
+
for (const ex of range.extras) {
|
|
1837
|
+
if (ex.parentNode)
|
|
1838
|
+
ex.remove();
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1825
1841
|
return;
|
|
1826
1842
|
}
|
|
1827
1843
|
}
|
|
@@ -2260,17 +2276,30 @@ function render(container, nameOrDef, props = {}) {
|
|
|
2260
2276
|
}
|
|
2261
2277
|
const tpl = document.createElement("template");
|
|
2262
2278
|
tpl.innerHTML = html;
|
|
2263
|
-
const
|
|
2264
|
-
if (
|
|
2279
|
+
const rootElements = Array.from(tpl.content.childNodes).filter((n) => n.nodeType === Node.ELEMENT_NODE);
|
|
2280
|
+
if (rootElements.length === 0) {
|
|
2265
2281
|
throw new Error("[BarefootJS] render(): template returned empty HTML");
|
|
2266
2282
|
}
|
|
2267
|
-
if (!element.getAttribute(BF_SCOPE)) {
|
|
2268
|
-
element.setAttribute(BF_SCOPE, scopeId);
|
|
2269
|
-
}
|
|
2270
2283
|
container.innerHTML = "";
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2284
|
+
if (rootElements.length === 1) {
|
|
2285
|
+
const element = rootElements[0];
|
|
2286
|
+
if (!element.getAttribute(BF_SCOPE)) {
|
|
2287
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
2288
|
+
}
|
|
2289
|
+
container.appendChild(element);
|
|
2290
|
+
init(element, props);
|
|
2291
|
+
hydratedScopes.add(element);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const commentNode = document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`);
|
|
2295
|
+
container.appendChild(commentNode);
|
|
2296
|
+
for (const node of Array.from(tpl.content.childNodes)) {
|
|
2297
|
+
container.appendChild(node);
|
|
2298
|
+
}
|
|
2299
|
+
const proxyEl = rootElements[0];
|
|
2300
|
+
commentScopeRegistry.set(proxyEl, { commentNode, scopeId });
|
|
2301
|
+
init(proxyEl, props);
|
|
2302
|
+
hydratedScopes.add(proxyEl);
|
|
2274
2303
|
}
|
|
2275
2304
|
// src/runtime/streaming.ts
|
|
2276
2305
|
function __bf_swap(id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"directory": "packages/client"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@barefootjs/shared": "0.
|
|
58
|
+
"@barefootjs/shared": "0.3.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@barefootjs/jsx": "0.2.0"
|
|
61
|
+
"@barefootjs/jsx": ">=0.2.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"@barefootjs/jsx": {
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@barefootjs/jsx": "0.2.0",
|
|
70
69
|
"@happy-dom/global-registrator": "^20.0.11",
|
|
71
70
|
"typescript": "^5.0.0"
|
|
72
71
|
}
|
package/src/runtime/component.ts
CHANGED
|
@@ -124,13 +124,31 @@ export function createComponent(
|
|
|
124
124
|
return result
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
-
// 4.
|
|
127
|
+
// 4. Pre-generate the component's scope ID.
|
|
128
128
|
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
129
|
+
// `comment: true` components (synthesized inline-JSX-callback wrappers
|
|
130
|
+
// from #1211) render as transparent shells — the parsed `firstChild` is
|
|
131
|
+
// already the inner component's root with its own bf-s. Don't overwrite
|
|
132
|
+
// it (scopeId stays null), or `$c(__scope, 's0')` from the wrapper's
|
|
133
|
+
// init resolves to null.
|
|
134
|
+
const def = getRegisteredDef(name)
|
|
135
|
+
const isCommentWrapper = def?.comment === true
|
|
136
|
+
const scopeId = isCommentWrapper ? null : `${name}_${generateId()}`
|
|
137
|
+
|
|
138
|
+
// 5. Generate HTML from props.
|
|
139
|
+
//
|
|
140
|
+
// Thread the component's own scope ID into `_parentScopeId` for the
|
|
141
|
+
// template eval so renderChild() stamps parent-prefixed bf-s / bf-h /
|
|
142
|
+
// bf-m on child components — matching the SSR convention so a later
|
|
143
|
+
// `$c(scope, 'sN')` lookup resolves them. Without this, CSR-created
|
|
144
|
+
// children carry a random prefix and their event handlers never wire
|
|
145
|
+
// up (#1627). `slot.parent` takes precedence so hoisted-children
|
|
146
|
+
// placeholders (#1320) still resolve to the calling site's scope.
|
|
131
147
|
const prevParentScopeId = _parentScopeId
|
|
132
148
|
if (slot?.parent) {
|
|
133
149
|
_parentScopeId = slot.parent
|
|
150
|
+
} else if (scopeId) {
|
|
151
|
+
_parentScopeId = scopeId
|
|
134
152
|
}
|
|
135
153
|
let html: string
|
|
136
154
|
try {
|
|
@@ -139,7 +157,7 @@ export function createComponent(
|
|
|
139
157
|
_parentScopeId = prevParentScopeId
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
//
|
|
160
|
+
// 6. Create DOM element
|
|
143
161
|
const element = parseHTML(html.trim()).firstChild as HTMLElement
|
|
144
162
|
|
|
145
163
|
if (!element) {
|
|
@@ -147,16 +165,9 @@ export function createComponent(
|
|
|
147
165
|
return createPlaceholder(name, key)
|
|
148
166
|
}
|
|
149
167
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// from #1211) render as transparent shells — the parsed `firstChild` is
|
|
154
|
-
// already the inner component's root with its own bf-s. Don't overwrite
|
|
155
|
-
// it, or `$c(__scope, 's0')` from the wrapper's init resolves to null.
|
|
156
|
-
const def = getRegisteredDef(name)
|
|
157
|
-
const isCommentWrapper = def?.comment === true
|
|
158
|
-
if (!isCommentWrapper) {
|
|
159
|
-
element.setAttribute(BF_SCOPE, `${name}_${generateId()}`)
|
|
168
|
+
// 7. Set scope ID and key attributes.
|
|
169
|
+
if (scopeId) {
|
|
170
|
+
element.setAttribute(BF_SCOPE, scopeId)
|
|
160
171
|
}
|
|
161
172
|
if (slot) {
|
|
162
173
|
if (slot.parent) element.setAttribute(BF_HOST, slot.parent)
|
|
@@ -166,18 +177,18 @@ export function createComponent(
|
|
|
166
177
|
element.setAttribute(BF_KEY, String(key))
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
//
|
|
180
|
+
// 8. Set currentScope so provideContext/useContext are element-scoped.
|
|
170
181
|
// This allows context providers in initFn to store context on this element.
|
|
171
182
|
const prevScope = setCurrentScope(element)
|
|
172
183
|
|
|
173
|
-
//
|
|
184
|
+
// 9. Initialize the component (context providers set up here).
|
|
174
185
|
const initFn = getComponentInit(name)
|
|
175
186
|
if (initFn) {
|
|
176
187
|
// Pass original props (with getters) for reactivity
|
|
177
188
|
initFn(element, props)
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
//
|
|
191
|
+
// 10. Evaluate getter children and insert them.
|
|
181
192
|
// Children are evaluated NOW (after initFn) so that context provided by
|
|
182
193
|
// the parent is in the global store when children call useContext().
|
|
183
194
|
if (childrenIsGetter) {
|
|
@@ -187,13 +198,13 @@ export function createComponent(
|
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
//
|
|
201
|
+
// 11. Restore previous scope
|
|
191
202
|
setCurrentScope(prevScope)
|
|
192
203
|
|
|
193
|
-
//
|
|
204
|
+
// 12. Mark element as initialized
|
|
194
205
|
hydratedScopes.add(element)
|
|
195
206
|
|
|
196
|
-
//
|
|
207
|
+
// 13. Store props and register update function for element reuse in reconcileList
|
|
197
208
|
propsMap.set(element, props)
|
|
198
209
|
registerPropsUpdate(element, name, props)
|
|
199
210
|
|
package/src/runtime/hydrate.ts
CHANGED
|
@@ -294,6 +294,12 @@ function hydrateCommentScope(comment: Comment): void {
|
|
|
294
294
|
const proxyEl = nextElementSibling(comment) ?? comment.parentElement
|
|
295
295
|
if (!proxyEl) return
|
|
296
296
|
|
|
297
|
+
// A synchronous CSR render() may have already initialized this fragment
|
|
298
|
+
// scope and claimed its proxy before this async walk runs. Honour the same
|
|
299
|
+
// `hydratedScopes` signal the element-scope path uses (hydrateElementScope),
|
|
300
|
+
// so a comment-rooted scope is never re-initialized once claimed.
|
|
301
|
+
if (hydratedScopes.has(proxyEl)) return
|
|
302
|
+
|
|
297
303
|
commentScopeRegistry.set(proxyEl, { commentNode: comment, scopeId })
|
|
298
304
|
|
|
299
305
|
const parsed = parseProps(propsJson || null, `comment scope ${scopeId}`)
|
package/src/runtime/map-array.ts
CHANGED
|
@@ -277,6 +277,17 @@ export function mapArray<T>(
|
|
|
277
277
|
scopes.set(key, scope)
|
|
278
278
|
insertScope(scope, container, anchor)
|
|
279
279
|
}
|
|
280
|
+
|
|
281
|
+
// If client has fewer items than SSR rendered, remove orphaned nodes
|
|
282
|
+
for (let i = items.length; i < existingRanges.length; i++) {
|
|
283
|
+
const range = existingRanges[i]
|
|
284
|
+
if (range.startMarker?.parentNode) range.startMarker.remove()
|
|
285
|
+
if (range.primaryEl.parentNode) range.primaryEl.remove()
|
|
286
|
+
for (const ex of range.extras) {
|
|
287
|
+
if (ex.parentNode) ex.remove()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
280
291
|
return // Hydration complete — effects handle future updates
|
|
281
292
|
}
|
|
282
293
|
}
|
package/src/runtime/render.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* never import this module.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { BF_SCOPE } from '@barefootjs/shared'
|
|
9
|
+
import { BF_SCOPE, BF_SCOPE_COMMENT_PREFIX } from '@barefootjs/shared'
|
|
10
10
|
import { setParentScopeId } from './component'
|
|
11
11
|
import { hydratedScopes } from './hydration-state'
|
|
12
12
|
import { getComponentInit } from './registry'
|
|
13
|
+
import { commentScopeRegistry } from './scope'
|
|
13
14
|
import { getTemplate, type TemplateFn } from './template'
|
|
14
15
|
import type { ComponentDef, InitFn } from './types'
|
|
15
16
|
|
|
@@ -86,20 +87,41 @@ export function render(
|
|
|
86
87
|
|
|
87
88
|
const tpl = document.createElement('template')
|
|
88
89
|
tpl.innerHTML = html
|
|
89
|
-
const element = tpl.content.firstChild as HTMLElement
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
const rootElements = Array.from(tpl.content.childNodes).filter(
|
|
92
|
+
(n): n is HTMLElement => n.nodeType === Node.ELEMENT_NODE
|
|
93
|
+
)
|
|
94
94
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
95
|
+
if (rootElements.length === 0) {
|
|
96
|
+
throw new Error('[BarefootJS] render(): template returned empty HTML')
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
container.innerHTML = ''
|
|
100
|
-
container.appendChild(element)
|
|
101
100
|
|
|
102
|
-
|
|
101
|
+
if (rootElements.length === 1) {
|
|
102
|
+
const element = rootElements[0]
|
|
103
|
+
if (!element.getAttribute(BF_SCOPE)) {
|
|
104
|
+
element.setAttribute(BF_SCOPE, scopeId)
|
|
105
|
+
}
|
|
106
|
+
container.appendChild(element)
|
|
107
|
+
init(element, props)
|
|
108
|
+
hydratedScopes.add(element)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Multi-root (fragment) template: the child scopes are siblings, not
|
|
113
|
+
// descendants of one root, so $c() can't resolve them by subtree walk.
|
|
114
|
+
// Recreate the SSR fragment layout — a `bf-scope:` comment marker followed
|
|
115
|
+
// by the sibling roots — and register it so candidatesInScope() walks the
|
|
116
|
+
// comment range. Without this only the first root would hydrate.
|
|
117
|
+
const commentNode = document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`)
|
|
118
|
+
container.appendChild(commentNode)
|
|
119
|
+
for (const node of Array.from(tpl.content.childNodes)) {
|
|
120
|
+
container.appendChild(node)
|
|
121
|
+
}
|
|
103
122
|
|
|
104
|
-
|
|
123
|
+
const proxyEl = rootElements[0]
|
|
124
|
+
commentScopeRegistry.set(proxyEl, { commentNode, scopeId })
|
|
125
|
+
init(proxyEl, props)
|
|
126
|
+
hydratedScopes.add(proxyEl)
|
|
105
127
|
}
|