I’m building a pattern library in Jekyll. I like Jekyll for this because I can store all my reusable components in the _includes/
directory and use Jekyll’s {% include my-component.html %}
tag to import components as needed. And those components can be seeded with ⚡REAL DATA⚡ from the client’s database stored in JSON files in the _data
directory. In practice rendering a Product card might look something like this:
{% assign product = site.data.products.first %}
{% include product-card.html %}
And the product-card.html
partial looks like this:
<div class="product">
<img class="product-image" src="{{ product.image }}" alt="">
<h3 class="product-title">{{ product.title }}</h3>
<p>{{ product.description }}</p>
</div>
This is cool! However, assigning the variable over and over is a bit redundant and kludgy. You assign multiple variables and forget the variable name that the component expects, get collisions, etc. It left me wanting my components to be a little more agnostic and encapsulated.
Passing Data into Jekyll Includes
When cruising through the Includes documentation I noticed a relatively new feature where you can pass data into a component. This means I can stop assigning magic-keyword variables for every included partial.
{% include product-card.html title=site.data.products.first.title
image=site.data.products.first.image
description=site.data.products.first.description %}
Then our components standardize around the include.foo
syntax:
<div class="product">
<img class="product-image" src="{{ include.image }}" alt="">
<h3 class="product-title">{{ include.title }}</h3>
<p>{{ include.description }}</p>
</div>
It’s a very subtle change but results in much cleaner and more trackable code. It’s exactly what I want from Jekyll and is very similar to Rails Partials, React’s props, or…
Jekyll Includes are a gateway drug to Web Components
After a couple days of writing includes like this I thought to myself “Why am I not just writing Web Components?” With just a tiny shift to what I’m doing now, here’s what that would look like…
<link rel="import" href="product-card.html"/>
<product-card title="{{ site.data.products.first.title }}"
image="{{ site.data.products.first.image }}"
description="{{ site.data.products.first.description }}">
</product-card>
Now our product-card.html
gets a little more complex because we need some boilerplate to register the element. Also, native <template>
elements don’t have any built-in templating language (weird, I know), so we’ll want to use Polymer to add syntactic sugar and avoid lots of thing.innerText
cruft.
<link rel="import" href="polymer.html"/>
<dom-module id="product-card">
<template>
<img class="product-image" src="[[ product.image ]]" alt="">
<h3 class="product-title">[[ product.title ]]</h3>
<p>[[ product.description ]]</p>
</template>
<!-- Polymer-style Custom Element Boilerplate -->
<script>
class ProductCard extends Polymer.Element {
static get is() { return "product-card"; }
// Register properties and default values
static get properties() {
return {
title: {
type: String,
value: 'Really long product title name with keywords'
},
image: {
type: String,
value: 'http://placehold.it/400x300/'
},
description: {
type: String,
value: 'lorem ipsum dolor sit amit'
}
}
}
}
customElements.define(ProductCard.is, ProductCard);
</script>
</dom-module>
Phew! We’re done.
There are a lot of things I like about this. This product-card.html
is a file that lives in a public directory and could even be served by a CDN. All HTML/CSS/JS is fetched by the browser as-needed (no bloat!) and using HTTP/2 would make that very fast. One file too! No more updating 3 files to fix one issue.
So.. why not use React? This this is a good question. What I’m looking for is actually similar to React’s Components and Props API…
import React from 'react';
import ReactDOM from 'react-dom';
class ProductCard extends React.Component {
return (
<div>
<img class="product-image" src="{ props.image }" alt="">
<h3 class="product-title">{ props.title }</h3>
<p>{ props.description }</p>
</div>
)
}
class App extends React.Component {
return (
<ProductCard title="Foo"
image="something.jpg"
description="Lorem ipsum..."/>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
This would work, but React is currently two steps away from where I am right now. React shows up to the party with its dependency siblings: a bundler, a transpiler, a data-store, a router, and more. That bundles up to a lot of JavaScript delivered to an unpredictable client. To achieve server-side and client-side rendering parity you likely need a separate Node app/middleware to stitch, split, and serve the files together.
I like that Web Components are an entirely client-side technology but can be rendered server-side in existing tech stacks whether it’s Jekyll, Rails, or even some Enterprise Java system. I can piecemeal start converting <div class="product-card">
to <product-card>
with little consequence in the codebase today. I have a lever to pull for how decoupled I want to be from the backend stack. Being able to “enhance” static sites makes Web Components one-step away from where I am right now.
I don’t mind saying this either: I like the HTML’y-ness of Web Components as well. I still have a JS-dependency and I’ve inherited a framework dependency (Polymer) but I eject myself from the All-in-JS approach and the transpile dependency stack. Writing HTML that contains JavaScript, not JavaScript that contains HTML, feels good to me; Dave Rupert, Bachelor of Arts in Japanology and Definitely NOT a CoolGuy 😎 Computer Scientist.
I haven’t made the jump to Polymer Web Components for this project. Moving to Web Components would change the deliverable scope from “static prototype HTML” to “hot, production-ready Web Components that can manage user state, authentication, and checkout with the right API key”. But I can’t stop thinking about it. “Living” components would be better than “static” handoffs, but it would increase development time (maybe) and incur just enough architectural change that APIs would need to be built, etc. Hopefully soon I’ll make the jump and can tell you stories, but for now, I’d love to hear more war stories from people who are embracing Web Components.