Skip to content

Instantly share code, notes, and snippets.

@ryuheechul
Last active March 8, 2025 07:50
Show Gist options
  • Select an option

  • Save ryuheechul/8bfa44574e96a80fdda1c9de7bb1c57b to your computer and use it in GitHub Desktop.

Select an option

Save ryuheechul/8bfa44574e96a80fdda1c9de7bb1c57b to your computer and use it in GitHub Desktop.
Svelte Custom Element on Astro

Svelte Custom Element on Astro

There is a demo shows you the minimal code and working example of how to set up a astro project to make use of Svelte compiles to Custom Element feature at https://stackblitz.com/edit/svelte-ce-on-astro-demo?file=README.md.

And here is the gist

Tour of the main code

The component

<!-- SvelteCounter.svelte -->

<!-- this one file transforms into a regular svelte component and a custom element thanks to the line below -->
<svelte:options customElement={{ tag: "svelte-counter" }} />

<script>
	let count = $state(0);
	
	function handleClick() {
		count += 1;
	}

	let { message = 'default message' } = $props();

</script>

<details open style="margin: 1rem;">
	<summary>
		{ message }:
	</summary>
	<button style="margin-left:1rem;" on:click={handleClick}>
		clicks: {count}
	</button>
</details>

Good old Astro client island from a Svelte component

---
// pages/regular.astro

import Layout from '../layouts/Layout.astro';
import SvelteCounter from '../components/SvelteCounter.svelte';
---

<Layout>
  <details open>
    <!-- ... -->
    <SvelteCounter client:load message="regular hydration" />
  </details>
</Layout>

Mixed usage that lets you think it just works

---
// pages/mixed.astro

import Layout from '../layouts/Layout.astro';
import SvelteCounter from '../components/SvelteCounter.svelte';
---

<Layout>
  <SvelteCounter client:load message="regular hydration" />
  
  <details open>
    <!-- ... -->
    <svelte-counter message="as custom element" />
  </details>
</Layout>

Standalone usage reveals the extra import required

---
// pages/as-web-comp.astro

import Layout from '../layouts/Layout.astro';
---

<Layout>
  <script>
    // unline the case with ./mixed.astro, we will need to import this on the client side to register the custom element
    import {} from '../components/SvelteCounter.svelte';
  </script>

  <details open>
    <!-- ... -->
    <svelte-counter message="as custom element" />
  </details>
</Layout>

with AlpineJS

This usage is why I was interested in utilizing Svelte's custom element compiling in the first place.

---
// pages/with-alpine.astro

import Layout from '../layouts/Layout.astro';
---

<Layout>
  <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
  <script>
    // unline the case with ./mixed.astro, we will need to import this on the client side to register the custom element
    import {} from '../components/SvelteCounter.svelte';

  </script>
  <script is:inline>
    const animals = ["pig", "cow", "lion", "elephant"];

    function getRandomInt(max) {
      return Math.floor(Math.random() * max);
    }

    function randomAnimal() {
      return animals[getRandomInt(animals.length)];
    }
  </script>

  <details open>>
    <!-- ... -->
    <div style="margin: 1rem;" x-data="{list:[randomAnimal()]}">
      <button @click="list.push(randomAnimal())">click to add more</button>
      <template x-for="(item, i) in list"> 
        <svelte-counter x-bind:message="'' + (i+1) + '-' + item" />
      </template>
    </div>
  </details>
</Layout>

Svelte config

// astro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';

import svelte from '@astrojs/svelte';

// https://astro.build/config
export default defineConfig({
  integrations: [
    svelte({
      compilerOptions: {
        customElement: true, // this is the one that what should tell Svelte to compile components to CEs
      },
    }),
  ],
});

There are a bit more code you can take a look and also get to interact with the demo at https://stackblitz.com/edit/svelte-ce-on-astro-demo?file=README.md.

@ryuheechul
Copy link
Author

ryuheechul commented Mar 8, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment