v1.0.0

@bongo/markdown

@bongo/markdown is the Markdown loader plugin for bongo, powered by md4x.

Install

Shell
bun add @bongo/markdown

Usage

bongo.config.tsts
import { markdown } from '@bongo/markdown';

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

The plugin registers an .md loader. It converts Markdown files to HTML content files, and exposes the frontmatter as page metadata:

content/hello.mdmd
---
title: Hello
tags: docs, markdown
---

# Hello

See Content and Frontmatter.

Elements

A <bongo:name> element in Markdown prose calls the partial with that name. bongo leaves elements inside code blocks and inline code as written.

The element's attributes become the partial's arguments. bongo reads them as data, and never runs them as code:

Markdown
<bongo:card title="Hello" count=3 draft=false tags=[js, css] meta={ author: 'Luke' } featured />
  • A quoted value, "…", '…', or `…`, is a string. Backslash escapes work as in JavaScript. ${…} is plain text.
  • A bare value is a number, true, false, null, or else text, so src=img/a.png is a string.
  • […] is a list and {…} is a map, written like JSON5: keys need no quotes, and a trailing comma is fine.
  • A name with no value is true.

Content between tags

An element can also wrap Markdown. The plugin renders the Markdown between the tags to HTML, and passes it to the partial as $slot:

Markdown
<bongo:card title="Hello">
Some *Markdown* text.
</bongo:card>

A .hbs partial prints it with {{{ $slot }}}. It does not need to declare $slot:

partials/card.hbshbs
{{#expect title}}
<article class="card"><h3>{{ title }}</h3>{{{ $slot }}}</article>

Built-in elements

bongo's own elements, <bongo:asset>, <bongo:image>, <bongo:style>, <bongo:script>, and <bongo:client>, pass through untouched, for bongo to replace. See Special elements.

Images

Images next to a post need no element. bongo gives ![alt](hero.jpg) and <img src="hero.jpg"> a srcset and their dimensions. Use <bongo:image> for a <picture> with more formats, or for your own sizes. See Images.

Markdown
<figure>
	<bongo:image src="./hero.jpg" alt="…" sizes="(min-width: 800px) 720px, 100vw" />
	<figcaption>…</figcaption>
</figure>

Headings

The plugin gives each page its headings, in document order, as $page.headings. Each heading has a level, an id, its plain text, and its inner html, which keeps inline code and links. The headings are also in each entry of $pages, and in the $pages of data modules. A table of contents in a layout:

Handlebars
<ol>
{{#each $page.headings.filter(h => h.level === 2) as h}}
	<li><a href="#{{ h.id }}">{{{ h.html }}}</a></li>
{{/each}}
</ol>

The id is the anchor that headingIds: true gives the heading. Without that option, the page HTML has no anchors, so set headingIds: true when you link to headings. A line in a code block is not a heading. A partial in a heading is in html, but not in text or id.

Options

markdown() accepts md4x HTML options. For example, headingIds: true gives each heading an id that links can target. Syntax highlighting is optional:

bongo.config.tsts
import { markdown } from '@bongo/markdown';
import { shiki } from '@bongo/markdown/highlight';

export default {
	plugins: [
		markdown({
			highlighter: shiki({
				theme: 'github-dark',
				langs: ['js', 'ts', 'css', 'bash'],
			}),
		}),
	],
};

Highlighters

Install the highlighter package that you want to use:

Shell
bun add shiki
bun add sugar-high

Then import from @bongo/markdown/highlight:

TypeScript
import { shiki, sugarhigh } from '@bongo/markdown/highlight';

Shiki

shiki(options) loads Shiki with the requested theme and languages. A fence in a language that is not in langs renders as escaped plain text in the same theme. Set unknown: 'throw' to fail the build instead. A fence with no language keeps the plain md4x block.

Sugar High

sugarhigh() loads Sugar High, version 2 or later. It highlights fenced code in a language that Sugar High knows, such as ts, py, or bash. Other code blocks stay plain. Sugar High colors tokens with --sh-* CSS variables, so add a theme to your styles.

Your own highlighter

A highlighter can also be your own function. md4x calls it once for each code block, with the code and the block details, such as lang. It must return synchronously: either the HTML for the whole block, <pre> included, or undefined to keep the plain block.

Twinkleplop

Twinkleplop writes CSS classes, not inline colors, so a theme stylesheet gives the colors. Install its markdown core, one package for each language that you use, and a theme:

Shell
bun add @twinkleplop/markdown-core @twinkleplop/typescript @twinkleplop/bash @twinkleplop/theme-github

Then give your languages to twinkleplop() from @bongo/markdown/twinkleplop:

bongo.config.tsts
import { markdown } from '@bongo/markdown';
import { twinkleplop } from '@bongo/markdown/twinkleplop';
import { language as bash } from '@twinkleplop/bash';
import { language as typescript } from '@twinkleplop/typescript';

export default {
	plugins: [
		markdown({
			highlighter: twinkleplop({
				languages: {
					typescript: typescript(),
					bash: bash(),
				},
			}),
		}),
	],
};

Languages

languages maps a fence name to a highlighter, or to the name of another entry, such as js: 'typescript'. Common aliases resolve to the languages that you register: typescript also covers ts, mts, and cts fences, and bash covers sh, shell, and zsh. Your own entries take priority.

A fence in a language that is not in languages renders as escaped plain text. Set on_unknown_language: 'throw' to fail the build instead. A fence with no language keeps the md4x <pre><code> output, unless you set default_language.

Other options

The other options, such as default_language, line_numbers, and render, are the same as in the twinkleplop markdown plugins. Fences can mark lines with {1,3-4}, add a title with [app.ts], and turn on line numbers with ts:line-numbers.

Theme

Import the theme in your CSS by its package name. See Packages in @import.

assets/app.csscss
@import "@twinkleplop/theme-github";

.twinkleplop {
	background: var(--twp-background);
}
Handlebars
<bongo:style src="app.css"></bongo:style>

The theme has light and dark colors. A dark class on an ancestor, such as <html class="dark">, selects the dark colors. To use one set only, import @twinkleplop/theme-github/light or @twinkleplop/theme-github/dark.

Code blocks

fence wraps the HTML of each code block. Use it to add a title bar, a language label, or a copy button. It works with every highlighter, and without one:

bongo.config.tsts
import { markdown } from '@bongo/markdown';
import type { Fence } from '@bongo/markdown';

const escape = (text: string) => text.replace(/[&<>"]/g, char => `&#${char.charCodeAt(0)};`);

const fence: Fence = (html, block) => {
	let title = escape(block.filename ?? block.lang);
	return `<figure class="code"><figcaption>${title}</figcaption>${html}</figure>`;
};

export default {
	plugins: [markdown({ fence })],
};
  • html is the output of the highlighter. When there is no highlighter, or when the highlighter returns undefined, such as for an unknown language, it is the md4x <pre><code> block with the code escaped.
  • block is the block that the highlighter gets, typed as Fence.Block: lang (an empty string when the block has no language), filename from [app.ts], and highlights from {1,3-4}. It also has code, the text of the block, with its last line break.
  • Every code block goes through fence: fences with a language, fences without one, and indented code blocks.
  • When you set fence, the highlighter does not get filename. Thus a highlighter that prints its own title, such as twinkleplop with [app.ts], does not add a second one. Print the title in fence.
  • lang and filename are text from your Markdown, as you wrote it. Escape them before you put them in HTML.
  • fence runs during the render, so it must return a string, not a promise.
Edit this page on GitHub MIT license · bongo v1.0.0

    Type to search page titles and sections.