Svelte x 11ty
Tags: webdev, svelte, 11ty, javascript
Published: 2020-11-13 21:58:37

---
title: Svelte x 11ty
published: true
description: How to add svelte components to an 11ty site with prerendering and component hydration
tags: webdev, svelte, 11ty, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/a6ld0wukc3uhpjysijx6.jpg
---

# tldr
repo: https://github.com/gobeli/11ty-svelte
demo: https://gobeli.github.io/11ty-svelte (have a look at the network tab and see the prerendered markup)

# Intro
Back in June I wrote a post about prerendering svelte components. You can check it out [here](https://dev.to/gobeli/svelte-prerendering-5579). The article gives a basic overview of how you would go about prerendering a svelte app. The approach used is not really sophisticated and integrated easily with existing sites / static site generators (SSGs). 

Recently I have become quite fond of [eleventy](https://www.11ty.dev/) and have used it for some projects, that is why I would like to expand on the previous post by giving an example of integrating svelte prerendering in an 11ty website.

# Why though?
Static websites and SSGs are awesome, but often times parts of our websites are dynamic and need a bit of JavaScript. Svelte is great at integrating into an existing site and does not require the whole app to be written in it. For SEO and performance purposes, it's a good idea to prerender the dynamic parts of your website and not just build them at runtime in the browser.

# Let's get into it
## Overview
We will be writing our 11ty website with the Nunjucks templating language and leveraging [shortcodes](https://www.11ty.dev/docs/shortcodes/) and other eleventy features to create our demo site.

Furthermore we will use rollup to generate the code for the prerendering as well as the client side bundle.

## Creating the Site
The site we will create is pretty basic, there will be a single `index.html` and one svelte component which will be included in the index page.

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    ...
  </head>
  <body>
    <main>
      <h1>Svelte x 11ty</h1>
      {% svelte "Test.svelte" %}
    </main>
    <script async defer src="scripts/index.js"></script>
  </body>
</html>
```
I have already added a `svelte` shortcode in here, which is not yet defined as well as a `script` which we also need to implement. 

## The Svelte Component
Our svelte component is quite simple, it takes a name and makes it editable via an input: 
```html
<script>
  export let name = 'Etienne';
</script>
<input type="text" bind:value={name}> My name is {name}
```

## Shortcode for Prerendering
Shortcodes can be used to create reusable content within an eleventy site. This is perfect for reusable svelte components. The shortcode will take the name of a svelte file as well as optional props for the component. It will then create an SSR bundle of the component and immediately invoke it to return the static html. 

First let's create a function to render the component out as html. The components markup itself is not enough since the client side bundle needs to have a root which it can use to hydrate the component. We also make sure, that the static props are passed down to the template via a `data`-attribute:

```js
function renderComponent(component, filename, props) {
  return `
    <div class="svelte--${filename}" data-props='${JSON.stringify(props || {})}'>
      ${component.render(props).html}
    </div>
  `
} 
```

Next, let's create the actual shortcode used within the `index.html`: 

```js
const path = require('path')
const rollup = require('rollup');
const svelte = require('rollup-plugin-svelte');

module.exports = async function svelteShortcode(filename, props) {
  // find the component which is requested
  const input = path.join(process.cwd(), 'src', 'content', 'scripts', 'components', filename);

  // create the rollup ssr build
  const build = await rollup
      .rollup({
        input,
        plugins: [
          svelte({
            generate: 'ssr',
            hydratable: true,
            css: false,
          }),
        ],
        external: [/^svelte/],
      });

  // generate the bundle
  const { output: [ main ] } = await build.generate({
    format: 'cjs',
    exports: 'named',
  })

  if (main.facadeModuleId) {
    const Component = requireFromString(main.code, main.facadeModuleId).default;
    return renderComponent(Component, filename, props);
  }
}
```

The `requireFromString` function is used to immediately require the rollup generated code from memory. (See https://stackoverflow.com/questions/17581830/load-node-js-module-from-string-in-memory).

Make sure to add the shortcode in your `.eleventyconfig.js` as an `NunjucksAsyncShortcode`: `config.addNunjucksAsyncShortcode('svelte', svelte);` 

Now, if we run `npx eleventy` we can already see how the component is rendered into the final output: 
```html
<div class="svelte--Test.svelte" data-props='{}'>
  <input type="text" value="Etienne"> My name is Etienne
</div>
```

To see the props in action just add your own name as the second parameter of the shortcode in the `index.html`, like this: `{% svelte "Test.svelte", { name: 'not Etienne' } %}` and the output will be:
```html
<div class="svelte--Test.svelte" data-props='{"name":"not Etienne"}'>
  <input type="text" value="not Etienne"> My name is not Etienne
</div>
```

## Hydrate
So far so good, but half of the fun of svelte is it's dynamic capabilities within the browser, so let's make sure we can hydrate the markup which we already have.

To do that we will first create an entry point for the client side code. Let's create a new JS file and within it a function which gets the wrapper around the svelte components by their class and hydrates them:
```js
function registerComponent (component, name) {
document.querySelectorAll(`.${CSS.escape(name)}`).forEach($el => {
    // parse the props given from the dataset
    const props = JSON.parse($el.dataset.props);
    new component({
      target: $el,
      props,
      hydrate: true
    })
  })
}
```
The `CSS.escape` is needed, because we have a `.` in our class name. 

To register a component just use the function and pass the css class to it: 
```js
import Test from './components/Test.svelte';

registerComponent(Test, 'svelte--Test.svelte');
```

Awesome, only one more step to go: We need to compile the client side code for it to run in the browser. To do that, let's create a new [eleventy JavaScript page](https://www.11ty.dev/docs/languages/javascript/), it's not going to be an actual html page but a JavaScript bundle.

Within the page similarly to the shortcode we will create a rollup bundle, but this time, it will be compiled for client side use and return the JS code and not the rendered html:
```js
const rollup = require('rollup');
const svelte = require('rollup-plugin-svelte');
const nodeResolve = require('@rollup/plugin-node-resolve');
const path = require('path')


module.exports = class Scripts {
  data () {
    return {
      permalink: '/scripts/index.js',
      eleventyExcludeFromCollections: true,
    }
  }

  async render () {
    const build = await rollup.rollup({
      input: path.join(process.cwd(), 'src', 'content', 'scripts', 'index.js'),
      plugins: [
        svelte({
          hydratable: true,
        }),
        nodeResolve.default({
          browser: true,
          dedupe: ['svelte'],
        }),
      ]
    });

    const { output: [ main ] } = await build.generate({
      format: 'iife',
    });
  
    if (main.facadeModuleId) {
      return main.code;
    }
  }
}
```
Et voila, your component is hydrated and the app is fully functional.

## Next steps
Here are some ideas to expand on this simple prototype:
- Use terser to minify the client side bundle in production
- Handle css used within the svelte component
- Handle content which is written into the `head` from the components
- Make the directory of your svelte component configurable


_<span>Photo by <a href="https://unsplash.com/@sigmund?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Sigmund</a> on <a href="https://unsplash.com/s/photos/blueprint?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>_