Markdown-it
Comark's default parser is built on top of markdown-it via markdown-exit, a maintained fork. Most markdown-it plugins work with Comark with minimal effort.
Usage
Wrap any markdown-it plugin with defineComarkPlugin and pass it in markdownItPlugins:
import { defineComarkPlugin } from 'comark/parse'
import markdownItSub from 'markdown-it-sub'
import markdownItSup from 'markdown-it-sup'
const subscript = defineComarkPlugin(() => ({
name: 'subscript',
markdownItPlugins: [markdownItSub],
}))
const superscript = defineComarkPlugin(() => ({
name: 'superscript',
markdownItPlugins: [markdownItSup],
}))import { parse } from 'comark'
const tree = await parse('H~2~O is water, 2^10^ is 1024', {
plugins: [subscript(), superscript()]
})<script setup lang="ts">
import { Comark } from '@comark/vue'
const plugins = [subscript(), superscript()]
</script>
<template>
<Comark :plugins="plugins">H~2~O is water</Comark>
</template>Create
To add new syntax, write a markdown-it inline or block rule and include it in markdownItPlugins. Comark automatically converts the resulting tokens into AST nodes.
The following example adds ==highlighted text== syntax:
import type { MarkdownItPlugin } from 'comark'
import { defineComarkPlugin } from 'comark/parse'
const highlightRule = (state: any, silent: boolean) => {
const start = state.pos
const max = state.posMax
if (start + 1 >= max) return false
if (state.src.charCodeAt(start) !== 0x3D /* = */) return false
if (state.src.charCodeAt(start + 1) !== 0x3D /* = */) return false
let pos = start + 2
while (pos + 1 < max) {
if (state.src.charCodeAt(pos) === 0x3D && state.src.charCodeAt(pos + 1) === 0x3D) {
if (!silent) {
const token = state.push('mark_open', 'mark', 1)
token.markup = '=='
state.pos = start + 2
state.posMax = pos
state.md.inline.tokenize(state)
state.push('mark_close', 'mark', -1)
state.posMax = max
}
state.pos = pos + 2
return true
}
pos++
}
return false
}
const markdownItHighlight: MarkdownItPlugin = (md) => {
md.inline.ruler.before('emphasis', 'mark', highlightRule)
}
export default defineComarkPlugin(() => ({
name: 'highlight',
markdownItPlugins: [markdownItHighlight],
}))==hello== becomes the following AST node:
["mark", {}, "hello"]Compatibility
When you provide markdownItPlugins, they are registered directly on the underlying markdown-it instance with full access to its API:
- Inline rules —
md.inline.ruler - Block rules —
md.block.ruler - Core rules —
md.core.ruler - Renderer rules —
md.renderer.rules(limited — see below)
What Works
Most plugins that add parsing rules work out of the box:
- Plugins that add new inline syntax (subscript, superscript, mark, insert)
- Plugins that add new block syntax (containers, definition lists, footnotes)
- Plugins that transform tokens via core rules (abbreviations, replacements)
What Doesn't Work
md.renderer, so any logic in md.renderer.rules is ignored.If a plugin only customizes rendering, handle the rendering side in Comark using custom components:
<script setup lang="ts">
import { Comark } from '@comark/vue'
const components = {
mark: (props, { slots }) => h('mark', { class: 'highlight' }, slots.default?.())
}
</script>
<template>
<Comark :components="components">==highlighted text==</Comark>
</template>import { Comark } from '@comark/react'
const components = {
mark: ({ children, ...props }) => <mark className="highlight" {...props}>{children}</mark>
}
<Comark components={components}>{content}</Comark>markdown-exit vs markdown-it
Comark uses markdown-exit as its base parser. The fork maintains full API compatibility with markdown-it, so plugins written for markdown-it work without modification. The MarkdownItPlugin type accepted by Comark is the standard plugin signature:
type MarkdownItPlugin = (md: MarkdownIt) => void