Primate

Markdown

Primate runs Markdown templates with server-side rendering, frontmatter support, and table of contents generation.

Primate uses marked to compile Markdown to HTML.

Setup

Install

npm install @primate/markdown

Configure

import config from "primate/config";
import markdown from "@primate/markdown";

export default config({
  modules: [markdown()],
});

Templates

Create Markdown templates in components using standard Markdown syntax.

<!-- components/post-index.md -->
# All Posts

Here are all the posts:

## First Post
Introduction to Primate

## Second Post
Building applications

Serve the template from a route:

// routes/posts.ts
import response from "primate/response";
import route from "primate/route";

route.get(() => response.view("post-index.md"));

Frontmatter

Use YAML frontmatter to define metadata within your Markdown files.

---
title: "My Blog Post"
author: "John Adams"
date: "2024-01-01"
published: true
---

# My Blog Post

By John Adams on 2024-01-01

This is the content of the post.

Serve the Markdown file:

// routes/blog.ts
import response from "primate/response";
import route from "primate/route";

route.get(() => response.view("blog-post.md"));

Table of Contents

Markdown automatically generates a table of contents from headings within the document.

<!-- components/article.md -->
# Article Title

## Introduction

Some intro text.

## Main Content

More content.

## Conclusion

Final thoughts.

Serve the article:

// routes/article.ts
import response from "primate/response";
import route from "primate/route";

route.get(() => response.view("article.md"));

The rendered component includes toc data with heading information that can be used to generate navigation.

Using the Table of Contents

Access the toc data and frontmatter meta in your route to build navigation:

// routes/docs/[page].ts
import type Component from "@primate/markdown/Component";
import respone from "primate/response";
import route from "primate/route";

route.get(request => {
  const page = request.path.get("page");

  return app => {
    const { html, toc, meta } = app.component<Component>(`docs/${page}.md`);

    return response.view("DocPage.html", {
      content: html,
      toc,
      meta,
      title: meta.title || page,
    })(app, {}, request);
  };
});

Create an HTML component that uses the table of contents and metadata:

<!-- components/DocPage.html -->
<div class="doc-layout">
  <header>
    <h1>${meta.title || title}</h1>
    ${meta.author ? `<p class="author">By ${meta.author}</p>` : ""}
    ${meta.date ? `<p class="date">${meta.date}</p>` : ""}
  </header>

  <nav class="toc">
    <h3>Table of Contents</h3>
    <ul>
      ${toc.map(item => `
        <li>
          <a href="#${item.slug}">${item.text}</a>
        </li>
      `).join("")}
    </ul>
  </nav>

  <main class="content">
    ${content}
  </main>
</div>

You must have @primate/html installed and configured in your config/app.ts, or alternatively any other frontend for this.

Configuration

Option Type Default Description
fileExtensions string[] [".md"] Associated file extensions
options MarkedExtension {} marked options
pretransform Pretransform () => void Pretransform function

Example

import markdown from "@primate/markdown";
import config from "primate/config";

export default config({
  modules: [
    markdown({
      // add `.markdown` to associated file extensions
      fileExtensions: [".md", ".markdown"],
    }),
  ],
});

Resources

Previous
Handlebars
Next
Marko