Skip to content

Instantly share code, notes, and snippets.

@ryuheechul
Last active March 8, 2025 07:50
Show Gist options
  • Save ryuheechul/8bfa44574e96a80fdda1c9de7bb1c57b to your computer and use it in GitHub Desktop.
Save ryuheechul/8bfa44574e96a80fdda1c9de7bb1c57b to your computer and use it in GitHub Desktop.

Revisions

  1. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -102,6 +102,8 @@ import Layout from '../layouts/Layout.astro';
    </Layout>
    ```

    _`<span>` above is not important but to demonstrate what I learned from https://blog.jim-nielsen.com/2023/html-web-components/._

    #### But why client side `<script>` is required here?

    Why not just `import SvelteCounter from '../components/SvelteCounter.svelte';` at the top between `---` and `---`?
  2. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -94,7 +94,10 @@ import Layout from '../layouts/Layout.astro';
    <details open>
    <!-- ... -->
    <svelte-counter message="as custom element" />
    <svelte-counter message="as custom element">
    <!-- You can also (pre) render something before the custom element is rendered like this ... -->
    <span>initializing...</span>
    </svelte-counter>
    </details>
    </Layout>
    ```
  3. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -153,7 +153,7 @@ import Layout from '../layouts/Layout.astro';
    </Layout>
    ```
    ### Svelte config
    ### Svelte config (via astro.config.mjs)

    ```mjs
    // astro.config.mjs
  4. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 11 additions and 12 deletions.
    23 changes: 11 additions & 12 deletions svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -20,23 +20,22 @@ There is a demo shows you the minimal code and working example of how to set up
    <svelte:options customElement={{ tag: "svelte-counter" }} />
    <script>
    let count = $state(0);
    let count = $state(0);
    function handleClick() {
    count += 1;
    }
    let { message = 'default message' } = $props();
    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>
    <summary>
    { message }:
    </summary>
    <button style="margin-left:1rem;" on:click={handleClick}>
    clicks: {count}
    </button>
    </details>
    ```

  5. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -142,7 +142,7 @@ import Layout from '../layouts/Layout.astro';
    }
    </script>
    <details open>>
    <details open>
    <!-- ... -->
    <div style="margin: 1rem;" x-data="{list:[randomAnimal()]}">
    <button @click="list.push(randomAnimal())">click to add more</button>
    @@ -172,6 +172,12 @@ export default defineConfig({
    },
    }),
    ],
    // this section is not necessary but could be helpful to see authored source code with the production build (it's already available at development build though)
    vite: {
    build: {
    sourcemap: true,
    },
    },
    });
    ```

  6. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -100,6 +100,18 @@ import Layout from '../layouts/Layout.astro';
    </Layout>
    ```

    #### But why client side `<script>` is required here?

    Why not just `import SvelteCounter from '../components/SvelteCounter.svelte';` at the top between `---` and `---`?

    Anything in between `---` and `---` at the top of `.astro` files is processed in compiling time.
    The code here will not be directly embeded into an html.
    In the case of `mixed.astro`, it was a happy coincedence that astro processed the svelte renderer when a svelte client island is hydrated which happens to run the code of registering the custom element as well as hydraing.
    If you simply import between `---` and `---` and not actually embed `<SvelteCounter>`, it is simply ignored.

    Adding `<script>import {} from '../components/SvelteCounter.svelte';</script>` will basically do a similar thing without hydrating since there is none to hydrate but to register a CE. Also this code runs as `type="module"` by default as that's how Astro works which means it's automatically `defer`ed.


    ### with AlpineJS

    This usage is why I was interested in utilizing Svelte's custom element compiling in the first place.
  7. ryuheechul revised this gist Mar 8, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -89,7 +89,7 @@ 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
    // unlike 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>
    @@ -114,7 +114,7 @@ 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
    // unlike 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>
  8. ryuheechul created this gist Mar 8, 2025.
    166 changes: 166 additions & 0 deletions svelte-custom-element-on-astro.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,166 @@
    # 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](https://svelte.dev/docs/svelte/custom-elements) at https://stackblitz.com/edit/svelte-ce-on-astro-demo?file=README.md.


    ## And here is the gist

    - Svelte allow you to compile its component to a custom element (aka Web Component)
    - https://svelte.dev/docs/svelte/custom-elements
    - Astro doesn't have a guide on how to do this but I figured out how to and thus the sharing
    - https://stackblitz.com/edit/svelte-ce-on-astro-demo?file=README.md

    ## Tour of the main code

    ### The component
    ```svelte
    <!-- 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

    ```astro
    ---
    // 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

    ```astro
    ---
    // 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

    ```astro
    ---
    // 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.

    ```astro
    ---
    // 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

    ```mjs
    // 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.