616 lines
15 KiB
Markdown
616 lines
15 KiB
Markdown
# remark-frontmatter
|
||
|
||
[![Build][build-badge]][build]
|
||
[![Coverage][coverage-badge]][coverage]
|
||
[![Downloads][downloads-badge]][downloads]
|
||
[![Size][size-badge]][size]
|
||
[![Sponsors][sponsors-badge]][collective]
|
||
[![Backers][backers-badge]][collective]
|
||
[![Chat][chat-badge]][chat]
|
||
|
||
**[remark][]** plugin to support frontmatter (YAML, TOML, and more).
|
||
|
||
## Contents
|
||
|
||
* [What is this?](#what-is-this)
|
||
* [When should I use this?](#when-should-i-use-this)
|
||
* [Install](#install)
|
||
* [Use](#use)
|
||
* [API](#api)
|
||
* [`unified().use(remarkFrontmatter[, options])`](#unifieduseremarkfrontmatter-options)
|
||
* [`Options`](#options)
|
||
* [Examples](#examples)
|
||
* [Example: different markers and fences](#example-different-markers-and-fences)
|
||
* [Example: frontmatter as metadata](#example-frontmatter-as-metadata)
|
||
* [Example: frontmatter in MDX](#example-frontmatter-in-mdx)
|
||
* [Authoring](#authoring)
|
||
* [HTML](#html)
|
||
* [CSS](#css)
|
||
* [Syntax](#syntax)
|
||
* [Syntax tree](#syntax-tree)
|
||
* [Types](#types)
|
||
* [Compatibility](#compatibility)
|
||
* [Security](#security)
|
||
* [Related](#related)
|
||
* [Contribute](#contribute)
|
||
* [License](#license)
|
||
|
||
## What is this?
|
||
|
||
This package is a [unified][] ([remark][]) plugin to add support for YAML, TOML,
|
||
and other frontmatter.
|
||
|
||
Frontmatter is a metadata format in front of the content.
|
||
It’s typically written in YAML and is often used with markdown.
|
||
|
||
This plugin follow how GitHub handles frontmatter.
|
||
GitHub only supports YAML frontmatter, but this plugin also supports different
|
||
flavors (such as TOML).
|
||
|
||
## When should I use this?
|
||
|
||
You can use frontmatter when you want authors, that have some markup
|
||
experience, to configure where or how the content is displayed or supply
|
||
metadata about content, and know that the markdown is only used in places
|
||
that support frontmatter.
|
||
A good example use case is markdown being rendered by (static) site generators.
|
||
|
||
If you *just* want to turn markdown into HTML (with maybe a few extensions such
|
||
as frontmatter), we recommend [`micromark`][micromark] with
|
||
[`micromark-extension-frontmatter`][micromark-extension-frontmatter] instead.
|
||
If you don’t use plugins and want to access the syntax tree, you can use
|
||
[`mdast-util-from-markdown`][mdast-util-from-markdown] with
|
||
[`mdast-util-frontmatter`][mdast-util-frontmatter].
|
||
|
||
## Install
|
||
|
||
This package is [ESM only][esm].
|
||
In Node.js (version 16+), install with [npm][]:
|
||
|
||
```sh
|
||
npm install remark-frontmatter
|
||
```
|
||
|
||
In Deno with [`esm.sh`][esmsh]:
|
||
|
||
```js
|
||
import remarkFrontmatter from 'https://esm.sh/remark-frontmatter@5'
|
||
```
|
||
|
||
In browsers with [`esm.sh`][esmsh]:
|
||
|
||
```html
|
||
<script type="module">
|
||
import remarkFrontmatter from 'https://esm.sh/remark-frontmatter@5?bundle'
|
||
</script>
|
||
```
|
||
|
||
## Use
|
||
|
||
Say our document `example.md` contains:
|
||
|
||
```markdown
|
||
+++
|
||
layout = "solar-system"
|
||
+++
|
||
|
||
# Jupiter
|
||
```
|
||
|
||
…and our module `example.js` contains:
|
||
|
||
```js
|
||
import remarkFrontmatter from 'remark-frontmatter'
|
||
import remarkParse from 'remark-parse'
|
||
import remarkStringify from 'remark-stringify'
|
||
import {unified} from 'unified'
|
||
import {read} from 'to-vfile'
|
||
|
||
const file = await unified()
|
||
.use(remarkParse)
|
||
.use(remarkStringify)
|
||
.use(remarkFrontmatter, ['yaml', 'toml'])
|
||
.use(function () {
|
||
return function (tree) {
|
||
console.dir(tree)
|
||
}
|
||
})
|
||
.process(await read('example.md'))
|
||
|
||
console.log(String(file))
|
||
```
|
||
|
||
…then running `node example.js` yields:
|
||
|
||
```js
|
||
{
|
||
type: 'root',
|
||
children: [
|
||
{type: 'toml', value: 'layout = "solar-system"', position: [Object]},
|
||
{type: 'heading', depth: 1, children: [Array], position: [Object]}
|
||
],
|
||
position: {
|
||
start: {line: 1, column: 1, offset: 0},
|
||
end: {line: 6, column: 1, offset: 43}
|
||
}
|
||
}
|
||
```
|
||
|
||
```markdown
|
||
+++
|
||
layout = "solar-system"
|
||
+++
|
||
|
||
# Jupiter
|
||
```
|
||
|
||
## API
|
||
|
||
This package exports no identifiers.
|
||
The default export is [`remarkFrontmatter`][api-remark-frontmatter].
|
||
|
||
### `unified().use(remarkFrontmatter[, options])`
|
||
|
||
Add support for frontmatter.
|
||
|
||
###### Parameters
|
||
|
||
* `options` ([`Options`][api-options], default: `'yaml'`)
|
||
— configuration
|
||
|
||
###### Returns
|
||
|
||
Nothing (`undefined`).
|
||
|
||
###### Notes
|
||
|
||
Doesn’t parse the data inside them:
|
||
[create your own plugin][unified-create-plugin] to do that.
|
||
|
||
### `Options`
|
||
|
||
Configuration (TypeScript type).
|
||
|
||
###### Type
|
||
|
||
```ts
|
||
type Options = Array<Matter | Preset> | Matter | Preset
|
||
|
||
/**
|
||
* Sequence.
|
||
*
|
||
* Depending on how this structure is used, it reflects a marker or a fence.
|
||
*/
|
||
export type Info = {
|
||
/**
|
||
* Closing.
|
||
*/
|
||
close: string
|
||
/**
|
||
* Opening.
|
||
*/
|
||
open: string
|
||
}
|
||
|
||
/**
|
||
* Fence configuration.
|
||
*/
|
||
type FenceProps = {
|
||
/**
|
||
* Complete fences.
|
||
*
|
||
* This can be used when fences contain different characters or lengths
|
||
* other than 3.
|
||
* Pass `open` and `close` to interface to specify different characters for opening and
|
||
* closing fences.
|
||
*/
|
||
fence: Info | string
|
||
/**
|
||
* If `fence` is set, `marker` must not be set.
|
||
*/
|
||
marker?: never
|
||
}
|
||
|
||
/**
|
||
* Marker configuration.
|
||
*/
|
||
type MarkerProps = {
|
||
/**
|
||
* Character repeated 3 times, used as complete fences.
|
||
*
|
||
* For example the character `'-'` will result in `'---'` being used as the
|
||
* fence
|
||
* Pass `open` and `close` to specify different characters for opening and
|
||
* closing fences.
|
||
*/
|
||
marker: Info | string
|
||
/**
|
||
* If `marker` is set, `fence` must not be set.
|
||
*/
|
||
fence?: never
|
||
}
|
||
|
||
/**
|
||
* Fields describing a kind of matter.
|
||
*
|
||
* > 👉 **Note**: using `anywhere` is a terrible idea.
|
||
* > It’s called frontmatter, not matter-in-the-middle or so.
|
||
* > This makes your markdown less portable.
|
||
*
|
||
* > 👉 **Note**: `marker` and `fence` are mutually exclusive.
|
||
* > If `marker` is set, `fence` must not be set, and vice versa.
|
||
*/
|
||
type Matter = (MatterProps & FenceProps) | (MatterProps & MarkerProps)
|
||
|
||
/**
|
||
* Fields describing a kind of matter.
|
||
*/
|
||
type MatterProps = {
|
||
/**
|
||
* Node type to tokenize as.
|
||
*/
|
||
type: string
|
||
/**
|
||
* Whether matter can be found anywhere in the document, normally, only matter
|
||
* at the start of the document is recognized.
|
||
*
|
||
* > 👉 **Note**: using this is a terrible idea.
|
||
* > It’s called frontmatter, not matter-in-the-middle or so.
|
||
* > This makes your markdown less portable.
|
||
*/
|
||
anywhere?: boolean | null | undefined
|
||
}
|
||
|
||
/**
|
||
* Known name of a frontmatter style.
|
||
*/
|
||
type Preset = 'toml' | 'yaml'
|
||
```
|
||
|
||
## Examples
|
||
|
||
### Example: different markers and fences
|
||
|
||
Here are a couple of example of different matter objects and what frontmatter
|
||
they match.
|
||
|
||
To match frontmatter with the same opening and closing fence, namely three of
|
||
the same markers, use for example `{type: 'yaml', marker: '-'}`, which matches:
|
||
|
||
```yaml
|
||
---
|
||
key: value
|
||
---
|
||
```
|
||
|
||
To match frontmatter with different opening and closing fences, which each use
|
||
three different markers, use for example
|
||
`{type: 'custom', marker: {open: '<', close: '>'}}`, which matches:
|
||
|
||
```text
|
||
<<<
|
||
data
|
||
>>>
|
||
```
|
||
|
||
To match frontmatter with the same opening and closing fences, which both use
|
||
the same custom string, use for example `{type: 'custom', fence: '+=+=+=+'}`,
|
||
which matches:
|
||
|
||
```text
|
||
+=+=+=+
|
||
data
|
||
+=+=+=+
|
||
```
|
||
|
||
To match frontmatter with different opening and closing fences, which each use
|
||
different custom strings, use for example
|
||
`{type: 'json', fence: {open: '{', close: '}'}}`, which matches:
|
||
|
||
```json
|
||
{
|
||
"key": "value"
|
||
}
|
||
```
|
||
|
||
### Example: frontmatter as metadata
|
||
|
||
This plugin handles the syntax of frontmatter in markdown.
|
||
It does not *parse* that frontmatter as say YAML or TOML and expose it
|
||
somewhere.
|
||
|
||
In unified, there is a place for metadata about files:
|
||
[`file.data`][vfile-file-data].
|
||
For frontmatter specifically, it’s customary to expose parsed data at `file.data.matter`.
|
||
|
||
We can make a plugin that does this.
|
||
This example uses the utility [`vfile-matter`][vfile-matter], which is specific
|
||
to YAML.
|
||
To support other data languages, look at this utility for inspiration.
|
||
|
||
`my-unified-plugin-handling-yaml-matter.js`:
|
||
|
||
```js
|
||
/**
|
||
* @typedef {import('unist').Node} Node
|
||
* @typedef {import('vfile').VFile} VFile
|
||
*/
|
||
|
||
import {matter} from 'vfile-matter'
|
||
|
||
/**
|
||
* Parse YAML frontmatter and expose it at `file.data.matter`.
|
||
*
|
||
* @returns
|
||
* Transform.
|
||
*/
|
||
export default function myUnifiedPluginHandlingYamlMatter() {
|
||
/**
|
||
* Transform.
|
||
*
|
||
* @param {Node} tree
|
||
* Tree.
|
||
* @param {VFile} file
|
||
* File.
|
||
* @returns {undefined}
|
||
* Nothing.
|
||
*/
|
||
return function (tree, file) {
|
||
matter(file)
|
||
}
|
||
}
|
||
```
|
||
|
||
…with an example markdown file `example.md`:
|
||
|
||
```markdown
|
||
---
|
||
key: value
|
||
---
|
||
|
||
# Venus
|
||
```
|
||
|
||
…and using the plugin with an `example.js` containing:
|
||
|
||
```js
|
||
import remarkParse from 'remark-parse'
|
||
import remarkFrontmatter from 'remark-frontmatter'
|
||
import remarkStringify from 'remark-stringify'
|
||
import {read} from 'to-vfile'
|
||
import {unified} from 'unified'
|
||
import myUnifiedPluginHandlingYamlMatter from './my-unified-plugin-handling-yaml-matter.js'
|
||
|
||
const file = await unified()
|
||
.use(remarkParse)
|
||
.use(remarkStringify)
|
||
.use(remarkFrontmatter)
|
||
.use(myUnifiedPluginHandlingYamlMatter)
|
||
.process(await read('example.md'))
|
||
|
||
console.log(file.data.matter) // => {key: 'value'}
|
||
```
|
||
|
||
### Example: frontmatter in MDX
|
||
|
||
MDX has the ability to export data from it, where markdown does not.
|
||
When authoring MDX, you can write `export` statements and expose arbitrary data
|
||
through them.
|
||
It is also possible to write frontmatter, and let a plugin turn those into
|
||
export statements.
|
||
|
||
To automatically turn frontmatter into export statements, use
|
||
[`remark-mdx-frontmatter`][remark-mdx-frontmatter].
|
||
|
||
With an `example.mdx` as follows:
|
||
|
||
```mdx
|
||
---
|
||
key: value
|
||
---
|
||
|
||
# Mars
|
||
```
|
||
|
||
This plugin can be used as follows:
|
||
|
||
```js
|
||
import {compile} from '@mdx-js/mdx'
|
||
import remarkFrontmatter from 'remark-frontmatter'
|
||
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
|
||
import {read, write} from 'to-vfile'
|
||
|
||
const file = await compile(await read('example.mdx'), {
|
||
remarkPlugins: [remarkFrontmatter, [remarkMdxFrontmatter, {name: 'matter'}]]
|
||
})
|
||
file.path = 'output.js'
|
||
await write(file)
|
||
|
||
const mod = await import('./output.js')
|
||
console.log(mod.matter) // => {key: 'value'}
|
||
```
|
||
|
||
## Authoring
|
||
|
||
When authoring markdown with frontmatter, it’s recommended to use YAML
|
||
frontmatter if possible.
|
||
While YAML has some warts, it works in the most places, so using it guarantees
|
||
the highest chance of portability.
|
||
|
||
In certain ecosystems, other flavors are widely used.
|
||
For example, in the Rust ecosystem, TOML is often used.
|
||
In such cases, using TOML is an okay choice.
|
||
|
||
When possible, do not use other types of frontmatter, and do not allow
|
||
frontmatter anywhere.
|
||
|
||
## HTML
|
||
|
||
Frontmatter does not relate to HTML elements.
|
||
It is typically stripped, which is what [`remark-rehype`][remark-rehype] does.
|
||
|
||
## CSS
|
||
|
||
This package does not relate to CSS.
|
||
|
||
## Syntax
|
||
|
||
See [*Syntax* in
|
||
`micromark-extension-frontmatter`](https://github.com/micromark/micromark-extension-frontmatter#syntax).
|
||
|
||
## Syntax tree
|
||
|
||
See [*Syntax tree* in
|
||
`mdast-util-frontmatter`](https://github.com/syntax-tree/mdast-util-frontmatter#syntax-tree).
|
||
|
||
## Types
|
||
|
||
This package is fully typed with [TypeScript][].
|
||
It exports the additional type [`Options`][api-options].
|
||
|
||
The YAML node type is supported in `@types/mdast` by default.
|
||
To add other node types, register them by adding them to
|
||
`FrontmatterContentMap`:
|
||
|
||
```ts
|
||
import type {Data, Literal} from 'mdast'
|
||
|
||
interface Toml extends Literal {
|
||
type: 'toml'
|
||
data?: TomlData
|
||
}
|
||
|
||
declare module 'mdast' {
|
||
interface FrontmatterContentMap {
|
||
// Allow using TOML nodes defined by `remark-frontmatter`.
|
||
toml: Toml
|
||
}
|
||
}
|
||
```
|
||
|
||
## Compatibility
|
||
|
||
Projects maintained by the unified collective are compatible with maintained
|
||
versions of Node.js.
|
||
|
||
When we cut a new major release, we drop support for unmaintained versions of
|
||
Node.
|
||
This means we try to keep the current release line, `remark-frontmatter@^5`,
|
||
compatible with Node.js 16.
|
||
|
||
This plugin works with unified 6+ and remark 13+.
|
||
|
||
## Security
|
||
|
||
Use of `remark-frontmatter` does not involve **[rehype][]** ([hast][]) or user
|
||
content so there are no openings for [cross-site scripting (XSS)][wiki-xss]
|
||
attacks.
|
||
|
||
## Related
|
||
|
||
* [`remark-yaml-config`](https://github.com/remarkjs/remark-yaml-config)
|
||
— configure remark from YAML configuration
|
||
* [`remark-gfm`](https://github.com/remarkjs/remark-gfm)
|
||
— support GFM (autolink literals, footnotes, strikethrough, tables,
|
||
tasklists)
|
||
* [`remark-mdx`](https://github.com/mdx-js/mdx/tree/main/packages/remark-mdx)
|
||
— support MDX (ESM, JSX, expressions)
|
||
* [`remark-directive`](https://github.com/remarkjs/remark-directive)
|
||
— support directives
|
||
* [`remark-math`](https://github.com/remarkjs/remark-math)
|
||
— support math
|
||
|
||
## Contribute
|
||
|
||
See [`contributing.md`][contributing] in [`remarkjs/.github`][health] for ways
|
||
to get started.
|
||
See [`support.md`][support] for ways to get help.
|
||
|
||
This project has a [code of conduct][coc].
|
||
By interacting with this repository, organization, or community you agree to
|
||
abide by its terms.
|
||
|
||
## License
|
||
|
||
[MIT][license] © [Titus Wormer][author]
|
||
|
||
<!-- Definitions -->
|
||
|
||
[build-badge]: https://github.com/remarkjs/remark-frontmatter/workflows/main/badge.svg
|
||
|
||
[build]: https://github.com/remarkjs/remark-frontmatter/actions
|
||
|
||
[coverage-badge]: https://img.shields.io/codecov/c/github/remarkjs/remark-frontmatter.svg
|
||
|
||
[coverage]: https://codecov.io/github/remarkjs/remark-frontmatter
|
||
|
||
[downloads-badge]: https://img.shields.io/npm/dm/remark-frontmatter.svg
|
||
|
||
[downloads]: https://www.npmjs.com/package/remark-frontmatter
|
||
|
||
[size-badge]: https://img.shields.io/bundlejs/size/remark-frontmatter
|
||
|
||
[size]: https://bundlejs.com/?q=remark-frontmatter
|
||
|
||
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
|
||
|
||
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
|
||
|
||
[collective]: https://opencollective.com/unified
|
||
|
||
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
|
||
|
||
[chat]: https://github.com/remarkjs/remark/discussions
|
||
|
||
[npm]: https://docs.npmjs.com/cli/install
|
||
|
||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
|
||
|
||
[esmsh]: https://esm.sh
|
||
|
||
[health]: https://github.com/remarkjs/.github
|
||
|
||
[contributing]: https://github.com/remarkjs/.github/blob/main/contributing.md
|
||
|
||
[support]: https://github.com/remarkjs/.github/blob/main/support.md
|
||
|
||
[coc]: https://github.com/remarkjs/.github/blob/main/code-of-conduct.md
|
||
|
||
[license]: license
|
||
|
||
[author]: https://wooorm.com
|
||
|
||
[mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown
|
||
|
||
[mdast-util-frontmatter]: https://github.com/syntax-tree/mdast-util-frontmatter
|
||
|
||
[micromark]: https://github.com/micromark/micromark
|
||
|
||
[micromark-extension-frontmatter]: https://github.com/micromark/micromark-extension-frontmatter
|
||
|
||
[hast]: https://github.com/syntax-tree/hast
|
||
|
||
[rehype]: https://github.com/rehypejs/rehype
|
||
|
||
[remark]: https://github.com/remarkjs/remark
|
||
|
||
[remark-rehype]: https://github.com/remarkjs/remark-rehype
|
||
|
||
[remark-mdx-frontmatter]: https://github.com/remcohaszing/remark-mdx-frontmatter
|
||
|
||
[typescript]: https://www.typescriptlang.org
|
||
|
||
[unified]: https://github.com/unifiedjs/unified
|
||
|
||
[unified-create-plugin]: https://unifiedjs.com/learn/guide/create-a-plugin/
|
||
|
||
[vfile-file-data]: https://github.com/vfile/vfile#filedata
|
||
|
||
[vfile-matter]: https://github.com/vfile/vfile-matter
|
||
|
||
[wiki-xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
|
||
|
||
[api-options]: #options
|
||
|
||
[api-remark-frontmatter]: #unifieduseremarkfrontmatter-options
|