🗿
Svelte5でコンポーネントとSnippetを統一的に扱うメモ
色々処理の共通化などを模索しているとSvelteコンポーネントとSnippetを統一的に扱いたくなることがありました。その時に調べた内容を備忘として残しておきます。おまけで文字列も統一的に扱う例にしています。Svelteのバージョンアップにより、そのうちもっとスマートにできるようになる気がします。
具体的な処理
Snippetに統一する場合
code
App.svelte
<script module lang="ts">
import { castCompToSnippet, castStrToSnippet } from "./utils.svelte";
import Comp from "./Comp.svelte";
const str = "This is String";
</script>
<script lang="ts">
const comp2Snippet = castCompToSnippet(Comp);
const str2Snippet = castStrToSnippet(str);
const snpt2Snippet = Snpt;
</script>
{#snippet Snpt()}
<div>
<b>This is Snippet</b>
</div>
{/snippet}
<!--- Render as Snippet ------------------------------>
{@render comp2Snippet()}
{@render str2Snippet()}
{@render snpt2Snippet()}
<!---------------------------------------------------->
<style>
:global(em) { color: red; }
:global(b) { color: blue; }
:global(i) { color: green; }
:global(em, b, i) {
font-weight: 100;
font-style: normal;
}
</style>
Comp.svelte
<em>This is Component</em>
utils.svelte
<script module lang="ts">
import { type Component, type Snippet, createRawSnippet, hydrate } from "svelte";
import { render } from "svelte/server";
const browser = typeof window !== undefined // usually used `import { browser } from "$app/environment"`;
type RawSnippetProps = (() => Record<string, any>) | undefined;
export function castCompToSnippet(comp: Component) {
return createRawSnippet((props: RawSnippetProps) => ({
render: () => `<div>${browser ? "" : svrender(comp).body}</div>`,
setup(target: Element) {
hydrate(comp, { target, props: props?.() });
}
}));
}
export function castStrToSnippet(str: string) {
return createRawSnippet(() => ({ render: () => `<div><i>${str}</i></div>` }));
}
</script>
簡単な内容
-
createRawSnippet
を用いて全てSnippet
に変換
コンポーネントを挟む場合
code
App.svelte
<script module lang="ts">
import RenderAdapter from "./RenderAdapter.svelte";
import Comp from "./Comp.svelte";
const str = "This is String";
</script>
{#snippet Snpt()}
<div>
<b>This is Snippet</b>
</div>
{/snippet}
<!--- Render with Component --------------------------->
<RenderAdapter item={Comp} />
<RenderAdapter item={str} />
<RenderAdapter item={Snpt} />
<!----------------------------------------------------->
<style>
:global(em) { color: red; }
:global(b) { color: blue; }
:global(i) { color: green; }
:global(em, b, i) {
font-weight: 100;
font-style: normal;
}
</style>
Comp.svelte
<em>This is Component</em>
RenderAdapter.svelte
<script module lang="ts">
import type { Snippet, Component } from "svelte";
type Props = {
item: Snippet | Component | string;
[key: string]: unknown,
};
function isSnippet(item: Snippet | Component): boolean {
return item.length === 1;
}
function isComponent(item: Snippet | Component): boolean {
return item.length === 2;
}
</script>
<script lang="ts">
const { item, ...rest }: Props = $props();
</script>
{#if typeof item === "string"}
<div><i>{item}</i></div>
{:else if isSnippet(item)}
{@render item()}
{:else if isComponent(item)}
{@const Item = item}
<Item {...rest} />
{/if}
簡単な内容
- コンポーネント内で
props
の型を判別し、型に応じて表示
雑記
Svelte5がリリースされてから公式サイトが非常に見にくくなったと思うのは私だけなんでしょうか。フォントもそうだし、チュートリアル項目の俯瞰もやりにくくてしょうがないです。個人的には前の方が良かったな~
Discussion