Vue single file components in Jekyll

In a previous post I briefly sketched how Vue apps can be integrated into Jekyll blog posts using script tags. However, most Vue apps need some pre-processing before they can be integrated into a page. Here, I’ll dig deeper into how one can integrate Vue applications using pre-processors.

The problem

Vue pre-processors take .vue files and compile them to JavaScript, HTML, and CSS. Jekyll in contrast takes kramdown files and HTML templates and renders them to JavaScript, HTML, and CSS. Obviously, neither of the two can take the output of the other as input, so you cannot simply chain them.

Another problem is, that if you build Vue files for a production environment, the JavaScript and CSS files will get minified, which means you cannot easily access them from a tag of your Jekyll posts, as all the names of your objects and functions get changed.

Possible solutions

I can think of three workarounds that allow you to integrate Vue apps to your Jekyll pages:

  1. write your own Vue pre-processor that outputs something that Jekyll can use, e.g. an HTML template snipped or some kramdown files.
  2. develop a Vue app independently and add it via a DOM element.
  3. publish your components as libraries and develop your Vue apps in your Jekyll posts.

The first solution is IMO the most elegant, but it is also the one that requires the most work. The second option is easier to implement, but offers less flexibility building apps directly within blog posts, because the App is developed outside of the blog post. The third option allows to develop structured Vue apps within a blog post but requires to split your Vue app into multiple component libraries and compile each one of them separately.

My main motivation is to create Vue apps composed of multiple components directly in a blog post, so I’ll stick with the third option here.

Creating Vue component libraries

To export your components as libraries, you need to do two things: add an entry point for your library, and then compile them as a library. To use them in a Jekyll post, you need to include the JavaScript- and the CSS file in your blog post via according tags.

Add an entry point to your component. The purpose of the entry point is to allow you to define the exports of your component and to register your component in Vue. So to create a component library, create a folder, move your component(s) to this folder and create an index.js. An example of an index.js could look like this:

import Vue from "vue";
import MyComponent from "./MyComponent.vue"

const components = {MyComponent};

Object.keys(components).forEach(name=>{
  Vue.component(name,components[name])
})

export default Components;

In this brief example, I register and export one component: MyComponent.vue. If I wanted to add more components, I would need to import them and add them to the components object. The purpose of the registration is to allow you to access your component after the JavaScript gets minified.

Compile component as a library. To compile the component as a library, you need to invoke the vue-cli-service build command with “–target lib” parameter. The most convenient way to do so is to add a script in your pachage.json. This build script should point to the index.js of your component library. An example of that could look like this:

"scripts": {
  ...
  "build-my-lib": "vue-cli-service build --no-clean --fix  --target lib --name my-component ./src/components/my-component/index.js",
  ...
},

This example would create compiles Vue component and creates the my-component .js and .css files in the output folder. The output folder is typically specified in the vue.config.js file.

Include stylesheets. Next, one needs to include the stylesheet component files in the blog post. The stylesheet can be included in the body tag. While it is considered to be best practice to include stylesheets in the head element, link elements that contain stylesheets are actually body-ok thus the page will be loaded correctly.

...
<link href="/vue-app/my-component.css" rel="stylesheet">
...

Define Vue app. Next, to use your component in a blog post, you need to define the Vue app there. As described in my previous post, the container element needs to be encapsulated with raw/endraw tags, but other than that it is straight forward.

<div id="app" markdown="0">
  <my-component/>
</div>

Finally, you need to load the Vue-app, which follows a fairly standard way of doing so. The only thing you have to keep you need to the module attribute of the *

<script type="module">
import "/vue-app/my-component.umd.js";
  new Vue({
    el: '#app',
  })
</script>

Multiple component libraries. For adding multiple component libraries to your project, you have to repeat the previous four steps for each of the libraries. Compiling multiple libraries by hand one after each other can get cumbersome. To build multiple libraries at once, you can use the npm-run-all-package. It allows you to define build steps that run other build steps.

For example, suppose all the build steps for your libraries are prefixed with “-lib”. Then using the npm-run-all package you could add another build script that invokes all other build scripts referenced by a glob-like naming. So in our example, “build-lib-*” would invoke all build steps that start with “build-lib-“

"scripts": {
  ...
  "build-lib-x": "...",
  "build-lib-y": "...",
  "build-all-libs": "npm-run-all build-lib-*",
  ...
},

Conclusion

Vue allows us to define reusable components, but they need to be pre-processed to be included in a web-page. This pre-processing poses a problem when combined with other content rendering engines such as Jekyll, which also needs to process files to create something displayable.

In this post I’ve outlined a strategy that allows you to use Vue component libraries directly in Jekyll posts. To do so, you need to define and compile the Vue components as component libraries and then integrate load them from your blog post.

The described strategy allows you to develop your Vue app directly in your blog posts and to reuse your Vue components. The downside of this strategy is some configuration overhead and Vue apps get more difficult to debug.