Gustave helps you to generate static sites from markdown or yaml files with Nuxt npm run generate
command, by transforming Markdown or yaml files into JSON files. Those JSON files can then be easily consumed by your Vue.js components.
Features
- Frontmatter support for markdown files
- Handle dates in filenames for blogging, like Jekyll (e.g:
2018-08-21-apples.md
) - Hot-reloading when markdown files are changed
- Node v10.12.0 (uses
{ recursive: true }
option fromfs.mkdir
)
Install Gustave as a dev dependency only, as it is needed only at compile time to generate the JSON files from our markdown files.
npm install nuxt-gustave --save-dev
Add static/api
directory to .gitignore
file. This is where JSON files will be generated by default.
# nuxt-gustave
static/api
The core concept of Gustave are compilers : a compiler is just a function exporting a compile
method. This compile
method fetches data from somewhere (for example, local mardown files), save them as JSON and return to Nuxt an array of dynamic routes if needed ( for example : ['user/1', 'user/4', 'user/18']
).
π‘Thoses routes array are required for dynamic routes by
npm run generate
command, see https://nuxtjs.org/api/configuration-generate#routes . This is not required for static routes like/contact
or/about
. If you return them directly from your compiler, you don't need to add them manually tonuxt.generate.routes
property fromnuxt.config.js
.
Create a compilers/posts.js
file that will turn mardown files from a content/posts
directory into a static/api/posts.json
file
const { parseMarkdownDirectory } = require('nuxt-gustave/lib/markdown')
const { saveToJsonDirectory } = require('nuxt-gustave/lib/gustave')
exports.compile = () => {
const resources = parseMarkdownDirectory('content/posts')
saveToJsonDirectory('posts.json', resources)
return resources.map(resource => `/posts/${resource.$slug}`)
}
NOTA BENE : we declared here an array of routes with /posts/${resource.$slug}
: this mean than, later, we MUST create a corresponding pages/posts/_slug.vue
component to handle thoses routes, to actually generate our html.
You can also convert a single file to markdown :
const { parseMarkdownFile } = require('nuxt-gustave/lib/markdown')
const { saveToJsonDirectory } = require('nuxt-gustave/lib/gustave')
exports.compile = () => {
const resource = parseMarkdownFile('content/settings.md')
saveToJsonDirectory('settings.json', resource)
return []
}
Now we have to configure nuxt.config.js
file to use Gustave module and register our new compilers/posts.js
compiler.
module.exports = {
// ...
// add nuxt-gustave module
modules: ['nuxt-gustave'],
// register compilers to use:
gustave: {
compilers: [
'compilers/posts.js',
// you can pass options to your compiler with array notation, if needed:
// ['compilers/posts.js', { hello: 'world' }]
]
}
}
In content/posts
directory, add two posts:
π content/posts/first-post.md
---
title: this is my firt blog post :D
date: 2019-01-02
---
Hello there, this is my first blog post with Gustave.
π content/posts/second-post.md
---
title: second post
date: 2019-01-03
---
Another post in mardown.
Now , if we run npm run dev
, npm run generate
or npx nuxt-gustave
command, a static/api/posts.json
file will be automatically created. Simply editing those markdown files will also automatically regenerate the JSON file.
[
{
"title": "second post",
"date": "2019-01-03T00:00:00.000Z",
"$html": "<p>Another post in mardown. This is the future.</p>\n",
"$id": "second-post.md",
"$slug": "another-post"
},
{
"title": "this is my firt blog post :D",
"date": "2019-01-02T00:00:00.000Z",
"$html": "<p>Hello there, this is my first blog post with Gustave.</p>\n",
"$id": "first-post.md",
"$slug": "hello"
}
]
Gustave added some useful variables here :
$html
: the mardkown rendered as html.$id
: a uniq id to identify this resource. Filename is used by default.$slug
: a slug generated from the filename to build pretty urls like "/posts/my-second-post"
All thoses variables can be overriden inside the compiler, before the resources are saved as a JSON file or even directly in the markdown front-matter:
---
$slug: react-wordpress-reactpress
---
We now have a posts.json
file that can be used from our components. Below are a simple example of how it could be done, but from this point; it's really up to you : Gustave's Job is only to generate the JSON files declaring the routes.
Display all posts : π pages/posts/index.vue
<template>
<div>
<div v-for="post in posts" :key="post.$slug">
<h2>
<nuxt-link :to="`/posts/${post.$slug}`">{{post.title}}</nuxt-link>
</h2>
<div v-html="post.$html" />
</div>
</div>
</template>
<script>
import posts from 'static/api/posts.json'
export default {
computed: {
posts() {
return posts
}
}
}
</script>
Display a single post : pages/posts/_slug.vue
<template>
<div>
<h1>{{post.title}}</h1>
<div v-html="post.$html"></div>
</div>
</template>
<script>
import posts from 'static/api/posts.json'
export default {
computed: {
post() {
return posts.find(post => post.$slug === this.$route.params.slug)
}
}
}
</script>
Gustave is using Markdown-it to render markdown, with a default instance. We can pass your own markdownIt instance to get the full control about markdownIt configuration :
module.exports = {
// ...
gustave: {
markdownIt() {
const markdownIt = require('markdown-it')({
html: false,
linkify: true
})
// we can add plugins here too
return markdownIt
}
// ...
}
}
Code syntaxic coloration with markdownIt can be enable automatically with the highlight
options (behind the hood, this will simply inject automatically CSS and JS from highlight.js npm package) :
module.exports = {
// ...
gustave: {
highlight: true
// ...
}
}
If you have a lot of posts, it is easier to retrieve quickly a post if your filename starts with the date. This what the "jekyllMode" is for :
For the following directory structure:
π content
π posts
π 2018-07-02-my-first-post.md
π 2018-08-03-my-last-post.md
You can create the following compiler :
exports.compile = () => {
const resources = parseMarkdownDirectory('content/posts', {
jekyllMode: true
})
saveToJsonDirectory('posts.json', resources)
return resources.map(resource => `/blog/${resource.$slug}`)
}
It will create the following JSON in static/api
, already sorted by date and with a $date field:
[
{
"$date": "2018-07-02",
"$slug": "my-last-post",
"$id": "my-last-post.md",
"$html": "<div>html content of my last post</div>"
},
{
"$date": "2018-07-02",
"$slug": "my-first-post",
"$id": "my-first-post.md",
"$html": "<div>html content of my first post</div>"
}
]
if you have a field with an ISO Date (in the yaml frontmatter for example), you can use sortResoucesByDate
function manually to sort resources before saving them as JSON. For example:
resources = sortResourcesByDate(resources, '$date', 'asc')