- Setting up Parcel + Pug + Marked
- Technologies Used
- Setting Up Marked
- Supporting metadata
- Supporting Syntax Highlighting & KaTeX
- Adding a Table of Contents
- Usage within Pug
Setting up Parcel + Pug + Marked
I wanted a simple setup for this TIL blog that could handle plain HTML/CSS/JS but also provide flexibility and control over everything, starting from the raw request when required. I tried various options, including GatsbyJS + Netlify, Nextjs, and even a bunch of frameworks supported by Cloudflare Pages.
Technologies Used
Ultimately, I settled on using Pug for a cleaner template, Parcel for basic bundling, and deployed the project via Cloudflare Pages.
Parcel
Parcel: A self-described "zero configuration build tool."
Pug
Pug: Self-described as a "simple templating language with a strong focus on performance and powerful features". Pug supports using Markdown via filters
Marked
Markdown: Via Marked, a low-level compiler for parsing Markdown.
Cloudflare Pages
Cloudflare Pages: Provides lightening quick deployments to the Cloudflare global network.
Setting Up Marked
While most of the setup was straightforward, getting Markdown to work nicely, especially with support for metadata, syntax highlighting and KaTeX, required some additional configuration.
Supporting metadata
At the top of my Markdown posts, I include a header that looks like this:
---
title: Setting up Parcel + Pug + Marked
brief: Notes on setting up Parcel, Pug, and Markdown
date: 2024-01-26
tags: web-dev
---
To extract this metadata from the Markdown file before parsing it, I use the following function:
# metadata.js
export default (fileContent) => {
// Regex to capture YAML front matter between "---" lines
const frontMatterRegex = /^---\n([\s\S]*?)\n---/
const match = frontMatterRegex.exec(fileContent)
if (!match) {
throw new Error('Could not find front matter')
}
// Extract the YAML part and convert it into a key-value structure
const yaml = match[1]
const yamlLines = yaml.split('\n')
const metadata = {}
yamlLines.forEach((line) => {
const [key, ...rest] = line.split(':')
const value = rest.join(':').trim()
// If it's a list of tags, split by commas and trim each tag
if (key.trim() === 'tags') {
metadata[key.trim()] = value.split(',').map((tag) => tag.trim())
} else {
metadata[key.trim()] = value
}
})
const content = fileContent.replace(frontMatterRegex, '').trim()
metadata.content = content
return metadata
}
Supporting Syntax Highlighting & KaTeX
For the Markdown setup, I use the following libraries:
Here's how I configure them:
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import markedKatex from "marked-katex-extension"
import hljs from 'highlight.js'
import parseMeta from './metadata.js'
const marked = new Marked(
markedHighlight({
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
return hljs.highlight(code, { language }).value
},
}),
)
marked.use(markedKatex({
throwOnError: false
}))
export default {
filters: {
markdown: (text) => {
const { content } = parseMeta(text)
return marked.parse(content)
},
},
}
Adding a Table of Contents
To generate a table of contents for the Markdown posts, I use the marked-gfm-heading-id extension. This library automatically adds IDs to the headings in the generated HTML, making it easy to create a table of contents.
import { gfmHeadingId, getHeadingList } from "marked-gfm-heading-id"
marked.use(gfmHeadingId({ prefix: "til-" }),
{
hooks: {
postprocess(html) {
const headings = getHeadingList()
const toc = ({ id, text, level }) => `
<li class="h${level}">
<a href="#${id}">${text}</a>
</li>`
return `<ul class="toc">${headings.map(toc).join('')}</ul> ${html}`
}
}
}
)
Usage within Pug
Within Pug templates, I can simply include the Markdown file for a post:
body
.markdown-body
include:markdown ../posts/setting-up-parcel-pug-marked.md