All skills
onmax avatar

/comark

@de09c7b official
by Maxonmax/nuxt-skills715 stars
38

Comark (Components in Markdown) parser: syntax, AST, Vue/React/Svelte/Angular renderers, plugins, and LLM streaming with auto-close.

Use this Skill: https://skilld.dev/gh/onmax/nuxt-skills/comark

This session only. Nothing lands on disk.

referencesparsing-ast.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Parsing & Document Model

Complete guide for parsing Comark content and working with the serializable MarkdownDocument model.

Table of Contents


String Parsing

The primary way to parse Comark content is using the parseMarkdown() function:

import { parseMarkdown } from 'comark'

const content = `---
title: My Document
---

# Hello World

This is **markdown** with :icon-star component.

::alert{type="info"}
Important message here
::
`

const result = await parseMarkdown(content)

Result Structure

interface MarkdownDocument {
  nodes: Node[]                   // Parsed AST nodes
  frontmatter: Record<string, any>      // YAML frontmatter data
  meta: {
    toc?: any                           // Table of contents (from toc plugin)
    summary?: Node[]              // Summary content (from summary plugin)
    [key: string]: any                  // Other plugin metadata
  }
}

Parse Options

interface ParserOptions {
  autoUnwrap?: boolean      // Remove unnecessary <p> wrappers (default: true)
  autoClose?: boolean       // Auto-close unclosed syntax (default: true)
  plugins?: ComarkPlugin[]  // Enable plugins (e.g., highlight, emoji, toc)
}

Examples

// Default parsing
const result = await parseMarkdown(content)

// Disable auto-unwrap
const result = await parseMarkdown(content, { autoUnwrap: false })

// Disable auto-close
const result = await parseMarkdown(content, { autoClose: false })

// Both disabled
const result = await parseMarkdown(content, {
  autoUnwrap: false,
  autoClose: false
})

Auto-Unwrap Feature

Auto-unwrap removes unnecessary paragraph wrappers from container components:

Without auto-unwrap:

["alert", {}, ["p", {}, "Text"]]

With auto-unwrap:

["alert", {}, "Text"]

Auto-unwrap applies to any component whose only child is a single paragraph.

Auto-Close Feature

Auto-close automatically closes unclosed markdown syntax, essential for streaming:

import { autoCloseMarkdown } from 'comark'

// Unclosed bold
const partial = '**bold text'
const closed = autoCloseMarkdown(partial)
// Result: '**bold text**'

// Unclosed component
const component = '::alert{type="info"}\nMessage'
const closedComponent = autoCloseMarkdown(component)
// Result: '::alert{type="info"}\nMessage\n::'

// Unclosed properties
const props = 'Text {prop="value'
const closedProps = autoCloseMarkdown(props)
// Result: 'Text {prop="value"}'

Auto-close handles:

  • Inline markers: *, **, ***, ~~, backticks
  • Brackets: [, ], (, )
  • Comark components: ::component
  • Property braces: {...}

Async Parsing with Syntax Highlighting

For syntax highlighting support, use the highlight plugin:

import { parseMarkdown } from 'comark'
import shiki from 'comark/plugins/shiki'

const content = `
# Code Example

\`\`\`javascript
function hello() {
  console.log("Hello!")
}
\`\`\`
`

// Enable syntax highlighting
const result = await parseMarkdown(content, {
  plugins: [shiki()]
})

// With custom Shiki options
const result = await parseMarkdown(content, {
  plugins: [
    shiki({
      themes: {
        light: 'github-light',
        dark: 'github-dark'
      },
      languages: ['javascript', 'typescript', 'python']
    })
  ]
})

Highlight Plugin Options

// Standard entry: comark/plugins/shiki
interface ShikiOptions {
  registerDefaultLanguages?: boolean  // default: true
  registerDefaultThemes?: boolean     // default: true
  themes?: {
    light?: ThemeRegistration         // default: material-theme-lighter
    dark?: ThemeRegistration          // default: material-theme-palenight
  }
  languages?: Array<LanguageRegistration | LanguageRegistration[]>  // merged onto default set
  transformers?: ShikiTransformer[]
  preStyles?: boolean
}

// Core entry: comark/plugins/shiki/core — no defaults / no registerDefault*
interface ShikiCoreOptions {
  themes: {                           // required
    light?: ThemeRegistration
    dark?: ThemeRegistration
  }
  languages: Array<LanguageRegistration | LanguageRegistration[]>  // required
  transformers?: ShikiTransformer[]
  preStyles?: boolean
}

Dual Theme Support

import shiki from 'comark/plugins/shiki'

const result = await parseMarkdown(content, {
  plugins: [
    shiki({
      themes: {
        light: 'github-light',
        dark: 'github-dark'
      }
    })
  ]
})

Document Structure

Comark returns a serializable MarkdownDocument with compact array-based nodes.

MarkdownDocument Format

interface MarkdownDocument {
  nodes: Node[]                   // Parsed AST nodes
  frontmatter: Record<string, any>      // YAML frontmatter data
  meta: {
    toc?: any                           // Table of contents (from toc plugin)
    summary?: Node[]              // Summary content (from summary plugin)
    [key: string]: any                  // Other plugin metadata
  }
}

type Node =
  | string                    // Text nodes
  | [tag: string, props?: Record<string, any>, ...children: Node[]]

Node Structure

Text Node:

"plain text content"

Element Node:

["tag", { "prop": "value" }, ...children]

Examples

// Paragraph with text
["p", {}, "Simple paragraph"]

// Paragraph with bold text
["p", {}, "Text with ", ["strong", {}, "bold"], " word"]

// Heading with ID
["h1", { "id": "hello-world" }, "Hello World"]

// Link with attributes
["a", { "href": "https://example.com", "target": "_blank" }, "Link"]

// Comark Component
["alert", { "type": "info" }, ["p", {}, "Message"]]

// Component with slots
[
  "card",
  {},
  ["template", { "name": "header" }, ["h2", {}, "Title"]],
  ["template", { "name": "content" }, ["p", {}, "Content"]]
]

// Default slot: content without #slot-name becomes direct children
["component", {}, "hello"]

// Explicit #default wraps in a template node (equivalent in rendering)
["component", {}, ["template", { "name": "default" }, "hello"]]

Complete Document Example

Input:

---
title: Example
---

# Hello World

This is **bold** and *italic* text.

::alert{type="warning"}
Warning message
::

AST:

{
  "nodes": [
    [
      "h1",
      { "id": "hello-world" },
      "Hello World"
    ],
    [
      "p",
      {},
      "This is ",
      ["strong", {}, "bold"],
      " and ",
      ["em", {}, "italic"],
      " text."
    ],
    [
      "alert",
      { "type": "warning" },
      ["p", {}, "Warning message"]
    ]
  ],
  "frontmatter": {
    "title": "Example"
  },
  "meta": {}
}

Property Conventions

  • Boolean props: :bool="true" (props starting with :)
  • Standard props: key="value"
  • ID: id="value" (from {#value})
  • Class: class="value" (from {.value})
  • Custom data: Any attribute name and value

Rendering Documents

Render to HTML

import { parseMarkdown } from 'comark'
import { renderHtmlFromDocument } from '@comark/html'

const content = '# Hello World\n\nThis is **markdown**.'
const doc = await parseMarkdown(content)

const html = await renderHtmlFromDocument(doc)
console.log(html)

Output:

<h1 id="hello-world">Hello World</h1>
<p>This is <strong>markdown</strong>.</p>

Render to Markdown

Convert AST back to Comark markdown:

import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'

const content = '# Hello\n\n::alert{type="info"}\nMessage\n::'
const document = await parseMarkdown(content)

const markdown = await renderMarkdown(document)
console.log(markdown)

Output:

# Hello

::alert{type="info"}
Message
::

Use Cases

  • Round-trip parsing (parse → modify AST → render back)
  • AST transformation
  • Content normalization
  • Markdown generation from programmatic AST

← Back to Main Skills Guide

Source: SKILL.md on GitHub

No alerts9d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    Comark is a markdown parsing and rendering library that enables custom components in markdown across various web frameworks. The skill is rated as low risk because its primary function involves processing untrusted content (e.g., from AI agents), creating a surface for indirect prompt injection. It also utilizes dynamic component loading, which is a core feature but represents a dynamic execution pattern.

  • Socket9d

    No alerts

  • Snyk9d

    Risk: LOW · No issues

Signed by skilld at de09c7b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last week

README badge

README badge for onmax/nuxt-skills/comark