How to get the content on a website? Separating it from the technical stuff sounds like a good practice for distraction free writing and focused coding. Can an import statement do the trick to load the content from a markup file?
This blog post explores how content written in the markup language AsciiDoc can be used in a single page web application. It shows a minimal generic setup without using existing content plugins. The examples are based on webpack, NuxtJS and Vue.js. You can adapt the recipes in any project that uses webpack.
The examples in this post use Vue.js and a static site generator NuxtJS that is built on top of Vue.js. If you are using Angular or React you’ll need to adapt the recipes to match your technology.
The moving parts
This section describes the different frameworks that work together. They load the content from an AsciiDoc file and present them on a page of this blog.
The paragraphs include why I chose each piece of technology. If you skip this section now, return here later to look up technologies you want to learn more about.
- Webpack provides a base layer for build automation and bundling. Without bundling, there would be no single page web app at the end. A wide and deep ecosystem provides tools and add-ons for different kinds of files and configurations in the build process. For me it’s built-in when using NuxtJS (see below).
- webpack Loaders
- Loaders allow you to load files from disk to use them in your application. You can stack multiple loaders, so that the output of one loader is the input of another loader. A loader allows me to load the content from the disk and process it as HTML.
NuxtJS builds on top of webpack and Vue.js and provides developers with a ready-to-use setup for progressive web apps and statically generated sites.
This website for example uses it to create a static site and publishes to Netlify to host it. NuxtJS helps me with a well-defined application structure, an ecosystem to pick what I need, and all the hooks and extension points I need to try out new things.
- AsciiDoc and Asciidoctor
- Semantic HTML5 Backend for Asciidoctor
- Asciidoctor and its default HTML output come with ready-to-use stylesheets. The default output adds a lot of div-tags, so I looked for a lighter HTML output. The output of the “Semantic HTML5 Backend For Asciidoctor” (or html5s for short) provides me this output.
- Hightlight.js can highlight source code blocks for a lot of different languages. It can run on the client side in the browser, but then the source code will flicker as the CSS is applied after rendering the page. This modules adds it to the AsciiDoc processing at build time, the content is pre-rendered correctly with all styles.
- Bulma is a light-weight CSS framework supporting Flexbox features and is fully responsive. This website’s theme is based on Bulma. All HTML content wrapped within a tag with class content will have a reasonable default-formatting. As we’ll see later, this will match nicely with the semantic HTML output. It gives me the chance of a fresh look-and-feel with some options to change things, while keeping me on track as I am not very good at CSS. For me it is between Bootstrap (all sites look the same and being tired to look at it) and Tailwind CSS (where the ones with good CSS experience can create cool stuff).
Let’s walk through the steps one-by-one to build the fully working example.
The pipeline I’m about to build will have the following steps “bottom-up”:
- AsciiDoc loader to load content from a file and to convert it to HTML,
- HTML-to-Vue loader to transform the HTML to a Vue.js component, and
- Using the Vue.js component within the NuxtJS application.
Loader to convert AsciiDoc to HTML
Using jsdom resolves two challenges for me:
Adding missing tags if the HTML content is not well-formed.
I used this to repair some missing closing tags for tables I encountered for the Semantic HTML5 Backend for Asciidoctor.
Changing of the HTML using the DOM API.
I used it to simplify the HTML output, so it matches the Bulma styles. In the next loader I use it to translate some HTML tags to Vue.js components.
The loader above translates this AsciiDoc snippet
This is a link:/someurl[*bold* link]!
to the HTML below:
<p> <strong>This</strong> is a <a href="/someurl">link</a>! </p>
Loader to convert HTML to Vue.js component
This loader takes the HTML of the previous step and translates some parts of the HTML to Vue.js attributes and components. Example: By translating all local anchors to NuxtJS links, they will no longer reload the page on activation, but use the SPA mechanism to update the content. The latest version of NuxtJS will also pre-load any content once the link scrolls into view.
This loader above translates HTML from above
<p> <strong>This</strong> is a <a href="/someurl">link</a>! </p>
to a Vue.js component:
<template> <p> <strong>This</strong> is a <nuxt-link to="/someurl">link</nuxt-link>! </p> </template>
Adding loaders to the NuxtJS configuration
NuxtJS bundles a ready-to-use loader pipeline. Developers can extend it by adding more steps and handle other files. The common file extension for AsciiDoc is .adoc, so we’ll teach NuxtJS how to convert these files with the two additional loaders outlined in the previous sections plus the standard Vue.js loader. As usual the order of loaders is reversed: they process files from the bottom to the top.
Using the Vue.js component
You can see the live page here.
Background information and discussion
What I like about this solution:
- Keep my content in AsciiDoc, where I can write it focusing on the content.
- Simplified hosting with a static site generator like NuxtJS
- Flexibility to dig into CSS and optimizations with NuxtJS, therefore it doesn’t chain me to an inflexible solution.
What I don’t like about this solution:
- While the Semantic HTML5 backend comes close to the HTML I would like to see, I needed to tweak it to match the standard Bulma CSS. I should probably learn more about CSS to make the output appear the way I want it to appear.
If you like what you’ve seen, but don’t want to start from scratch, have a look at nuxt/content (that supports Markdown, but not AsciiDoc), and this blog post with a step-by-step guide on how to set up a blog with NuxtJS and nuxt/content.
All sources you see here are from the source code of this website using AsciiDoc’s partial include syntax at build time. Therefore, all the examples you see here are tested and should work. If not, please let me know, and I’ll correct them.
So this is my setup to load AsciiDoc content into a Vue.js and NuxtJS website using webpack. With the respective webpack loaders a single
import statement is enough to load the content as a Vue.js component. I hope you enjoyed the tour.
Get in contact to share your feedback or tell others about what you found here by sharing the link!
Thank you Anett aka @emsuiko for reviewing my draft version of this post: You found errors, asked the right questions and gave me hints to improve it. All remaining errors are my own.
Further reading in this blog: