-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support islands having slots, including named slots (#22)
* feat: support islands having slots, including named slots
- Loading branch information
Showing
14 changed files
with
541 additions
and
6,198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,142 @@ | ||
import type { SvelteComponent } from "svelte"; | ||
import { SvelteComponent } from "svelte"; | ||
import type { Callback } from "./directives/types.js"; | ||
|
||
export function hydrate(load: Callback) { | ||
class Island extends HTMLElement { | ||
private instance?: SvelteComponent<any, any, any>; | ||
// @ts-expect-error no export type definition | ||
import { detach, insert, noop } from "svelte/internal"; | ||
|
||
connectedCallback() { | ||
const target = this; | ||
// https://github.com/lukeed/freshie/blob/5930c2eb8008aac93dcdad1da730e620db327072/packages/%40freshie/ui.svelte/index.js#L20 | ||
function slotty(elem: Element | Comment) { | ||
return function (...args: any[]) { | ||
let frag: any = {}; | ||
|
||
load((Component) => { | ||
import("stack54/data").then(({ raw_decode }) => { | ||
const props = raw_decode(this.getAttribute("props")!) as any; | ||
this.instance = new Component({ props, target, hydrate: true }); | ||
}); | ||
}, target); | ||
frag.c = frag.c || noop; | ||
frag.l = frag.l || noop; | ||
|
||
frag.m = | ||
frag.m || | ||
function (target: any, anchor: any) { | ||
insert(target, elem, anchor); | ||
}; | ||
|
||
frag.d = | ||
frag.d || | ||
function (detaching: any) { | ||
if (detaching) detach(elem); | ||
}; | ||
|
||
return frag; | ||
}; | ||
} | ||
|
||
class Island extends HTMLElement { | ||
hydrated = false; | ||
|
||
private instance?: SvelteComponent<any, any, any>; | ||
|
||
// connectedCallback() { | ||
// if ( | ||
// !this.hasAttribute("await-children") || | ||
// document.readyState === "interactive" || | ||
// document.readyState === "complete" | ||
// ) { | ||
// this.childrenConnectedCallback(); | ||
// } else { | ||
// // connectedCallback may run *before* children are rendered (ex. HTML streaming) | ||
// // If SSR children are expected, but not yet rendered, wait with a mutation observer | ||
// // for a special marker inserted when rendering islands that signals the end of the island | ||
// const onConnected = () => { | ||
// document.removeEventListener("DOMContentLoaded", onConnected); | ||
// mo.disconnect(); | ||
// this.childrenConnectedCallback(); | ||
// }; | ||
|
||
// const mo = new MutationObserver(() => { | ||
// if ( | ||
// this.lastChild?.nodeType === Node.COMMENT_NODE && | ||
// this.lastChild.nodeValue === "astro:end" | ||
// ) { | ||
// this.lastChild.remove(); | ||
// onConnected(); | ||
// } | ||
// }); | ||
|
||
// mo.observe(this, { childList: true }); | ||
|
||
// // in case the marker comment got stripped and the mutation observer waited indefinitely, | ||
// // also wait for DOMContentLoaded as a last resort | ||
// document.addEventListener("DOMContentLoaded", onConnected); | ||
// } | ||
// } | ||
|
||
// childrenConnectedCallback() { | ||
// this.hydrate(); | ||
// } | ||
|
||
connectedCallback() { | ||
this.hydrate(); | ||
} | ||
|
||
hydrate = () => { | ||
const file = this.getAttribute("file"); | ||
const directive = this.getAttribute("directive"); | ||
|
||
const load: Callback = window[file! as keyof Window]; | ||
|
||
if (load === undefined) { | ||
window.addEventListener(`stack54:${directive}`, this.hydrate, { | ||
once: true, | ||
}); | ||
return; | ||
} | ||
|
||
disconnectedCallback() { | ||
this.instance?.$destroy(); | ||
const parent = this.parentElement?.closest("stack54-island"); | ||
|
||
// @ts-expect-error | ||
if (parent && !parent.hydrated) { | ||
parent.addEventListener("stack54:hydrate", this.hydrate, { | ||
once: true, | ||
}); | ||
return; | ||
} | ||
} | ||
|
||
if (!customElements.get("stack54-island")) { | ||
customElements.define("stack54-island", Island); | ||
const target = this; | ||
|
||
load((Component) => { | ||
import("stack54/data").then(({ raw_decode }) => { | ||
const props = raw_decode(this.getAttribute("props")!); | ||
const slots = this.querySelectorAll("stack54-slot"); | ||
|
||
const slotted: Array<[string, Element]> = []; | ||
|
||
for (let slot of slots) { | ||
const closest = slot.closest(this.tagName); | ||
if (!closest?.isSameNode(this)) continue; | ||
const name = slot.getAttribute("name") || "default"; | ||
slotted.push([name, slot]); | ||
} | ||
|
||
const _props = { | ||
...props, | ||
$$scope: {}, | ||
$$slots: Object.fromEntries( | ||
slotted.map(([k, _]) => [k, [slotty(_ as any)]]) | ||
), | ||
}; | ||
|
||
const opts = { target, props: _props, hydrate: true, $$inline: true }; | ||
this.instance = new Component(opts); | ||
|
||
this.hydrated = true; | ||
this.dispatchEvent(new CustomEvent("stack54:hydrate")); | ||
}); | ||
}, target); | ||
}; | ||
|
||
disconnectedCallback() { | ||
this.instance?.$destroy(); | ||
} | ||
} | ||
|
||
if (!customElements.get("stack54-island")) { | ||
customElements.define("stack54-island", Island); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
packages/core/test/apps/basic/src/views/island/counter.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<script> | ||
let count = 0; | ||
</script> | ||
|
||
<button data-testid="dec" on:click="{() => (count = count - 1)}">-</button> | ||
|
||
<p data-testid="text">{count}</p> | ||
|
||
<button data-testid="inc" on:click="{() => (count = count + 1)}">+</button> |
8 changes: 8 additions & 0 deletions
8
packages/core/test/apps/basic/src/views/island/no-slot.page.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<script lang="ts"> | ||
import Document from "../components/document.svelte"; | ||
import NoSlot from "./no-slot.svelte"; | ||
</script> | ||
|
||
<Document> | ||
<NoSlot /> | ||
</Document> |
10 changes: 10 additions & 0 deletions
10
packages/core/test/apps/basic/src/views/island/no-slot.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<script island="load" lang="ts"> | ||
import { onMount } from "svelte"; | ||
import Counter from "./counter.svelte"; | ||
let name: string | undefined; | ||
onMount(() => (name = "no slot")); | ||
</script> | ||
|
||
<p data-testid="no-slot">{name}</p> | ||
|
||
<Counter /> |
16 changes: 16 additions & 0 deletions
16
packages/core/test/apps/basic/src/views/island/with-slot.page.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<script> | ||
import Document from "../components/document.svelte"; | ||
import WithSlot from "./with-slot.svelte"; | ||
</script> | ||
|
||
<Document> | ||
<WithSlot> | ||
<div> | ||
<p>default</p> | ||
</div> | ||
|
||
<div slot="named"> | ||
<p>named</p> | ||
</div> | ||
</WithSlot> | ||
</Document> |
13 changes: 13 additions & 0 deletions
13
packages/core/test/apps/basic/src/views/island/with-slot.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<script island="load"> | ||
import Counter from "./counter.svelte"; | ||
</script> | ||
|
||
<Counter /> | ||
|
||
<div data-testid="default-slot"> | ||
<slot /> | ||
</div> | ||
|
||
<div data-testid="named-slot"> | ||
<slot name="named" /> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { defineConfig } from "stack54/config"; | ||
import express from "@stack54/express/plugin"; | ||
import island from "@stack54/island" | ||
|
||
export default defineConfig({ | ||
integrations: [express()], | ||
integrations: [express(), island()], | ||
}); |
Oops, something went wrong.