I've been learning about WebC recently and I'm pretty sure I'm converted. So I thought I'd put together a set of notes from my experience of learning it. To do this, let's create a little project together. From scratch.
Prerequisites
Before starting, I'm going to assume:
- You are happy to write some JavaScript
- You're familiar Eleventy
- You maybe understand custom elements (a.k.a web components)
mkdir 11ty-webc-nutshell
cd 11ty-webc-nutshell
npm init -y
npm install @11ty/eleventy @11ty/eleventy-plugin-webc
That'll create us a project folder, set it up to use NPM and install our dependencies.
Configuration
Now we need to configure Eleventy to use WebC. This is pretty simple, we'll add our eleventy config file. I'm going to use eleventy.config.cjs, but there are several of other names you can use.
My current thinking is this is more important than a "dot file" like
.gitignore
and I'd like to be specific this is CommonJS.
const eleventyWebc = require('@11ty/eleventy-plugin-webc')
module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(eleventyWebc)
}
Pages
Pretty simple so far. Now lets create our first page. This uses the new .webc
extension (hint: it's just HTML, so you can tell your editor about that to make it prettier).
index.webc
---
layout: html.webc
---
<main>
<h1>Hello, there</h1>
<my-first-component></my-first-component>
</main>
This all looks familiar so far, if you've used Eleventy before that is. It starts of with Front Matter which is meta-information to tell Eleventy things. Here we say we'd like to use another file as the layout. So this page will be rendered inside the html.webc
which we'll create a bit later.
After the Front Matter, we create our page. It has a nice big title and it uses a custom element! WebC is based on web standards, it hooks in to custom elements and starts to do it's magic. So let's create our first WebC component!
Components
By default, WebC will automatically pick up any .webc
file you put into the _components
folder. Let's create _components/my-first-component.web:
You can configure where Eleventy/WebC looks for these components or you can use the default location if you want.
<p class="call">General Kenobi</p>
Thats what a WebC component is, it's just HTML. There are lots of other clever things you can do, but at it's purest it can just be HTML. Eleventy will replace <my-first-component>
in the page with our <p>
element for you. If you just want a way of reusing HTML code, WebC is a fine way to do that.
So to be a bit more interesting, let's give our component some style.
_components/my-first-component.web
<p class="call">General Kenobi</p>
<style>
.call {
color: rebeccapurple;
text-decoration: underline;
}
.call::after {
content: '!';
}
.response {
color: blue;
text-decoration: underline;
}
</style>
Here we've made the text look cooler with some CSS. By default, WebC will see that we've used a <style>
tag and will take it out of our template to be bundled up. We'll tell it where to put it later in our layout.
This style is global so we're using a class to make it more specific. You can alternatively add webc:scoped
to the <style>
tag to auto-generate a name for you and make it so the style only applies to things inside the WebC file.
By adding CSS (or JavaScript) it changes how WebC behaves a little. Without it, WebC will take the HTML content and straight replace it anywhere it is referenced. This is known as a html-only component. With scripts or styles, the component remains a web-component so the <my-first-component>
remains in your HTML and it's contents becomes everything else in your in your .webc
file.
You can configure how the content is inserted with a
<slot>
tag, more info
So let's add some JavaScript too, add this script tag to the existing file, as long as it is at the top-level of the HTML document in your component file.
_components/my-first-component.web
<!--
previous example code here...
-->
<script>
class MyFirstComponent extends HTMLElement {
connectedCallback() {
setTimeout(() => {
this.appendMessage('This will make a fine addition to my collection')
}, 2_000)
}
appendMessage(message) {
const p = document.createElement('p')
p.textContent = message
p.classList.add('message')
this.appendChild(p)
}
}
window.customElements.define('my-first-component', MyFirstComponent)
</script>
Because WebC is not in html-only mode, it keeps the <my-first-component>
element in your rendered HTML and you can attach code to that. We create our client-side custom element, MyFirstComponent
by subclassing HTMLElement. Then we use define
on the custom elements registry to get it hooked up. When someone visits the page, the browser attach our code onto the HTML element and call connectedCallback
to let us know.
As a demonstration, we add another styled message after waiting an extra 2 seconds.
Layouts
Ok so what happens to these scripts and styles, and how do they fit into a nice HTML document? It's completely up to you! We'll use a layout to add the styles and scripts into our rendered HTML, create _includes/layout.webc:
<!doctype html>
<html>
<head>
<title>My first WebC</title>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width" />
<style @raw="getBundle('css')" webc:keep></style>
</head>
<body>
<template @html="this.content" webc:nokeep></template>
<footer>My first WebC 2023</footer>
<script type="module" @raw="getBundle('js')" webc:keep></script>
</body>
</html>
There's are a few bits going on here, let's break it down.
As mentioned above, one of the powers of WebC is that it can bundle up your JavaScript and CSS for you. WebC takes out those <script>
and <style>
tags from your .webc
files and we have to get them into to our HTML document. We do this with some familiar <script>
and <style>
elements! These ones though have special attributes to the files generated by WebC and insert them into the document. Other brief notes:
- The scripts and styles are per-page so only the resources you need are on each page.
- As WebC removes
<script>
and<style>
elements, here we need to tell it not to by adding thewebc:keep
attribute. - Any
webc:
attributes are removed from the HTML you publish. - The value of
@raw
can actually be anything from Eleventy, e.g. things from the Front Matter or Data Cascade. In this case it's a shortcode provided by eleventy-plugin-bundle (which WebC uses under the hood).
The second interesting bit is that we're using a @html
attribute on a <template>
element. This takes the HTML generated from processing our page and puts it inside the <template>
element. There is a special webc:nokeep
attribute on there too. This tells WebC that we aren't interested in the template itself and to get rid of it, replacing it with whatever our page has rendered.
Round up
To summerise, we:
- Set up a fresh Eleventy project and added the WebC plugin
- Created a page using WebC as the templating language
- Added the WebC component that the page uses
- Made the component more interesting with styles and scripts
- Laid out the whole page using a WebC layout
If you want to skip to the end, who doesn't, see all the code in one place at examples/eleventy-webc.
Current questions
A few things I've been thinking and don't currently have answers for:
- Can a
webc:setup
access "props"? - How can you modify the attributes on non-html-component?
- I keep getting a
Uncaught SyntaxError: Identifier '...' has already been declared
& have to restart the 11ty.
Next steps
My team has a custom-elements based design system, partly using every-layout, and it has an existing Eleventy plugin to generate styles and scripts. I wonder how I could migrate or provide a way to do this via WebC instead. There are currently some pretty big regexes I'd rather not have to maintain!
Here are some places you could go next:
- The WebC reference is the obvious place to look for more information and inspiration. You can do stuff like if statements or for loops or provide slots to describe how WebC will embed content in your component.
- Props and Attributes are really powerful and let you process Eleventy data into your templated content.
- 11ty/eleventy-plugin-bundle is a good place to look if you're interested in how the script and style bundling works. A big snoop through the code helped me work out what was going on.
- MDN has a good guide on getting started with custom elements if you want to learn more about those.
- I haven't dived into it yet, but webc:setup looks really interesting
- I'm working on a "year in review" type thing for a coffee club I'm in and it's using WebC, robb-j/year-in-rebrew if you're interested.
Found this useful or spotted a mistake? Let me know on Mastodon!.