# Comarkdown Comark Comark

> Migrate from @nuxtjs/mdc to Comark

- Skill: `tomevault-io/comarkdown-comark-comark` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/comarkdown-comark-comark`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/comarkdown-comark-comark/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/comarkdown-comark-comark

---


# Migrate from @nuxtjs/mdc to Comark

`@nuxtjs/mdc` was Nuxt-only. Comark is its successor — the markdown syntax is fully compatible, your `.md` files need no changes. What changes is the JavaScript API.

The migration has two parts: **Core Package** (programmatic API) and **Nuxt Module** (components, slots, config).

## Quick Overview

- **Package**: `@nuxtjs/mdc` → `comark` (core only) or `@comark/nuxt` (Nuxt module)
- **Parse**: `parseMarkdown()` → `parse()` · factory: `createParse()` (sync, no await)
- **Render**: `stringifyMarkdown()` → `renderMarkdown()` from `comark/render`
- **AST**: object tree → compact tuples `['tag', props, ...children]`
- **Result**: `result.body` / `result.data` → `tree.nodes` / `tree.frontmatter`
- **Renderer**: `<MDCRenderer :body :data>` → `<ComarkRenderer :tree>`
- **All-in-one**: `<MDC :value>` → `<Comark :markdown>`
- **Slots**: `<MDCSlot />` → native `<slot />`
- **Plugins**: global `nuxt.config` → per-component `defineComarkComponent({ plugins })`
- **Markdown files**: no changes needed

## Core Package

### API Mapping

| `@nuxtjs/mdc` | `comark` |
|---|---|
| `parseMarkdown(md, opts)` | `parse(md, opts)` from `comark` |
| `createMarkdownParser(opts)` (async) | `createParse(opts)` (sync — no await) |
| `stringifyMarkdown(body, data)` | `renderMarkdown(tree)` from `comark/render` |
| `result.body` (`MDCRoot`) | `tree.nodes` (`ComarkNode[]`) |
| `result.data` | `tree.frontmatter` |
| `result.data.title` | `tree.frontmatter.title` |
| `result.toc` | `tree.meta.toc` (requires `toc` plugin) |
| `result.excerpt` | `tree.meta.summary` (requires `summary` plugin) |

### AST Format

| `@nuxtjs/mdc` | `comark` |
|---|---|
| `{ type: 'root', children: MDCNode[] }` | `{ nodes: ComarkNode[], frontmatter: {}, meta: {} }` |
| `{ type: 'element', tag: 'p', props: {}, children: [] }` | `['p', {}, ...children]` |
| `{ type: 'text', value: 'hello' }` | `'hello'` (plain string) |

### Parse Options

```typescript
// Before — MDCParseOptions
{
  remark: { plugins: { /* record */ } },
  rehype: { options: {...}, plugins: { /* record */ } },
  highlight: { theme: '...', langs: [...] } | false,
  toc: { depth: 3, searchDepth: 2 } | false,
}

// After — ParseOptions
{
  plugins: ComarkPlugin[],   // ordered array, not a record
  autoUnwrap: true,          // removes <p> from single-paragraph containers
  autoClose: true,           // completes incomplete syntax (useful for streaming)
  html: true,                // parse embedded HTML tags
}
```

### Plugins

The `unified`/`remark`/`rehype` pipeline is replaced by Comark's own lighter plugin interface.

| Feature | Before | After |
|---|---|---|
| Syntax highlighting | `rehypeHighlight` via `createMarkdownParser` | `highlight()` from `comark/plugins/highlight` |
| Table of Contents | `parseMarkdown(md, { toc: { depth: 3 } })` | `toc({ depth: 3 })` plugin |
| Excerpt / Summary | `result.excerpt` (built-in) | `summary()` plugin → `tree.meta.summary` |
| Emoji | `remark-emoji` (enabled by default) | `emoji()` plugin (opt-in) |

Available plugins: `comark/plugins/toc`, `comark/plugins/highlight`, `comark/plugins/emoji`, `comark/plugins/task-list`, `comark/plugins/summary`, `comark/plugins/security`, `comark/plugins/alert`, `comark/plugins/math`, `comark/plugins/mermaid`, `comark/plugins/punctuation`

## Nuxt Module

### Configuration

```typescript
// Before
export default defineNuxtConfig({
  modules: ['@nuxtjs/mdc'],
  mdc: { highlight: { ... }, remarkPlugins: { ... } },
})

// After
export default defineNuxtConfig({
  modules: ['@comark/nuxt'],
  // No plugin config here — plugins are defined per-component
})
```

`@comark/nuxt` auto-imports: `Comark`, `ComarkRenderer`, `defineComarkComponent`, `defineComarkRendererComponent`.

### Components

| `@nuxtjs/mdc` | `@comark/nuxt` |
|---|---|
| `<MDCRenderer :body :data :components>` | `<ComarkRenderer :tree :components>` |
| `<MDC :value :parser-options>` | `<Comark :markdown :options>` or `<Comark>{{ md }}</Comark>` |
| `<MDCSlot />` | `<slot />` |
| `<MDCSlot unwrap="p" />` | `<slot unwrap="p" />` |
| `<slot mdc-unwrap="p" />` | `<slot unwrap="p" />` |

For a pre-parsed tree, use `<ComarkRenderer>` directly instead of `<Comark>`.

#### `<ComarkRenderer>` props changes

| MDC `<MDCRenderer>` | Comark `<ComarkRenderer>` | Notes |
|---|---|---|
| `body` (`MDCRoot`) | `tree` (`ComarkTree`) | Different AST shape |
| `data` | — | Frontmatter is in `tree.frontmatter` |
| `tag` | — | Wrapper is always `<div class="comark-content">` |
| `prose` | — | `Prose*` resolution is automatic |
| `unwrap` | — | Use `autoUnwrap` in parse options |
| `components` | `components` | Same purpose |
| — | `componentsManifest` | New: dynamic async component resolver |
| — | `streaming` | New: streaming mode |
| — | `caret` | New: animated caret for streaming |

#### Summary rendering

```vue
<!-- Before -->
<MDCRenderer :body="result.excerpt ?? result.body" :data="result.data" />

<!-- After -->
<Comark summary>{{ markdown }}</Comark>
```

### `defineComarkComponent`

Replaces global `mdc: { ... }` config. Define reusable components with their own plugins and component mappings:

```typescript
import { defineComarkComponent } from '@comark/vue'
import highlight from 'comark/plugins/highlight'
import toc from 'comark/plugins/toc'

export const ArticleComark = defineComarkComponent({
  name: 'ArticleComark',
  plugins: [highlight({ themes: { light: githubLight, dark: githubDark } }), toc()],
  components: { alert: CustomAlert },
})
```

### Slots

`<MDCSlot />` → native `<slot />`. Named slots work the same (`#slotName` in markdown, `<slot name="slotName">` in component). The `unwrap` attribute (`<slot unwrap="p">`) strips wrapper tags from children.

### Prose Components

Same `Prose*.vue` naming convention in `components/prose/`. Resolution changed from kebab-case (`prose-p`) to PascalCase (`ProseP`) internally — no file changes needed.

### Nuxt UI Integration

When using Nuxt UI, `@comark/nuxt` registers Nuxt UI prose components automatically. Shorthand callout components are available:

```mdc
::note    — informational
::tip     — helpful suggestion
::warning — something to watch out for
::caution — critical warning
```

These are **only available with Nuxt UI**. Without it, use `::callout{icon="..." color="..."}`.

## Common Pitfalls

1. **`createParse` is sync** — no `await` needed (unlike `createMarkdownParser`)
2. **Attribute naming**: Comark uses `attrs.lang`, not `attrs.language`
3. **Frontmatter**: stored in `tree.frontmatter`, not passed separately
4. **`renderMarkdown` includes frontmatter** — reads `tree.frontmatter` automatically
5. **No `unified` pipeline** — `mdc.config.ts` hooks (`pre`, `remark`, `rehype`, `post`) have no equivalent, use `ComarkPlugin` interface instead
6. **Emoji is opt-in** — not enabled by default like in MDC's `remark-emoji`

## Component Syntax

The MDC block and inline component syntax is identical — no changes needed in `.md` files.

## Unsupported Features

- **Binding syntax** (`{{ variable }}`) — not supported, rendered as plain text
- **Props binding / data passing** — no equivalent for `parseMarkdown(md, { data: { ... } })`

---
> Source: [comarkdown/comark](https://github.com/comarkdown/comark) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-29 -->

