Custom

Markdown-it

Use existing markdown-it plugins with Comark and create new parser syntax using markdown-it rules.

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

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:

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

Plugins that rely on markdown-it's renderer will not work as expected. Comark uses its own rendering pipeline instead of 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>

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