Skip to content

Commit 5d07e6e

Browse files
fix: island rendered outside of bounding markers (#1495)
1 parent 3ab35a4 commit 5d07e6e

File tree

9 files changed

+130
-29
lines changed

9 files changed

+130
-29
lines changed

src/runtime/entrypoints/main.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ function createRootFragment(
2121
parentNode: parent,
2222
firstChild: replaceNode[0],
2323
childNodes: replaceNode,
24-
insertBefore(node: Node, child: Node) {
25-
parent.insertBefore(node, child);
24+
insertBefore(node: Node, child: Node | null) {
25+
parent.insertBefore(node, child ?? endMarker);
2626
},
2727
appendChild(child: Node) {
2828
// We cannot blindly call `.append()` as that would add
@@ -305,7 +305,14 @@ function _walkInner(
305305
_walkInner(islands, props, markerStack, vnodeStack, sib.firstChild);
306306
}
307307

308-
if (marker !== null && marker.kind === MarkerKind.Slot) {
308+
// Pop vnode if current marker is a slot or we are an island marker
309+
// that was created inside another island
310+
if (
311+
marker !== null &&
312+
(marker.kind === MarkerKind.Slot ||
313+
markerStack.length > 1 &&
314+
markerStack[markerStack.length - 2].kind === MarkerKind.Island)
315+
) {
309316
vnodeStack.pop();
310317
}
311318
}

tests/fixture_island_nesting/deno.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
},
66
"imports": {
77
"$fresh/": "../../",
8-
"preact": "https://esm.sh/preact@10.15.1",
9-
"preact/": "https://esm.sh/preact@10.15.1/",
8+
"preact": "https://esm.sh/preact@10.16.0",
9+
"preact/": "https://esm.sh/preact@10.16.0/",
1010
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.0",
1111
"@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
1212
"@preact/signals-core": "https://esm.sh/@preact/signals-core@1.2.3"

tests/fixture_island_nesting/fresh.gen.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,49 @@
44

55
import * as $0 from "./routes/index.tsx";
66
import * as $1 from "./routes/island_conditional.tsx";
7-
import * as $2 from "./routes/island_in_island.tsx";
8-
import * as $3 from "./routes/island_in_island_definition.tsx";
9-
import * as $4 from "./routes/island_jsx_child.tsx";
10-
import * as $5 from "./routes/island_jsx_children.tsx";
11-
import * as $6 from "./routes/island_jsx_island_jsx.tsx";
12-
import * as $7 from "./routes/island_jsx_text.tsx";
13-
import * as $8 from "./routes/island_nested_props.tsx";
14-
import * as $9 from "./routes/island_siblings.tsx";
7+
import * as $2 from "./routes/island_fn_child.tsx";
8+
import * as $3 from "./routes/island_in_island.tsx";
9+
import * as $4 from "./routes/island_in_island_definition.tsx";
10+
import * as $5 from "./routes/island_jsx_child.tsx";
11+
import * as $6 from "./routes/island_jsx_children.tsx";
12+
import * as $7 from "./routes/island_jsx_island_jsx.tsx";
13+
import * as $8 from "./routes/island_jsx_text.tsx";
14+
import * as $9 from "./routes/island_nested_props.tsx";
15+
import * as $10 from "./routes/island_order.tsx";
16+
import * as $11 from "./routes/island_siblings.tsx";
1517
import * as $$0 from "./islands/BooleanButton.tsx";
16-
import * as $$1 from "./islands/Island.tsx";
17-
import * as $$2 from "./islands/IslandConditional.tsx";
18-
import * as $$3 from "./islands/IslandInsideIsland.tsx";
19-
import * as $$4 from "./islands/IslandWithProps.tsx";
18+
import * as $$1 from "./islands/FragmentIsland.tsx";
19+
import * as $$2 from "./islands/Island.tsx";
20+
import * as $$3 from "./islands/IslandCenter.tsx";
21+
import * as $$4 from "./islands/IslandConditional.tsx";
22+
import * as $$5 from "./islands/IslandFn.tsx";
23+
import * as $$6 from "./islands/IslandInsideIsland.tsx";
24+
import * as $$7 from "./islands/IslandWithProps.tsx";
2025

2126
const manifest = {
2227
routes: {
2328
"./routes/index.tsx": $0,
2429
"./routes/island_conditional.tsx": $1,
25-
"./routes/island_in_island.tsx": $2,
26-
"./routes/island_in_island_definition.tsx": $3,
27-
"./routes/island_jsx_child.tsx": $4,
28-
"./routes/island_jsx_children.tsx": $5,
29-
"./routes/island_jsx_island_jsx.tsx": $6,
30-
"./routes/island_jsx_text.tsx": $7,
31-
"./routes/island_nested_props.tsx": $8,
32-
"./routes/island_siblings.tsx": $9,
30+
"./routes/island_fn_child.tsx": $2,
31+
"./routes/island_in_island.tsx": $3,
32+
"./routes/island_in_island_definition.tsx": $4,
33+
"./routes/island_jsx_child.tsx": $5,
34+
"./routes/island_jsx_children.tsx": $6,
35+
"./routes/island_jsx_island_jsx.tsx": $7,
36+
"./routes/island_jsx_text.tsx": $8,
37+
"./routes/island_nested_props.tsx": $9,
38+
"./routes/island_order.tsx": $10,
39+
"./routes/island_siblings.tsx": $11,
3340
},
3441
islands: {
3542
"./islands/BooleanButton.tsx": $$0,
36-
"./islands/Island.tsx": $$1,
37-
"./islands/IslandConditional.tsx": $$2,
38-
"./islands/IslandInsideIsland.tsx": $$3,
39-
"./islands/IslandWithProps.tsx": $$4,
43+
"./islands/FragmentIsland.tsx": $$1,
44+
"./islands/Island.tsx": $$2,
45+
"./islands/IslandCenter.tsx": $$3,
46+
"./islands/IslandConditional.tsx": $$4,
47+
"./islands/IslandFn.tsx": $$5,
48+
"./islands/IslandInsideIsland.tsx": $$6,
49+
"./islands/IslandWithProps.tsx": $$7,
4050
},
4151
baseUrl: import.meta.url,
4252
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function FragmentIsland() {
2+
return (
3+
<>
4+
<p>it{" "}</p>
5+
<p>works</p>
6+
</>
7+
);
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function IslandCenter() {
2+
return <p class="island">center</p>;
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { VNode } from "preact";
2+
3+
import FragmentIsland from "./FragmentIsland.tsx";
4+
5+
function Foo(props: { children: () => VNode }) {
6+
return props.children();
7+
}
8+
9+
export default function IslandFn() {
10+
return (
11+
<div class="island">
12+
<Foo>
13+
{() => <FragmentIsland />}
14+
</Foo>
15+
</div>
16+
);
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import IslandFn from "../islands/IslandFn.tsx";
2+
3+
export default function Home() {
4+
return (
5+
<div id="page">
6+
<IslandFn />
7+
</div>
8+
);
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import IslandCenter from "../islands/IslandCenter.tsx";
2+
3+
export default function IslandOrder() {
4+
return (
5+
<div id="page">
6+
<p>left</p>
7+
<IslandCenter />
8+
<p>right</p>
9+
</div>
10+
);
11+
}

tests/islands_test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,39 @@ Deno.test({
442442
sanitizeOps: false,
443443
sanitizeResources: false,
444444
});
445+
446+
Deno.test({
447+
name: "render island inside island when passed as fn child",
448+
449+
async fn(_t) {
450+
await withPageName(
451+
"./tests/fixture_island_nesting/main.ts",
452+
async (page, address) => {
453+
await page.goto(`${address}/island_fn_child`);
454+
await page.waitForSelector(".island");
455+
await waitForText(page, "#page", "it works");
456+
},
457+
);
458+
},
459+
460+
sanitizeOps: false,
461+
sanitizeResources: false,
462+
});
463+
464+
Deno.test({
465+
name: "render nested islands in correct order",
466+
467+
async fn(_t) {
468+
await withPageName(
469+
"./tests/fixture_island_nesting/main.ts",
470+
async (page, address) => {
471+
await page.goto(`${address}/island_order`);
472+
await page.waitForSelector(".island");
473+
await waitForText(page, "#page", "leftcenterright");
474+
},
475+
);
476+
},
477+
478+
sanitizeOps: false,
479+
sanitizeResources: false,
480+
});

0 commit comments

Comments
 (0)