Zach’s ugly mug (his face)

Zach Leatherman

Adding Components to Eleventy with WebC

Components have been an often requested feature in Eleventy. While I do consider it an unanswered question whether or not components are the best starting point for new developers, it’s hard to argue with the efficiency and delivery gains to be had for folks with a bit more experience.

Now, we could wait around for the heavier bundler-based frameworks to circle back and pretend like React server components are a revolutionary new pattern for HTML without JavaScript (can you imagine). But if I might be allowed to offer a well-argued and intellectually-bulletproof counterpoint: naw.

A History of Component-like Things

Historically, Eleventy has offered a few component-like features. Before we move on, I think it’s worthwhile to go through the prior art here (for Eleventy specifically).

  1. Liquid Render {% render %} tag, the successor to the deprecated Liquid Include {% include %} tag.
  2. Nunjucks Include {% include %} tag
  3. Nunjucks Macro (and Import)
  4. Eleventy Shortcodes (both single and paired)
  5. Eleventy Render Plugin

Shortcodes (and the Render plugin) were close to the developer experience folks expect from a component system, but they have their own drawbacks.

  • Some template languages were good at handling asynchronous behavior by default (Liquid) and others required awkward delineation between asynchronous and synchronous behavior (Nunjucks).
  • You can’t invoke other shortcodes from inside of a shortcode definition (the JS function in your Eleventy configuration file). You can nest shortcodes in paired shortcode content but that isn’t always enough.

Shortcodes are okay. We can do better.

The Age of Vue + Eleventy

Shipped in 2020 to support a netlify.com tech stack move to Eleventy, the Eleventy Vue SFC plugin was the first big foray into full first-party components for Eleventy. This used Vue single file components and rendered them to plain-’ol HTML (without shipping any client JavaScript).

Vue components are great! Here are a few things I loved:

  • Single file component authoring nicely co-locates HTML, CSS, and (for this plugin at least) server-only JavaScript.
  • Vue components felt more like authoring HTML (not JavaScript for everything).
  • Vue merges class and style attributes smartly between host components and component definitions. I know it’s a little thing but this felt like a huge authoring win when your component is HTML-aware (which shortcodes are not).
  • Using the is attribute to redefine components inline.
  • Using attribute bindings for scripting attribute values.
  • Vue SFCs also offer CSS scoping built-in, although in practice I didn’t use it much! I liked to have more descriptive classes for components I build.
  • The Eleventy Vue plugin also generated per page CSS bundles and brought first-class incremental builds to Eleventy Vue projects (not all template languages in Eleventy support incremental builds).

Maintenance Woes

The biggest drawback of this approach was that the Eleventy Vue plugin uses rollup-plugin-vue which—perhaps obviously—is tightly coupled to the Rollup bundler! I’ve talked a bit about the long term risk of coupling to an official bundler in Eleventy, and that concern certainly played out with some prescient accuracy here.

Vue 3 was released (beta in April 2020, stable in September 2020) after the Eleventy Vue plugin and an official Vue 3 rollup-plugin-vue@6.0.0 was released in November 2020. Notably, this was happening at the same time as a rapid rise in popularity for Vite. Accordingly, the rollup-plugin-vue repo is now archived and not maintained and folks are recommended to use Vite instead of the rollup plugin for Vue compilation.

Unfortunately, due to Vue’s upstream moves here, we’ll likely end up archiving eleventy-plugin-vue too.

Web Components

Building netlify.com with server-rendered zero-bundle Vue components was a great experience but interestingly we didn’t ship any client-side Vue components on the site (during my tenure). For interactivity we were leaning on Web Components (mostly Custom Elements), which offered a very similar zero-overhead mentality in the client-side world.

When you talk about web components publicly, it’s almost certain that you’ll get a helpful link to a 2019 blog post from Rich Harris (the creator of Svelte) titled Why I don’t use web components. The post has some valid criticism (though it leans kinda hard into complaints about Shadow DOM, that I quickly realized was an optional feature and conveniently ignored it 😅 for technical reasons that are outside the scope of this post).

But Rich’s top issue in the post is Progressive Enhancement. Yes, lots to agree on there. Rich starts his argument with the point that he can write a Svelte component that spits out server-rendered HTML like this:

<a target="_blank" noreferrer href="" class="svelte-1jnfxx">
Tweet this
</a>

…and in this Rich compares Svelte’s server-rendered HTML to a client-rendered Web Component:

<twitter-share text="" url="" via=""/>

Apples to oranges aside, this is a good distillation of the expectations mismatch for a lot of folks. If you’re coming over from a framework component background, you likely expect first-class server rendering for Web Components to be solved by the platform.

To put it succinctly, developers want to write <twitter-share text="…" url="…" via="…"> and have browsers serve <a target="_blank" noreferrer href="…">Tweet this</a> without a build or compile step.

Well, you can’t. But JavaScript component frameworks can’t do this either.

Going back to Rich’s example, I would say that the ideal client-rendered web component markup would look something like this:

<twitter-share>
<a href="">Tweet this</a>
</twitter-share>

…and the component might transform the markup to add the component attributes as needed, maybe to render something like this:

<twitter-share>
<a href="" target="_blank" noreferrer>Tweet this</a>
</twitter-share>

Notably, any time you modify the DOM with JavaScript it creates a tension point for progressive enhancement. Is it okay that before-JS or broken-JS users won’t have those attributes added to the link? Maybe for this use case. But for more complex components, these tradeoff decisions cannot be made universally. They must be made by the web authors with the full context of the use case for the project they are working on.

(Side note that this is why component-based design systems have a hard time—components don’t have the individual use-case specific context necessary to make judgement calls and can only advise on how they might be used in more broader, generic use cases)

It isn’t a level playing field to compare server-rendered framework components to client-rendered web components but it does highlight the need for additional Web Component tooling to deliver the maximum amount of code re-use with the minimum amount of tradeoff. Let’s add a build/compile/server render step for Web Components—my biased suggestion is to use WebC!

The Age of WebC + Eleventy

WebC is a framework-independent standalone HTML compiler for generating markup for web components.

When creating WebC, I wanted to bring all of the good things from Vue’s single file components to Web Components: HTML-first single file component authoring, class and style attribute merging, webc:is (instead of is to avoid attribute collisions), dynamic attributes using the : prefix, scoped CSS, per-page CSS and JS bundles, and fully incremental builds.

On top of those, WebC offers full access to the Data Cascade inside of your components, is 100% async friendly, designed as a server-first tool (rather than a client-first retrofit), offers zero-overhead client interactivity (requires no library code), is streaming friendly, is not coupled to a bundler and includes some bundler functionality built-in, and is extensible with other template language syntax (Markdown, Nunjucks, Liquid, Sass, etc).

Here are a few links to get started with WebC:

What’s Next for WebC?

  1. More integrations! I loved to see an Express plugin for WebC from Nick Colley and a Vite plugin for WebC from @mayank99
  2. HTML bucketing! We already have CSS/JS asset aggregation. I’d like to see this with arbitrary HTML too. Think of a single WebC component file for a web font that includes both the @font-face CSS and the preload HTML together! Or a WebC icon that only includes a single <g> that aggregates up to a reusable de-duplicated SVG icon set.
  3. Writing asset bundles to files (now we’re really getting into bundler-functionality)
  4. Aliases for node_modules so you can import easily from npm! Folks can publish a webc file for re-use in any other project (that also includes the HTML, JS, and CSS) to use directly.
  5. More sugar for loops/conditional rendering. Those this is possible using template syntax or JavaScript render functions now, it could be less verbose!
  6. Tighter integration with <is-land>: I’d like to see us get to the point where WebC components will be able to declare assets to load conditionally based on <is-land> loading conditions. Super granular control and power!

The best way to keep up to date here is to subscribe to our YouTube channel or follow us on Twitter @eleven_ty and/or @webc_omponents!

Thanks, y’all!

This is a post about web components! Check out all the web components posts.
Zach’s ugly mug (his face)

Zach is a builder for the web with IndieWeb Avatar for https://www.netlify.comNetlify. He created the IndieWeb Avatar for https://www.11ty.devEleventy site generator and is still fixated on web fonts. His public speaking résumé includes talks in eight different countries at events like Jamstack Conf, Beyond Tellerrand, Smashing Conference, CSSConf, and The White House. He is an emeritus of Filament Group, NEJS CONF, and still helps out with nebraskajs’s AvatarNebraskaJS. Read more about Zach »

Previous
Nick Taylor’s livecoding.ca: WebC is neat!
Next
Four Million npm Downloads for Eleventy

23 Reposts

Eleventy 🎈 v2.0.0-canary.16Spooky Raymond Camden 👻Sia KaramalegosDavid LeiningerPhil HawksworthEstela Franco (she/her)Joan León 🏞⚡️Matt BiilmannJulienRob Blake ∞Steve LeeSteve LeeSteve Lee全部入りHTML太郎全部入りHTML太郎Baldur BjarnasonBryce WrayWebPerformance ReportFrontend DogmaIndieWeb Avatar for https://www.alvinashcraft.comIndieWeb Avatar for https://thedevnews.comIndieWeb Avatar for https://vkdcode.inIndieWeb Avatar for https://vkdcode.in

78 Likes

Ted MartinCorey MegownEvan (he/him)Marco useCauseAndEffect()Ana RodriguesbenjaminMarco MicaleTobias AhlinJP SantosFlorian GeierstangerMatt Stow ✨🦄Michal Mocnyf-elixMatt StrömMarc RadziwillDr. Non-LinearPassleWebPerformance ReportTommie Manhammar ForssanderBryce WrayJim TittslerSPELLiott 🧙👻🎃全部入りHTML太郎Gareth Ford WilliamsZacarie CarrSteve LeePerfReviews ⚡️TristanMurtuzaali SurtiVirtualOverride🇵🇭Shawn BeattyFriedhold MatzJesse HeadyRichard HerbertRyan CaoLiam FiddlerChris BurnellBen BakeroletsRudolfs DaugulisMike AparicioMartin BerglundRobin CussolColin FahrionAlistair ShepherdJeroen ZwartepoorteEduardo UribeStephanie EcklesNick SollecitoGeoff RichMatt BiilmannScott Jehlaaron hansHugo NogueiraClark GunnKOOThomas Michael SemmlerAndrea Leardini🔪 Carnage Electron 🔪Tanner DolbyJohn Kemp-CruzVadim Makeev 💙💛ArpitMike SkowronekJoan León 🏞⚡️Matthias OttJuha LiikalaPhil HawksworthMWDelaneyEleventy 🎈 v2.0.0-canary.16Thien BuiSia KaramalegosDavid LeiningerJess "robot apocalypse" Peck 🐍🤖Oleksandr Shut 🇺🇦mortendk 🤘Matt Rossman 🍌Brett Jankord 
14 Comments
  1. Spooky Raymond Camden 👻

    @raymondcamden

    Am I right in seeing that if I want to use a WC in a Liquid/MD/etc file, I have to wrap with the render plugin? That's the only part that seems kinda annoying - a lot of extra typing. I can use a .webc file as the top doc, but I want the power of Liquid too :)

  2. Zach Leatherman

    @zachleat

    A few options: 1. You can use Liquid inside of WebC 11ty.dev/docs/languages… 2. You can use postprocess using the transform 11ty.dev/docs/languages… 3. You can rename the tags in the render plugin (view full options): 11ty.dev/docs/plugins/r…

  3. Spooky Raymond Camden 👻

    @raymondcamden

    Kinda makes sense. It feels a bit weird that I may make a .webc file just so *that* file can use N other .webc files that really my components, where the first is just... the page. Does that make sense? Do I just need to get over it?

  4. Zach Leatherman

    @zachleat

    not sURe I fOLlOw, No 😅 tO Use a liQUID INcludE yOu Need To uSE liQUId. TO USE a nuNjucKS maCRo You nEeD TO uSE NUNjUcKs. SamE fOr wEbC?

  5. Spooky Raymond Camden 👻

    @raymondcamden

    Yeah I get that - but it's like, I have page /foo. It isn't a web component. But it's going to use some. So I need to make /foo.webc. Meh, it will feel natural once I try once or twice (got an idea for a simple demo).

  6. Zach Leatherman

    @zachleat

    Just to be super clear, it isn’t a hard requirement. I converted zachleat.com to use WebC using the transform method so that I could just keep all of my existing liquid templates and sprinkle WebC components in there 11ty.dev/docs/languages…

  7. KOO

    @Dominus_Kelvin

    Awesome. Hey Zach I’d love to have you on a TKYT session to learn @eleven_ty Here is a previous session. Let me know what you think youtu.be/j9rn2180mNI

  8. Zach Leatherman

    @zachleat

    Let’s do itttttt how do I schedule

  9. KOO

    @Dominus_Kelvin

    Yay. I’ll DM you a link to a casual call 📞

  10. KOO

    @Dominus_Kelvin

    DM sent

  11. 𝕮

    @chrisshank23

    ``` Tweet this ``` As a side note, here is another way to progressively enhance with a custom element! Since the custom element extends the anchor element it’s arguable a cleaner way to implement this.

  12. Zach Leatherman

    @zachleat

    Yeah, I don’t predict that one will survive the trial by standards fire that is Safari.

  13. Zach Leatherman

    @zachleat

    i ThinK tHe MOst AcUte paiN I’Ve FeLt DireCtLy fRoM ThiS, I DocumeNTeD a bIT HeRe: ZAChleAt.cOm/web/webc-in-eL…

  14. Eleventy 🎈 v2.0.0-canary.16

    @eleven_ty

    WHy THe 🙄 SOuNDs FInE to mE THOUgH i wOuLD notE ZACHLeaT.COm/wEb/WEbc-iN-el…

Shamelessly plug your related post:

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)