Skill v1.0.1
Automated scan96/100+3 new
version: "1.0.1" name: migrate-mdc-to-comark description: >- Migrate a Nuxt project from @nuxtjs/mdc to Comark. Covers package changes, parse/render API mapping, AST format, Nuxt module config, components, slots, plugins, and Nuxt UI integration.
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()fromcomark/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-componentdefineComarkComponent({ 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
// 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 recordautoUnwrap: true, // removes <p> from single-paragraph containersautoClose: 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
// Beforeexport default defineNuxtConfig({modules: ['@nuxtjs/mdc'],mdc: { highlight: { ... }, remarkPlugins: { ... } },})// Afterexport 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
<!-- 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:
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:
::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
- `createParse` is sync: no
awaitneeded (unlikecreateMarkdownParser) - Attribute naming: Comark uses
attrs.lang, notattrs.language - Frontmatter: stored in
tree.frontmatter, not passed separately - `renderMarkdown` includes frontmatter: reads
tree.frontmatterautomatically - No `unified` pipeline:
mdc.config.tshooks (pre,remark,rehype,post) have no equivalent, useComarkPlugininterface instead - 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, so no changes are 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: { ... } })