Arcdown: Architect's Markdown Renderer
A small stack of Markdown tools (built on
markdown-it
) configured using the Architect team's preferred conventions for creating documentation and articles rendered and served from a cloud function.
Arcdown is an opinionated toolchain to create technical content from Markdown source files as quickly as possible to enable on-the-fly rendering in a Lambda (or any server) runtime.
All built-ins are configurable and extensible. |
npm install arcdown
ESM only, requires Node.js v14+
The simplest usage is to just pass Arcdown.render
a string of Markdown:
import { readFileSync } from 'node:fs'
import { Arcdown } from 'arcdown'
const mdString = `
---
title: Hello World
category: Examples
---
## Foo Bar
lorem ipsum _dolor_ sit **amet**
[Architect](https://arc.codes/)
`.trim()
const arcdown = new Arcdown()
const {
frontmatter, // attributes from frontmatter
html, // the good stuff: HTML!
slug, // a URL-friendly slug
title, // document title from the frontmatter
tocHtml, // an HTML table of contents
} = await arcdown.render(mdString)
const fromFile = await arcdown.render(readFileSync('../docs/some-markdown.md', 'utf-8'))
โ๏ธ See below for configuration options.
Arcdown.render
returns a RenderResult
object with 4 strings plus any document "frontmatter".
The Markdown document contents as HTML, unmodified, rendered by |
const { html } = await arcdown.render(mdString)
const document = `
<html>
<body>
<main>${html}</main>
</body>
</html>
` |
The document's table of contents as HTML (nested unordered lists). |
const { tocHtml, html } = await arcdown.render(mdString)
const document = `
<html>
<body>
<article>${html}</article>
<aside>${tocHtml}</aside>
</body>
</html>
` |
The document title, lifted from the document's frontmatter. |
const { title } = await arcdown.render(mdString)
console.log(`Rendered "${title}"`) |
A URL-friendly slug of the title. (possibly empty) Synonymous with links in the table of contents. |
const { slug } = await arcdown.render(mdString)
const docLink = `http://my-site.com/docs/${slug}` |
All remaining frontmatter. (possibly empty) The document's frontmatter is parsed by |
const { frontmatter } = await arcdown.render(file, options)
const sortedTags = frontmatter.tags.sort() |
Arcdown is set up to be used without any configuration. Out-of-the-box it uses defaults and conventions preferred by the Architect team (Architect project not required).
However, the renderer is customizable and extensible with a RendererOptions
object.
๐ชง See ./example/ for a kitchen sink demo.
Configure the core |
const arcdown = new Arcdown({
markdownIt: { linkify: false },
}) By default, |
Three plugins are provided out-of-the-box and applied in a specific order.
Set configuration for each plugin by passing a keyed RendererOptions.pluginOverrides
object.
โ๏ธ Disable a plugin by setting its key in
pluginOverrides
tofalse
.
Apply class names to each generated element based on its tag name. Provide a map of element names to an array of classes to be applied. This plugin is disabled unless configuration is provided. |
const arcdown = new Arcdown({
pluginOverrides: {
markdownItClass: {
// an element => class map
h2: [ 'title' ],
p: [ 'prose' ],
}
},
}) For performance reasons, this plugin was modified and bundled to |
Mark all external links (links starting with "http[s]://") with
|
const arcdown = new Arcdown({
pluginOverrides: {
markdownItExternalAnchor: {
domain: 'arc.codes',
class:'external',
},
},
}) |
A markdown-it plugin that adds an id attribute to headings and optionally permalinks. |
const arcdown = new Arcdown({
pluginOverrides: {
markdownItAnchor: {
tocClassName: 'pageToC',
},
},
})
{
tabIndex: false,
} |
A table of contents (TOC) plugin for Markdown-it with focus on semantic and security. Made to work gracefully with markdown-it-anchor. |
const arcdown = new Arcdown({
pluginOverrides: {
markdownItToc: {
containerClass: 'pageToC',
},
},
})
My Table of Contents:
${toc}
# The rest of
## My document |
It is possible to pass additional markdown-it
plugins to Arcdown's renderer by populating RendererOptions.plugins
.
Plugins can be provided in two ways and will be applied after the default plugins bundled with Arcdown.
The simplest method for extending |
import markdownItAttrs from 'markdown-it-attrs'
const arcdown = new Arcdown({
plugins: { markdownItAttrs },
}) |
If a plugin requires options, provide the Here the key name provided does not matter. |
import markdownItEmoji from 'markdown-it-emoji'
const arcdown = new Arcdown({
plugins: {
mdMoji: [
markdownItEmoji, // the plugin function
{ shortcuts: { laughing: ':D' } }, // options
],
},
}) |
A custom highlight()
method backed by Highlight.js is provided to the internal markdown-it
renderer. Arcdown will detect languages used in fenced code blocks in the provided Markdown string and attempt to register just those languages in hljs.
โ ๏ธ Currently, shorthand aliases for languages are not supported.
Full language names should be used with Markdown code fences. Instead ofjs
, usejavascript
Set Highlight.js configuration by passing a keyed RendererOptions.hljs
object.
A string that will be added to each |
const arcdown = new Arcdown({
hljs: {
classString: 'hljs relative mb-2',
},
}) |
Passed directly to
|
const arcdown = new Arcdown({
hljs: {
ignoreIllegals: false,
},
})
|
Additional language syntaxes can be added from third party libraries. |
import leanSyntax from 'highlightjs-lean'
const arcdown = new Arcdown({
hljs: {
languages: {
lean: leanSyntax, // add lean
powershell: false, // disallow powershell
},
},
}) |
Declare languages that should be registered when a specific language is detected. A common use-case is registering |
import leanSyntax from 'highlightjs-lean'
const arcdown = new Arcdown({
hljs: {
sublanguages: {
javascript: [ 'xml' ],
},
},
}) |
Highlight.js plugins can be passed to Arcdown's highlighter as an array of objects or class instances with functions keyed as hljs callbacks. See the hljs plugin docs for more info. |
class CodeFlipper {
constructor(options) {
this.token = options.token
}
'after:highlight'(result) {
result.value = result.value
.split(this.token)
.reverse()
.join(this.token)
}
}
const arcdown = new Arcdown({
hljs: {
plugins: [new CodeFlipper({ token: '\n' })],
},
}) |
A couple plugins have been forked and/or vendored locally to this package. This has been done to increase performance and render speed.
Arcdown is not attached to any single package, plugin, or even to the core rendering engine, so long as the resulting features are maintained.
Suggestions and PRs welcome ๐
A great balance of speed, stability, adoption, and extensibility.
Most syntax highlighters are not fast enough for server-side rendering. hljs was tuned to work on slow client machines and performs well on a server.
That said, starry-night
is really interesting.
Because we used it a lot building docs sites and technical blogs.
In no particular order
- markdown-it and their community for a solid .md ecosystem
- highlight.js for a battle-tested highlighter
- Architect and Begin for helping test/break things
- @galvez for the rad readme.md formatting conventions
- additional testing
- type defs
- benchmarks (try against remark)
- look for hljs perf increases
- expand typings with definitions from markdown-it
- web component enhancements ๐
- CLI for static file creation