2.3.0 • Published 2 months ago

arcdown v2.3.0

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
2 months ago

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.

Contents

  1. Usage
    1. Installation
    2. Example
    3. Render Result
  2. Configuration
    1. MarkdownIt Renderer
    2. Plugin Overrides
    3. User-Provided Plugins
    4. Highlight.js Config
  3. Development & Contributing
    1. FAQs & Decisions
    2. Credits
    3. Todo

What is this?

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.

Features

  • Document table of contents creator
  • Code syntax highlighter
  • Automatic frontmatter parsing
  • Generated HTML optimizations
  • Helpful return values

All built-ins are configurable and extensible.

Usage

Installation

npm install arcdown

ESM only, requires Node.js v14+

Example

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.

Render Result

Arcdown.render returns a RenderResult object with 4 strings plus any document "frontmatter".

html: string

The Markdown document contents as HTML, unmodified, rendered by markdown-it.

const { html } = await arcdown.render(mdString)

const document = `
<html>
<body>
  <main>${html}</main>
</body>
</html>
`

tocHtml: string

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>
`

title: string

The document title, lifted from the document's frontmatter.

const { title } = await arcdown.render(mdString)

console.log(`Rendered "${title}"`)

slug: string

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}`

frontmatter: object

All remaining frontmatter. (possibly empty)

The document's frontmatter is parsed by gray-matter and directly returned here.

const { frontmatter } = await arcdown.render(file, options)

const sortedTags = frontmatter.tags.sort()

Configuration

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.

markdown-it Config: markdownIt

markdownIt

Configure the core markdown-it renderer.
This config is passed directly to new MarkdownIt()

const arcdown = new Arcdown({
  markdownIt: { linkify: false },
})

By default, html, linkify, and typographer are enabled.

Plugin Overrides: pluginOverrides

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 to false.

markdownItClass

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.
Perfect for utility class libraries.

This plugin is disabled unless configuration is provided.

markdown-it-class docs

const arcdown = new Arcdown({
  pluginOverrides: {
    markdownItClass: {
      // an element => class map
      h2: [ 'title' ],
      p: [ 'prose' ],
    }
  },
})

For performance reasons, this plugin was modified and bundled to ./src/vendor/

markdownItExternalAnchor

Mark all external links (links starting with "https://") with target=_blank and an optional class.

markdown-it-external-anchor defaults are used in Arcdown.

markdown-it-external-anchor docs

const arcdown = new Arcdown({
  pluginOverrides: {
    markdownItExternalAnchor: {
      domain: 'arc.codes',
      class:'external',
    },
  },
})

markdownItAnchor

A markdown-it plugin that adds an id attribute to headings and optionally permalinks.

markdown-it-anchor docs

const arcdown = new Arcdown({
  pluginOverrides: {
    markdownItAnchor: {
      tocClassName: 'pageToC',
    },
  },
})

markdown-it-anchor is pre-configured with:

{
  tabIndex: false,
}

markdownItToc

A table of contents (TOC) plugin for Markdown-it with focus on semantic and security. Made to work gracefully with markdown-it-anchor.

markdown-it-toc-done-right docs

const arcdown = new Arcdown({
  pluginOverrides: {
    markdownItToc: {
      containerClass: 'pageToC',
    },
  },
})

markdown-it-toc-done-right enables users to include a copy of the table of contents in their markdown:

My Table of Contents:

${toc}

# The rest of

## My document

User-Provided Plugins: plugins

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.

plugins

The simplest method for extending markdown-it is to import a plugin function and provide it directly.

import markdownItAttrs from 'markdown-it-attrs'

const arcdown = new Arcdown({
  plugins: { markdownItAttrs },
})

plugins with options

If a plugin requires options, provide the markdown-it plugin as a tuple where the first item is the function and the second is the plugin options.

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
    ],
  },
})

Highlight.js (hljs) Config: hljs

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 of js, use javascript

Set Highlight.js configuration by passing a keyed RendererOptions.hljs object.

classString: string

A string that will be added to each <pre class=""> wrapper tag for highlighted code blocks.

const arcdown = new Arcdown({
  hljs: {
    classString: 'hljs relative mb-2',
  },
})

ignoreIllegals: boolean

Passed directly to hljs.highlight(). The docs say:

when true forces highlighting to finish even in case of detecting illegal syntax for the language...

const arcdown = new Arcdown({
  hljs: {
    ignoreIllegals: false,
  },
})

ignoreIllegals: true is the default, but can be set by the user.

languages: object

Additional language syntaxes can be added from third party libraries.
If needed, Highlight.js built-in languages can be disabled by setting their key to false.

import leanSyntax from 'highlightjs-lean'

const arcdown = new Arcdown({
  hljs: {
    languages: {
      lean: leanSyntax, // add lean
      powershell: false, // disallow powershell
    },
  },
})

sublanguages: object

Declare languages that should be registered when a specific language is detected.

A common use-case is registering 'xml' for 'javascript' to enable HTML highlighting for html string templates.

import leanSyntax from 'highlightjs-lean'

const arcdown = new Arcdown({
  hljs: {
    sublanguages: {
      javascript: [ 'xml' ],
    },
  },
})

plugins: object[]

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' })],
  },
})

Development & Contributing

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 🙏

FAQs & Decisions

Why markdown-it?

A great balance of speed, stability, adoption, and extensibility.

Why Highlight.js?

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.

Why plugin ___?

Because we used it a lot building docs sites and technical blogs.

Credits

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

Todo

  • 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
2.3.0

2 months ago

2.3.0-RC.1

2 months ago

2.3.0-RC.2

2 months ago

2.3.0-RC.3

2 months ago

2.3.0-RC.0

3 months ago

2.2.1

8 months ago

2.2.0

10 months ago

2.1.1

1 year ago

1.0.1

2 years ago

1.0.0

2 years ago

0.5.0

2 years ago

0.4.0

2 years ago

2.1.0

2 years ago

2.0.0

2 years ago

0.6.0

2 years ago

0.3.2

2 years ago

0.3.1

2 years ago

0.3.0

2 years ago

0.2.0

2 years ago

0.1.1

2 years ago