A Vue / Jekyll Proof of Concept

Jul 11, 2017
Tags: Web Development Vue Jekyll

I’ve been continuing to play around with an isomorphic(ish) approach to building sites with Jekyll and Vue. In my last post, I looked at routing, navigation, and the history API. This time I look at sharing data, templating, and bringing it all together. Believing that the best way to “learn a thing”, is to just “build a thing”—I wen’t ahead and built out a little example application that brings all this stuff together in a single working site. The Vue code is super rough (still just learning)—I should be using props to pass down ship data from the home component to the ship component, I should add in transitions so moving through the routes looks smoother, etc.—but, “it works”™.

The Key Bits: works with/without JS, static pages and real URLS for SEO and usability, it’s fast un-optimized, and… Culture ships are cool.

Culture Namer

Why use Jekyll at all?

Before we get in too deep—It’s worth pointing out that you could just use Vue to render the static pages. Or you could run Vue on the server and have it render pages on the fly—something like Nuxt comes in handy for these types of things. If your primary concern is universal rendering—there’s no real reason to even reach for Jekyll. However, Jekyll is great. If you’re building a decoupled CMS, there’s lots of reasons why you might want to use Jekyll for the static bit—not the least of which, is that services like Siteleaf mean that you can easily add in a slick web-based interface for writers, editors, and content managers.

Sharing the data and getting all isomorphic

The real trick here is using one set of data to render both sets of views—one for the server and one for the client. So step one is building some JSON endpoints where Vue can consume the site, ship, ship-type, and book data used by Jekyll. Since we’re bundling our Vue code from the same repo as our Jekyll code we could just include the data directly via Webpack—but this may not always be the case (and, depending on the view, we may not always need all the data), so we’ll use Axios to pull on the endpoints as needed.

We setup _ships as a collection in Jekyll so each ship has its own Markdown file and frontmatter, indicating the ship type, the book it first appeared in, its name, and so on. So, we just step through all that, and generate the JSON file for Vue with Liquid. We’ll do the same thing with our _data files, one for the app (basic site level data like, site title, description, etc.), one for books, and one for ship-types.

---
title: shipData
---
[{% for ship in site.ships %}{
  "name": "{{ ship.title }}",
  "url": "{{ ship.url }}",
  "content": "{{ ship.content | strip_html | strip_newlines | remove:"Note: " | remove:'"'}}",
  "typeAbrev": "{{ ship.type-abrev }}",
  "typeLong": "{{ ship.type-long }}",
  "book": "{{ ship.book }}"
  }{% unless forloop.last %},{% endunless %}{% endfor %}]

Using Vue-Axios

Now that we’ve got our data endpoints, we can bring them into our Vue app. We use the created() hook (which triggers after the instance has been created, and data observation has already been setup) to update the existing siteData and shipData objects.

data () {
    return {
      siteData: [],
      shipData: [],
    }
  },
  created(){
    axios.get('/data/data.json').then(response => this.siteData = response.data);
    axios.get('/data/ships.json').then(response => this.shipData = response.data);
  }

Then we just dip into those data objects from each component as we need them. Below are the ship page views for both Jekyll and Vue. In Jekyll, each document in the collection gets its own URL. In Vue we’ll have to grab the ship name from the URL params and then pull the related data from shipData. In Jekyll we’ll use the where_exp filter to connect the book.yml and ship-type.yml to the current ship. In Vue we’ll have to use the v-for and v-if directives. Those types of differences aside, building templates for both Vue and Jekyll is remarkably simillar.

The Vue View

<div id="content" v-for="ship in shipData" v-if="ship.url.includes($route.params.name)">
      <h3>{{ ship.name }}</h3>
      <p>This ship first appeard in the book <a v-for="book in bookData" v-if="ship.book === book.name" v-bind:href="book.url">{{ ship.book }}</a>—the ship is a <strong>{{ ship.typeLong }} ({{ ship.typeAbrev }})</strong>. <span v-for="type in shipTypeData" v-if="ship.typeAbrev === type.type">{{ type.description }}</span></p>
      <p v-if="ship.content.length > 0"><span class="note">Note:</span> {{ ship.content }}</p>
    </div>
    <div class="button"><a v-on:click.prevent="$router.push(shipData[Math.floor(Math.random() * shipData.length)].url)" class="btn btn--lg btn--green">Pick a Random Ship</a></div>

The Jekyll View

<div id="content">
    <h3>{{ page.name }}</h3>
    <p>This ship first appeared in the book <a href="{{ book[0].booklink }}">{{ page.book }}</a>—the ship is a <strong>{{ page.type-long }} ({{ page.type-abrev }})</strong>. {{ type[0].description }}</p>
    {{ content }}
    <div class="button"><a href="{{ ship.url }}" class="btn btn--lg btn--green">Pick a Random Ship</a></div> 
  </div>

That they look so simillar, is a testament to just how intuitive it is to build templates in Vue—because Jekyll is wicked easy. As I said, my Vue code is still a bit rough—I assume there must be a better way to compare properties between shipData and BookData than stepping through each item, I just don’t know what it is right now—I guess I should be abstracting that out into a <template>?

Regardless, the point here is that there’s one store of data being used to populate two different views—one of those is generated at build time and shipped from the server, the other is generated on the client side (after the server ships down the necessary JSON).

Bringing it all together

This has been a fun little side project to mess with in my free time—and I’ll probably use this little boilerplate template for any Vue/Jekyll projects I build in the future, though none spring to mind. On the whole, I think this type of isomorphic approach has a lot of promise—though it is kind of a pain to have to biuld out all the views twice. It would be easier just to use Nuxt or the like—but if I’m building anything that non-technical users will need to interact with, I would prefer the ease and security of a Jekyll + Vue approach. One thing’s for sure, Vue is really great—and I’m looking forward to spending some more time getting to know it.


Vue Routing with Jekyll

Apr 5, 2017
Tags: Web Development Vue Jekyll

I’ve been continuing to play around with an isomorphic-ish approach to building sites with Jekyll and Vue—this week is routing. After we’ve pulled down the first page from the server, and begun relying on Vue, we still want to be able to move through the site while updating the URL, preserving the window’s history, and keeping things (components, sections, pages, etc.) organized in a way that makes sense—without resetting the whole DOM, obviously.

Luckily, the built in router for V2 let’s us do all this stuff. Of course, we don’t have to use the official router, we could drop in our own, or use one off the shelf—but theirs seems pretty great honestly.

Dynamic Matching & the History API

Once we’ve imported the module import VueRouter from 'vue-router' and told Vue to use it Vue.use(VueRouter), defining the routes is straightforward:

const routes = [
  { path: '/about', component: Page },
  { path: '/ship/:name', component: Ship }
]

We can define the routes manually, or use dynamic matching as you see above. We get back the matched value at $route.params.name so we can use the variable to do things like conditionally loading a block. This is how we’ll navigate around the site, after the first page load—instead of asking the server for a new page, we’ll either swap in a new component or grab the paramater from the route to update an existing one.

If we set mode: 'history' on the router object, we can use the history.pushState() method introduced with HTML5 to preserve our browsing history and reflect the changes we’re making to the DOM, in the URL.

const router = new VueRouter({
  mode: 'history',
  routes 
})

So, looking at a ship entry for Sanctioned Parts List the URL will read /ship/santioned-parts-list. Because we’re doing this in an isomorphic-ish way, we don’t have to worry about direct requests to that URL getting 404’d—a static version of the page already exists on the server thanks to Jekyll.

Programatic Navigation

We can use <router-link to='/about'>About</router-link> to explicitly link to a route—or we can use the router’s instance methods to navigate programatically router.push('/ship/little-gravitas'). If, for instance we wanted to generate a random link to a page, we could abstract that logic out into a separate method and then call it with an v-on:click directive <button @click="getShip">Generate Ship</button>.

Moving on from Here

Next up, is to figure out how to build templates for both Jekyll and Vue that leverage as much of the same code as possible. As a single source of data drives both views, the templates should also share as much as possible.


Vue Filters

Mar 16, 2017
Tags: Web Development Vue

In Vue, we can call filters on text just like we would when templating in Liquid with Jekyll. For instance, if we wanted to transform some Markdown into HTML we could do something like this:


...
message: "**The boy stood on the burning deck**, whence all but he had fled; The flame that lit the battle's wreck _Shone round him o'er the dead_."
...
{{ message | markdownify }}
...

Giving us:

<p><strong>The boy stood on the burning deck</strong>, whence all but he had fled; The flame that lit the battle&#39;s wreck <em>Shone round him o&#39;er the dead</em>.</p>

However, in Vue 2.0 pre-set filters were removed, and the use of filters was restricted to text interpolation and v-bind directives. Adding them back in is pretty painless though. We just include a filters: object and define our function:

import marked from 'marked'

var app = new Vue({
  el: '#app',
  data: {
    message: "**The boy stood on the burning deck**, whence all but he had fled; The flame that lit the battle's wreck _Shone round him o'er the dead_."
  },
  filters: {
     markdownify: function (val) {
          return marked(val);
     }
  }
})

In this case, we’re bringing in Marked and letting it do the heavy lifting. The only problem here is that our mustache tags are going to embed what was returned by the filter as plain text on the page—when what we want, is to embed the HTML. To embed HTML we need to use the v-html directive. It would be great if we could do something like <p v-html="message | markdownify"></p> but we can only call filters in the mustache and v-bind syntax. So, there’s a couple things here we could do. We could turn our filter into a method and then call it from within the v-html directive:

import marked from 'marked'

var app = new Vue({
  el: '#app',
  data: {
    message: "**The boy stood on the burning deck**, whence all but he had fled; The flame that lit the battle's wreck _Shone round him o'er the dead_."
  },
  methods: {
     markdownify: function (val) {
          return marked(val);
     }
  }
})
<div id="app">
<p v-html="markdownify(message)"></p>
</div>

Or, if we want to take advantage of caching—we could store the processed string in a computed property. This way we can process it once, and reuse it whenever we want, without having to call marked each time:

var app = new Vue({
  el: '#app',
  data: {
    message: "**The boy stood on the burning deck**, whence all but he had fled; The flame that lit the battle's wreck _Shone round him o'er the dead_."
  },
  methods: {
     markdownify: function (val) {
          return marked(val);
     }
  },
  computed: {
    rawmessage: function(){
      return marked(this.message)
    }
  }
})
<div id="app">
{{ rawmessage }}
</div>

Still lot’s to learn about Vue, and I wouldn’t be surprised if in a month or two I come to realize that I’m doing it all wrong here—but for now, this seems to work. If I’m going to be rendering Markdown via JSON via Jekyll, I imagine I’ll be using a mix of these approaches. For body text where I don’t have to worry about caching for the next page, I would probably opt for the method option—for headers and navigation, maybe computed properties would make more sense. Or not. I won’t be surprised if I come back in a month and completely refactor all this stuff, but I guess that’s just how I learn.


Vue and Jekyll

Mar 15, 2017
Tags: Jekyll Web Development Vue

I’ve been using Vue a bit lately, and I love it. If you’re not familiar with it, you should really check it out. In a nut: it’s a client-side JS framework, much like Ember, Angular, React, etc. It borrows conventions from all those other frameworks and ends up with—what I think anyways—is the simplest, most elegant, client-side framework around. Have a look at the docs and see for yourself.

Now, I’m also a big Jekyll fan, and love what static sites offer—so I thought why not combine them for the best of both worlds?

Isomorphic blah blah blah

I know, bear with me. Let’s say a user requests a URL from our site and we ship them the pre-rendered static page that’s sitting on our CDN. Great, we’ve used our blinged out CDN with HTTP2/Server Push, extreme caching, asset compression, double cup holders, and cloud bleed to get to first paint in record time—we also sent down a small bit of JSON while we we’re at it. Now, if JS is supported, what if the next request didn’t have to go to the server at all? What if instead, we used Vue—and a small bit of JSON to build the next page locally, in the browser? Spoiler: It would be wicked fast.

Isomorphic Demo

The trick then, is to be able to build the same page, with the same data on both the server and the client—an isomorphic view if you will. I’m probably a bit late to the game on all this isomorphic stuff, but better late than never.

I’ve been playing around with this pattern a bit and put together this bare-bones template for experiements. Have a look, and check out the demo—turn off Javascript to get the statically rendered page, re-enable JS to see the Vue view. The project relies on Webpack, and NPM to pull everything together.

I’ll be posting more about Vue as I dig in and learn more about it. So far I think it’s pretty fabulous. Single file components neatly separate template code from component methods and data, no JSX here. Angular-like directives make doing things like <p v-for="post in posts"></p> dead simple. That same kind of simplicty goes for event handling <button @click="submit.prevent">Submit</button>, conditional rendering <div v-if="logged-in">...</div>, attribute and class binding, and more. Check it out, it’s pretty great.


The Distress Signal is the personal blog of Bryan Schuetz. Bryan has been complaining on the Internet since the 90s. If you'd like to get in touch with Bryan, you can find him on twitter.