Rebuilding a Website with Modular Markup Components
by Ines Montani on
Update (October 3, 2016)
This post was originally written about the setup of spacy.io. We have since moved our blog posts over to this site, which is built based on the same principle of modular markup components.
In a small team, everyone should be able to contribute content to the website and make use of the full set of visual components, without having to worry about design or write complex HTML. To help us write docs, tutorials and blog posts about spaCy, we developed a powerful set of modularized markup components, implemented using Jade.
This approach has worked well for us, so we decided to open source our site, and explain the motivation. You can see the idea in action by taking a look at the source code, or our style guide, which lets you see most of our current markup components. There are also some simpler examples to follow in this post.
A product website will never stand still — it will be in constant development. Its architecture therefore needs to be set up from the start to be updated and modified frequently, and allow changes to its components. Instead of thinking in sections and pages, we need to start thinking in design systems and modules. Atomic Design is a popular methodology for modular design systems: small elements (atoms) are combined to larger structures (molecules), which can be connected to form complex components (organisms). For example, a permalink atom and a heading atom can be combined to form a headline molecule, which becomes part of a larger text block organism.
We need a lot of those flexible organisms, and they all come with custom markup that will likely change over time — think responsive embeds, data tables, or code blocks. Every time a component is used, its full markup needs to be implemented. This also means that every change to this markup requires changing every instance of the respective component. In order to make this work, we need a concise and dynamic markup language to define those components and make them accessible for content creators to use. In short: we need a markup language as compact as Markdown, as feature-rich as HTML and as programmable as JavasScript.
Jade (recently renamed to Pug) is a markup language that comes with a very powerful feature: mixins. Mixins are reusable content elements that act as "content functions", by taking custom arguments. Mixins offer a powerful way to implement a modular design. Every component and its mark-up can be defined once, and reused in different contexts across the site.
Jadeeach text in [ "one", "two", "three" ]
article.teaser(class=(text == "two") ? "active" : "")
To put it simply, Jade lets you program. Writing both the templates and the content itself in Jade keeps the markup consistent, and eliminates the problems and inflexibilities usually associated with the use of a simplified subset of a markup language.
Here's an example for an image mixin that can be used to insert a figure containing an image, complete with alt text and caption using only one line of code. Note how all markup, including the path to the image folder, is defined within the mixin:
Jade Mixin//- Definition
mixin image(source, alt, caption)
img.image-class(src="images/" + source alt=alt)
+image("my-image.jpg", "This is a caption", "My image")
Compiled HTML<figure class="figure-class">
<img class="image-class" src="images/my-image.jpg" alt="My image" />
<figcaption class="caption-class">This is a caption"</figcaption>
The mixin needs an option to specify the style ("success", "info", "warning" or "alert"), which will be translated to the corresponding class, as well as an option to make it dismissible and add a close button. The markup could look like this:
Jade Mixin//- Definition
mixin alert(style, dismissible)
.alert(class="alert-" + style + ( (dismissible) ? " alert-dismissible" : "") role="alert")
button.close(type="button" data-dismiss="alert" aria-label="Close")
+alert("success") You successfully used an alert mixin.
+alert("danger", true) This danger alert can be dismissed.
NoteThe "rest argument" syntax (...style) lets your mixin take an unknown number of arguments that become available to your mixin as an array. This is especially useful for CSS classes.
To handle modifiers, we're using a helper function called prefixArgs(), which adds a given prefix (like "grid-" or "table-") to the arguments and returns a list of class names.
The above markup still requires content creators to know and use the respective classes manually. To avoid this, you could instead define the styles within the mixins. This would give the content creators a +grid() utility that didn't expose any details of the design system. If the layout of the blog is ever going to change, the blog posts would remain untouched, and only the mixins would have to be adjusted to the new style.
Content creators should never have to worry about stylistic properties like margins, paddings and borders. They shouldn't have to worry about an arbitrary order of nested elements, responsive breakpoints or sizing. Thus, the details of the underlying CSS framework should not leak into the markup. After all, this is why we're creating sophisticated design systems in the first place: to make it easy to author effective content.
Content creators should never have to worry about stylistic properties like margins, paddings and borders.
Providing authors with a reasonable subset of markup utilities puts them back in control of the content they create and enables them to ship quickly and efficiently. In small teams, a short publishing pipeline like this is absolutely vital.
sudo npm install --global harp
git clone https://github.com/spacy-io/spacy
Like most static site generators, Harp compiles files into the same directory if they don't have a leading underscore. For instance, blog-post.jade would become blog-post.html. Files with a leading underscore are not compiled directly and can be used to store metadata and layout partials. This allows you to build dynamic templates that can accommodate both static content and dynamic data supplied via _data.json files.
There are two ways to set up reusable components:
Generate mixins that are combined into a global _mixins.jade file and included at the top of a page. This makes sense for smaller components that will be used a lot by content authors, especially since mixins can take long blocks of content to be used within the mixin.
Use dynamic partials via Harp's !=partial() syntax. This is useful for layout partials and widgets. Similar to mixin arguments, partials allow you to pass in data and variables to modify the included file. We're using this feature in a "latest blog posts" partial to display a mutable number of teasers.
During development, we use Harp to serve our entire site locally using one simple command only, harp server. To deploy the site, we pre-process and compile the code and upload the files.
Modular markup gives content authors more powerful, domain-specific components that allow them to use more complex markup while effectively writing less. Instead of optimizing the look and feel of individual pages that might change tomorrow, the front-end developer takes on a blacksmith role of forging reusable tools.