notes, posts, /etc
Try this interactive component:
This component was created using Svelte, which is a javascript (non-)framework for creating interactive components.
Svelte components are defined in self-contained .svelte
files, which describe all of the CSS styles, HTML markup, and javascript functionality necessary for the component to run.
The Svelte compiler is then run on these .svelte
files, and produces vanilla JS from them, which can be used anywhere.
This article details how to integrate Svelte components into a Hugo site (like the one you’re on now).
Rollup is a javascript “module bundler”. We can configure it to automatically find Svelte files in a particular directory, and compile them into /static
.
As long as Rollup runs before Hugo, we can then include those compiled JS files into the Hugo site as if we had defined them by hand in the /static
directory.
In order to start using Svelte, install yarn
and use yarn init
to set up package.json
with some initial values.
Install Rollup and the plugins we’ll be using:
yarn add -D rollup @rollup/plugin-node-resolve @rollup/plugin-replace
Install Svelte and its associated Rollup plugin:
yarn add -D svelte rollup-plugin-svelte
Install Typescript (typescript is optional, just a personal preference):
yarn add -D @rollup/plugin-typescript tslib typescript
The rollup config file can be stored anywhere, but I chose the following directory structure in my Hugo site for simplicity:
svelte/
├─ setup/ # files needed for svelte setup
│ ├─ rollup.config.ts # the Rollup configuration
│ └─ embed.js # more on this later, will be used to
│ # facilitate including Svelte components
│ # in the middle of an article
└─ components/ # a directory to hold all of the Svelte components
└─ HelloFromSvelte.svelte # the demo component you see at the top of this article
The next step is configuring Rollup, so that it knows where to find Svelte components, and where to install their compiled vanilla javascript files.
This article from codeandlife.com made setting up Rollup for embeddable Svelte components much easier.
My rollup.config.ts
file looks as follows:
import fs from 'fs';
import replace from '@rollup/plugin-replace';
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import { RollupOptions } from 'rollup';
const COMPONENT_DIR = './svelte/components';
// Search for .svelte files in /content and place js files with matching URL tree in /static
interface SvelteComponentLocation {
// The path to the .svelte file relative to /svelte/setup/
inputPath: string;
// The path to the .svelte.js file relative to /static/
outputPath: string;
}
function findSvelteComponents(): SvelteComponentLocation[] {
const files = fs.readdirSync(COMPONENT_DIR);
return files.map(fname => ({
inputPath: `../components/${fname}`,
outputPath: `components/${fname}.js`,
}));
}
function buildOptions(componentLoc: SvelteComponentLocation): RollupOptions {
return {
input: './svelte/setup/embed.js',
output: {
format: 'iife',
file: `static/${componentLoc.outputPath}`,
sourcemap: false,
},
plugins: [
replace({
delimiters: ['%%%', '%%%'],
preventAssignment: true,
values: {
'SVELTE_FILE_PATH': componentLoc.inputPath,
},
}),
svelte({
emitCss: false,
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
],
};
}
const cfg: RollupOptions[] = findSvelteComponents().map(buildOptions);
export default cfg;
This code searches the svelte/components
directory (configurable via the COMPONENT_DIR
variable) for .svelte
files, and returns separate Rollup configs for each one.
For each file, the buildOptions
function returns a configuration object that tells Rollup a few things:
embed.js
%%%SVELTE_FILE_PATH%%%
instances with the path to the current .svelte
file/static/components/{filename}.js
i.e. /static/components/SomeComponent.svelte.js
In order to understand how the above set of steps plays out, we also need to take a look at embed.js
:
import Component from '%%%SVELTE_FILE_PATH%%%';
var div = document.createElement('DIV');
var script = document.currentScript;
script.parentNode.insertBefore(div, script);
new Component({
target: div,
});
This javascript file imports a Svelte component from some path (which is filled in by Rollup), and renders it to the DOM, directly above where the current <script>
tag is (the current <script>
tag is acquired via document.currentScript
).
Combining these two parts, we should be able to embed compiled Svelte components by simply including a script tag that looks like <script src="/components/MyComponent.svelte.js"></script>
.
We’ll take a look at making that even easier in the next section.
The next step is to setup a Hugo “shortcode” to make embedding Svelte components from content pages easier than manually creating <script>
tags. Simply add to /layouts/shortcodes/svelte.html
:
<noscript>Javascript is disabled.</noscript>
<script src="/components/{{ .Get 0 }}.svelte.js"></script>
The <noscript>
tag includes a message to show to browsers where javascript is disabled.
With that svelte
shortcode added, Svelte components can be added from content pages very simply:
{{< svelte "HelloFromSvelte" >}}
{{ .Get 0 }}
is replaced by Hugo’s templating engine with the first argument to the shortcode (in this case, HelloFromSvelte
), and we are left with:
<script src="/components/HelloFromSvelte.svelte.js"></script>
Since Rollup compiled /svelte/components/HelloFromSvelte.svelte
, and imported it into embed.js
with output at /static/components/HelloFromSvelte.svelte.js
, the resulting script
tag is javascript that has the necessary code to create a div
right above the script
tag and then mount the Svelte component into that div.
I hope this post helped you add some interactivity to your Hugo site.
Let me know if you have any questions or thoughts by commenting below!