diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml
new file mode 100644
index 000000000..3f888a8ee
--- /dev/null
+++ b/.github/workflows/codspeed.yml
@@ -0,0 +1,43 @@
+name: codspeed
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ # Lets CodSpeed trigger backtest runs to build the initial history.
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ id-token: write # OIDC authentication with CodSpeed, no token secret needed
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ benchmarks:
+ name: Run benchmarks
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+ - uses: actions/setup-node@v7
+ with:
+ package-manager-cache: false
+ node-version: '24.21.0'
+ - run: npm i -g --force corepack && corepack enable
+ # `postinstall` stubs every package `dist/` to its sources, so the
+ # benches measure the checked-out code without a build step.
+ - run: pnpm install
+ - name: Run benchmarks
+ uses: CodSpeedHQ/action@v5
+ with:
+ mode: simulation
+ run: pnpm bench
diff --git a/AGENTS.md b/AGENTS.md
index 3781b1418..7637e4291 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -37,7 +37,7 @@ This is a **monorepo** containing the Comark Markdown parser, document model, pl
│ └── 4.ai/ # AI streaming examples (Nuxt + AI SDK)
├── docs/ # Documentation site (comark-docs layer; llms topic files in docs/server/routes/llms-*.txt.get.ts)
├── playground/ # Nuxt playground (`pnpm dev:playground`)
-├── benchmarks/ # mitata benchmarks for parse/render/plugins
+├── benchmarks/ # CodSpeed Vitest benches (*.bench.ts) + mitata cross-library comparisons (*.ts)
├── scripts/ # Build/sync/release scripts
├── test/ # Root-level tests (bundle-size snapshot)
├── pnpm-workspace.yaml # Workspace configuration + dependency catalog
@@ -896,6 +896,7 @@ pnpm build # Build all packages, then sync plugin re-exports
pnpm stub # Point every package's dist/ at src/ for local dev (runs on postinstall)
pnpm test # Run all package tests
pnpm test:spec # Run the SPEC fixtures only
+pnpm bench # Run the CodSpeed Vitest benches (benchmarks/*.bench.ts; wall-clock locally)
pnpm lint # oxlint + oxfmt --check
pnpm lint:fix # oxlint --fix + oxfmt (write)
pnpm typecheck # tsc --noEmit against the root tsconfig
@@ -923,10 +924,20 @@ Workflows live in `.github/workflows/`:
| Workflow | Purpose |
|----------|---------|
| `ci.yml` | lint → prepack → test → publish preview (pkg.pr.new) → bundle size check |
+| `codspeed.yml` | Runs `pnpm bench` under CodSpeed CPU simulation; reports perf changes on PRs |
| `commit-signature.yml` | Fails PRs containing unsigned commits |
| `bundle-snapshot.yml` | Reports bundle-size snapshot drift and updates it on demand |
| `docs-preview-comment.yml` | Posts a docs preview link on PRs that touch `docs/content/**` |
+### Benchmarks (CodSpeed)
+
+`benchmarks/*.bench.ts` run through `benchmarks/vitest.config.ts` with
+`@codspeed/vitest-plugin` (a no-op outside CodSpeed). Shared inputs live in
+`benchmarks/fixtures.ts`. Renaming a bench or editing a fixture resets its
+history, so add new ones instead. Warm parsers at module load. Streaming
+benches need a dedicated parser and a `{ streaming: false }` reset inside the
+bench body; otherwise the incremental cache returns the previous result.
+
### Bundle size snapshot
`test/bundle.test.ts` asserts an inline snapshot of the published size of every
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bba97a249..71f42737b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -91,6 +91,25 @@ pnpm vitest run bundle
If your change legitimately alters package sizes, update the snapshot with `pnpm vitest run bundle -u` after building, and review the resulting diff. Restore source stubs with `pnpm stub` before returning to example development.
+## Benchmarks
+
+Parse, streaming, render and plugin performance is tracked with [CodSpeed](https://app.codspeed.io/comarkdown/comark). The `codspeed` workflow runs the Vitest benches in `benchmarks/*.bench.ts` on every pull request and comments with any change compared to `main`. CodSpeed measures in CPU simulation, so results do not depend on CI machine noise.
+
+Run the suite locally (wall-clock timings, which are only useful for rough comparisons on your machine):
+
+```sh
+pnpm bench
+pnpm bench parse # only benchmarks/parse.bench.ts
+```
+
+When adding a benchmark:
+
+- Put shared inputs in `benchmarks/fixtures.ts`. Renaming a benchmark or editing a fixture resets its history in CodSpeed, so prefer adding a new one.
+- Create parsers once at module level and call each one before the benches run, so samples measure steady-state work, not setup.
+- Give every streaming bench its own parser and reset it with a non-streaming call inside the bench. Otherwise the incremental cache returns the previous result without parsing.
+
+The `mitata` scripts in `benchmarks/*.ts` compare Comark with markdown-it, markdown-exit and remark. Run them directly, for example `pnpm tsx benchmarks/comark-parse.ts`; they are not part of CodSpeed.
+
## Prepare a pull request
Link the relevant issue and follow the pull request template's **What** and **Why** sections. Describe the user-visible problem, the chosen fix and any compatibility impact. Include the checks you ran and any remaining limitations.
diff --git a/benchmarks/fixtures.ts b/benchmarks/fixtures.ts
new file mode 100644
index 000000000..3e4d5da4a
--- /dev/null
+++ b/benchmarks/fixtures.ts
@@ -0,0 +1,325 @@
+// Shared inputs for the CodSpeed suite (`*.bench.ts`).
+//
+// Everything here is deterministic: CodSpeed compares each benchmark against
+// the same benchmark on the base branch, so changing a fixture resets that
+// benchmark's history. Prefer adding a new fixture over editing an existing one.
+
+/** A short document, close to a chat message or a README intro. */
+export const smallMarkdown = `# Hello World
+
+This is a **markdown** paragraph with *italic* text, \`inline code\` and a
+[link](https://example.com).
+
+- List item 1
+- List item 2
+- List item 3
+`
+
+/** A mid-sized document exercising most of the CommonMark + GFM + Comark surface. */
+export const mediumMarkdown = `---
+title: Benchmark Test
+description: A document covering the common Markdown surface
+tags:
+ - markdown
+ - benchmark
+---
+
+# Hello World
+
+This is a **markdown** document with *italic* text and [links](https://example.com).
+
+## Features
+
+- List item 1
+- List item 2 with \`inline code\`
+- List item 3
+
+### Code Block
+
+\`\`\`javascript
+const hello = 'world'
+console.log(hello)
+\`\`\`
+
+### Tables
+
+| Header 1 | Header 2 | Header 3 |
+|----------|----------|----------|
+| Cell 1 | Cell 2 | Cell 3 |
+| Cell 4 | Cell 5 | Cell 6 |
+
+### Components
+
+::alert{type="info"}
+This is an alert component with **bold** content.
+::
+
+::card{title="My Card"}
+Card content here
+::
+
+A [span]{.text-primary} and an image .
+
+### More Content
+
+1. Numbered list
+2. Another item
+3. Final item
+
+> This is a blockquote with some **bold** text
+
+- [ ] An open task
+- [x] A completed task
+
+~~Strikethrough text~~
+
+
+ Inline HTML block
+
+`
+
+/** A long document, representative of a full documentation page. */
+export const largeMarkdown = `---
+title: Large Benchmark Document
+---
+
+# API Reference
+
+${Array.from(
+ { length: 40 },
+ (_, i) => `
+## Module ${i + 1}
+
+Paragraph ${i + 1} with **bold**, *italic*, \`code\` and a [link](https://example.com/${i}).
+
+\`\`\`typescript
+export function module${i + 1}(input: string): string {
+ const result = input.trim()
+ return result.length > 0 ? result : 'default'
+}
+\`\`\`
+
+| Option | Type | Default |
+|--------|------|---------|
+| \`a\` | \`string\` | \`'${i}'\` |
+| \`b\` | \`number\` | \`${i}\` |
+
+::alert{type="info"}
+Note ${i + 1} about **module ${i + 1}**.
+::
+
+- item ${i}.1
+- item ${i}.2
+- item ${i}.3
+
+> Quote ${i + 1}
+`
+).join('\n')}
+`
+
+/** Nested block and inline components with props — the Comark-specific syntax paths. */
+export const componentHeavyMarkdown = `::parent{id="root"}
+# Nested components
+
+::child{variant="a"}
+Inline :badge[hot]{color="red"} and :icon[star]{name="star"}.
+
+:::nested
+Deep content with **formatting** and a [link](https://example.com).
+:::
+::
+
+::card{title="Card A" :count="3" .featured}
+Body A with \`code\` and *emphasis*.
+
+#footer
+Slot content for the footer.
+::
+
+::card{title="Card B"}
+Body B
+
+- one
+- two
+::
+::
+
+Hello :world[Inline Component Content]{data-component="test"} again, with a
+[styled span]{.text-primary #anchor} and **bold{.loud}** text.
+
+::tabs
+ :::tabs-item{label="npm"}
+ \`\`\`bash
+ npm install comark
+ \`\`\`
+ :::
+
+ :::tabs-item{label="pnpm"}
+ \`\`\`bash
+ pnpm add comark
+ \`\`\`
+ :::
+::
+`
+
+/**
+ * Pathological input: long delimiter runs, deep bracket and component nesting.
+ * The parser and auto-close aim for linear time, so a super-linear regression
+ * shows up here first.
+ */
+export const adversarialMarkdown = `${'['.repeat(50)}text${']'.repeat(50)}
+
+${'*'.repeat(40)}borderline emphasis${'*'.repeat(40)}
+
+${'_a '.repeat(200)}
+
+${'`'.repeat(30)}unbalanced code
+
+\`\`\`js
+${'// comment\n'.repeat(200)}\`\`\`
+
+| ${'a | '.repeat(30)}
+| ${'--- | '.repeat(30)}
+| ${'b | '.repeat(30)}
+
+${'> '.repeat(30)}deep quote
+
+${'::wrapper\n'.repeat(20)}${'inner\n'.repeat(5)}${'::\n'.repeat(20)}
+${':inline{a="1"}'.repeat(100)}
+`
+
+/**
+ * Truncated markdown, as produced by an LLM mid-stream: unclosed emphasis,
+ * an open code fence, a half written table and an unterminated component.
+ */
+export const partialMarkdown = `---
+title: Streaming
+---
+
+# Streaming output
+
+Here is some **bold text that is not
+
+\`\`\`typescript
+export function incomplete(input: string) {
+ const value = input
+
+| Header 1 | Header 2 |
+|----------|----------|
+| Cell 1 |
+
+::alert{type="info"
+An alert that is still *being writt
+`
+
+/**
+ * A chat-style answer with no headings. A streaming tail that contains `#`
+ * forces a full reparse (heading ids depend on the whole document), so this is
+ * the fixture that exercises incremental node reuse.
+ */
+export const chatMarkdown = `Sure! Here is how streaming works in **Comark**, step by step.
+
+First, the parser keeps the nodes it produced for the previous chunk. When the
+next chunk arrives it checks that the new text *starts with* the old one.
+
+${Array.from(
+ { length: 12 },
+ (_, i) =>
+ `${i + 1}. Step ${i + 1} reuses every stable node and only reparses the tail, so the cost stays close to the size of the new text and not the whole answer.`
+).join('\n')}
+
+::alert{type="tip"}
+Call the same parser instance for every chunk, and pass \`{ streaming: true }\`.
+::
+
+\`\`\`ts
+const parse = createMarkdownParser()
+for await (const text of stream) {
+ render(await parse(text, { streaming: true }))
+}
+\`\`\`
+
+${Array.from(
+ { length: 8 },
+ (_, i) =>
+ `Paragraph ${i + 1} keeps going with **bold**, *italic*, \`code\` and a [link](https://example.com/${i}) so that every chunk lands in a different place.`
+).join('\n\n')}
+
+| Chunk | Reused | Reparsed |
+|-------|--------|----------|
+| 1 | 0 | all |
+| n | most | tail |
+
+That's it — let me know if you want a full example!
+`
+
+/** Splits `markdown` into `count` growing prefixes, ending with the full text. */
+export function toStreamChunks(markdown: string, count: number): string[] {
+ const chunks: string[] = []
+ const size = Math.ceil(markdown.length / count)
+ for (let i = size; i < markdown.length; i += size) {
+ chunks.push(markdown.slice(0, i))
+ }
+ chunks.push(markdown)
+ return chunks
+}
+
+/** Markdown exercising the optional (non-default) plugins. */
+export const pluginMarkdown = `${mediumMarkdown}
+## Extras :rocket: :tada:
+
+"Smart quotes" -- with dashes... and a footnote[^1].
+
+Another 'quoted' sentence --- with an em dash (c) 2026.
+
+[^1]: The footnote body with **bold** text.
+
+
+
+[Link](javascript:alert(1))
+
+
+
+## Afterwards
+
+Content after the summary delimiter.
+`
+
+/** Code-heavy markdown for the syntax highlighting plugins. */
+export const codeMarkdown = `# Highlighting
+
+\`\`\`typescript
+import { parseMarkdown } from 'comark'
+
+interface Options {
+ autoClose?: boolean
+ streaming?: boolean
+}
+
+export async function render(source: string, options: Options = {}): Promise {
+ const tree = await parseMarkdown(source, options)
+ return JSON.stringify(tree.nodes)
+}
+\`\`\`
+
+\`\`\`vue
+
+
+
+ Clicked {{ count }} times
+
+\`\`\`
+
+\`\`\`json
+{ "name": "comark", "version": "1.0.0", "private": false }
+\`\`\`
+
+\`\`\`bash
+pnpm add comark && pnpm bench
+\`\`\`
+
+Inline \`const x = 1\`{lang="ts"} code.
+`
diff --git a/benchmarks/parse.bench.ts b/benchmarks/parse.bench.ts
new file mode 100644
index 000000000..d917677f2
--- /dev/null
+++ b/benchmarks/parse.bench.ts
@@ -0,0 +1,62 @@
+import { bench, describe } from 'vitest'
+import { createMarkdownParser, parseMarkdown } from 'comark'
+import {
+ adversarialMarkdown,
+ componentHeavyMarkdown,
+ largeMarkdown,
+ mediumMarkdown,
+ smallMarkdown,
+} from './fixtures.ts'
+
+// Parsers are created once (the documented way to reuse a configuration) and
+// warmed at module load, so samples measure steady-state parsing rather than
+// first-call plugin and JIT setup.
+const parse = createMarkdownParser()
+const parseWithoutAutoClose = createMarkdownParser({ autoClose: false })
+const parseWithoutDefaultPlugins = createMarkdownParser({ registerDefaultPlugins: false })
+
+for (const markdown of [smallMarkdown, mediumMarkdown, largeMarkdown, componentHeavyMarkdown, adversarialMarkdown]) {
+ await parse(markdown)
+}
+await parseWithoutAutoClose(mediumMarkdown)
+await parseWithoutDefaultPlugins(mediumMarkdown)
+await parseMarkdown(mediumMarkdown)
+
+describe('parse', () => {
+ bench('small document', async () => {
+ await parse(smallMarkdown)
+ })
+
+ bench('medium document', async () => {
+ await parse(mediumMarkdown)
+ })
+
+ bench('large document', async () => {
+ await parse(largeMarkdown)
+ })
+
+ bench('component-heavy document', async () => {
+ await parse(componentHeavyMarkdown)
+ })
+
+ bench('adversarial document', async () => {
+ await parse(adversarialMarkdown)
+ })
+})
+
+describe('parse options', () => {
+ bench('medium document without auto-close', async () => {
+ await parseWithoutAutoClose(mediumMarkdown)
+ })
+
+ bench('medium document without default plugins', async () => {
+ await parseWithoutDefaultPlugins(mediumMarkdown)
+ })
+
+ // `parseMarkdown()` resolves its options and plugins on every call (the
+ // markdown-exit instance itself is shared). The gap with `parse` above is
+ // the per-call overhead `createMarkdownParser()` saves.
+ bench('medium document with parseMarkdown()', async () => {
+ await parseMarkdown(mediumMarkdown)
+ })
+})
diff --git a/benchmarks/plugins.bench.ts b/benchmarks/plugins.bench.ts
new file mode 100644
index 000000000..c11ff813e
--- /dev/null
+++ b/benchmarks/plugins.bench.ts
@@ -0,0 +1,101 @@
+import { bench, describe } from 'vitest'
+import { createMarkdownParser } from 'comark'
+import emoji from 'comark/plugins/emoji'
+import footnotes from 'comark/plugins/footnotes'
+import headings from 'comark/plugins/headings'
+import punctuation from 'comark/plugins/punctuation'
+import rangi from 'comark/plugins/rangi'
+import security from 'comark/plugins/security'
+import shiki from 'comark/plugins/shiki'
+import summary from 'comark/plugins/summary'
+import toc from 'comark/plugins/toc'
+import { codeMarkdown, pluginMarkdown } from './fixtures.ts'
+
+// Each parser runs the same input, so the gap between a plugin and `baseline`
+// is that plugin's own cost.
+const baseline = createMarkdownParser()
+const withToc = createMarkdownParser({ plugins: [toc()] })
+const withEmoji = createMarkdownParser({ plugins: [emoji()] })
+const withPunctuation = createMarkdownParser({ plugins: [punctuation()] })
+const withFootnotes = createMarkdownParser({ plugins: [footnotes()] })
+const withHeadings = createMarkdownParser({ plugins: [headings()] })
+const withSecurity = createMarkdownParser({ plugins: [security()] })
+const withSummary = createMarkdownParser({ plugins: [summary()] })
+const withAll = createMarkdownParser({
+ plugins: [toc(), emoji(), punctuation(), footnotes(), headings(), security(), summary()],
+})
+
+const codeBaseline = createMarkdownParser()
+const withShiki = createMarkdownParser({ plugins: [shiki()] })
+const withRangi = createMarkdownParser({ plugins: [rangi()] })
+
+// Warm every parser. For Shiki this also loads the shared highlighter, its
+// themes and grammars, so the benchmark measures highlighting, not setup.
+for (const parse of [
+ baseline,
+ withToc,
+ withEmoji,
+ withPunctuation,
+ withFootnotes,
+ withHeadings,
+ withSecurity,
+ withSummary,
+ withAll,
+]) {
+ await parse(pluginMarkdown)
+}
+for (const parse of [codeBaseline, withShiki, withRangi]) {
+ await parse(codeMarkdown)
+}
+
+describe('plugins', () => {
+ bench('baseline (default plugins only)', async () => {
+ await baseline(pluginMarkdown)
+ })
+
+ bench('toc', async () => {
+ await withToc(pluginMarkdown)
+ })
+
+ bench('emoji', async () => {
+ await withEmoji(pluginMarkdown)
+ })
+
+ bench('punctuation', async () => {
+ await withPunctuation(pluginMarkdown)
+ })
+
+ bench('footnotes', async () => {
+ await withFootnotes(pluginMarkdown)
+ })
+
+ bench('headings', async () => {
+ await withHeadings(pluginMarkdown)
+ })
+
+ bench('security', async () => {
+ await withSecurity(pluginMarkdown)
+ })
+
+ bench('summary', async () => {
+ await withSummary(pluginMarkdown)
+ })
+
+ bench('all of the above combined', async () => {
+ await withAll(pluginMarkdown)
+ })
+})
+
+describe('highlight', () => {
+ bench('baseline (no highlighter)', async () => {
+ await codeBaseline(codeMarkdown)
+ })
+
+ bench('shiki', async () => {
+ await withShiki(codeMarkdown)
+ })
+
+ bench('rangi', async () => {
+ await withRangi(codeMarkdown)
+ })
+})
diff --git a/benchmarks/render.bench.ts b/benchmarks/render.bench.ts
new file mode 100644
index 000000000..b3c651acc
--- /dev/null
+++ b/benchmarks/render.bench.ts
@@ -0,0 +1,77 @@
+import { bench, describe } from 'vitest'
+import { createMarkdownParser } from 'comark'
+import { renderMarkdown } from 'comark/render'
+import { renderAnsiFromDocument } from '@comark/ansi'
+import { renderHtmlFromDocument } from '@comark/html'
+import { componentHeavyMarkdown, largeMarkdown, mediumMarkdown, smallMarkdown } from './fixtures.ts'
+
+const parse = createMarkdownParser()
+
+// Documents are parsed once so render benchmarks measure rendering only.
+const smallDocument = await parse(smallMarkdown)
+const mediumDocument = await parse(mediumMarkdown)
+const largeDocument = await parse(largeMarkdown)
+const componentDocument = await parse(componentHeavyMarkdown)
+
+const ansiOptions = { colors: true, width: 80 }
+
+await renderHtmlFromDocument(mediumDocument)
+await renderAnsiFromDocument(mediumDocument, ansiOptions)
+await renderMarkdown(mediumDocument)
+
+describe('render html', () => {
+ bench('small document', async () => {
+ await renderHtmlFromDocument(smallDocument)
+ })
+
+ bench('medium document', async () => {
+ await renderHtmlFromDocument(mediumDocument)
+ })
+
+ bench('large document', async () => {
+ await renderHtmlFromDocument(largeDocument)
+ })
+
+ bench('component-heavy document', async () => {
+ await renderHtmlFromDocument(componentDocument)
+ })
+})
+
+describe('render ansi', () => {
+ bench('medium document', async () => {
+ await renderAnsiFromDocument(mediumDocument, ansiOptions)
+ })
+
+ bench('large document', async () => {
+ await renderAnsiFromDocument(largeDocument, ansiOptions)
+ })
+})
+
+describe('render markdown', () => {
+ bench('medium document', async () => {
+ await renderMarkdown(mediumDocument)
+ })
+
+ bench('large document', async () => {
+ await renderMarkdown(largeDocument)
+ })
+
+ bench('component-heavy document', async () => {
+ await renderMarkdown(componentDocument)
+ })
+})
+
+describe('parse and render', () => {
+ bench('medium document to html', async () => {
+ await renderHtmlFromDocument(await parse(mediumMarkdown))
+ })
+
+ bench('large document to html', async () => {
+ await renderHtmlFromDocument(await parse(largeMarkdown))
+ })
+
+ // Roundtrip used by editors and CMS integrations.
+ bench('component-heavy document to markdown', async () => {
+ await renderMarkdown(await parse(componentHeavyMarkdown))
+ })
+})
diff --git a/benchmarks/streaming.bench.ts b/benchmarks/streaming.bench.ts
new file mode 100644
index 000000000..280651bb2
--- /dev/null
+++ b/benchmarks/streaming.bench.ts
@@ -0,0 +1,82 @@
+import { bench, describe } from 'vitest'
+import { autoCloseMarkdown, createMarkdownParser } from 'comark'
+import type { ComarkParseFn } from 'comark'
+import {
+ adversarialMarkdown,
+ chatMarkdown,
+ largeMarkdown,
+ mediumMarkdown,
+ partialMarkdown,
+ toStreamChunks,
+} from './fixtures.ts'
+
+const mediumChunks = toStreamChunks(mediumMarkdown, 12)
+const chatChunks = toStreamChunks(chatMarkdown, 24)
+const partialChunks = toStreamChunks(partialMarkdown, 8)
+
+// One parser per benchmark: a streaming parser caches the previous input and
+// output, so sharing one would make results depend on execution order.
+const parsePartial = createMarkdownParser()
+const streamPartial = createMarkdownParser()
+const streamMedium = createMarkdownParser()
+const streamChat = createMarkdownParser()
+
+/**
+ * Replays a whole stream from scratch. The non-streaming call clears the
+ * parser's incremental cache; without it every iteration after the first would
+ * see an identical input and return the cached document without parsing.
+ */
+async function replay(parse: ComarkParseFn, chunks: string[]): Promise {
+ await parse('', { streaming: false })
+ for (const chunk of chunks) {
+ await parse(chunk, { streaming: true })
+ }
+}
+
+await parsePartial(partialMarkdown)
+await replay(streamPartial, partialChunks)
+await replay(streamMedium, mediumChunks)
+await replay(streamChat, chatChunks)
+autoCloseMarkdown(partialMarkdown, { frontmatter: true })
+
+describe('auto-close', () => {
+ bench('partial document', () => {
+ autoCloseMarkdown(partialMarkdown, { frontmatter: true })
+ })
+
+ bench('medium document', () => {
+ autoCloseMarkdown(mediumMarkdown, { frontmatter: true })
+ })
+
+ bench('large document', () => {
+ autoCloseMarkdown(largeMarkdown, { frontmatter: true })
+ })
+
+ bench('adversarial document', () => {
+ autoCloseMarkdown(adversarialMarkdown, { frontmatter: true })
+ })
+
+ bench('partial document, dropping trailing openers', () => {
+ autoCloseMarkdown(partialMarkdown, { frontmatter: true, dropTrailingOpeners: true })
+ })
+})
+
+describe('streaming', () => {
+ bench('parse a partial document', async () => {
+ await parsePartial(partialMarkdown)
+ })
+
+ bench('stream a partial document', async () => {
+ await replay(streamPartial, partialChunks)
+ })
+
+ // Headings in the tail force full reparses, so this tracks the worst case.
+ bench('stream a medium document', async () => {
+ await replay(streamMedium, mediumChunks)
+ })
+
+ // No headings: the tail is reparsed on its own and stable nodes are reused.
+ bench('stream a chat answer (incremental reuse)', async () => {
+ await replay(streamChat, chatChunks)
+ })
+})
diff --git a/benchmarks/vitest.config.ts b/benchmarks/vitest.config.ts
new file mode 100644
index 000000000..dbfb28c28
--- /dev/null
+++ b/benchmarks/vitest.config.ts
@@ -0,0 +1,18 @@
+import codspeedPlugin from '@codspeed/vitest-plugin'
+import { defineConfig } from 'vitest/config'
+
+// CodSpeed benchmark suite (`pnpm bench`). Kept separate from the root
+// `vitest.config.ts` so the bundle-size test and the benches never pick up
+// each other's files. Outside a CodSpeed run the plugin is a no-op and
+// `vitest bench` falls back to regular wall-clock measurements.
+export default defineConfig({
+ root: import.meta.dirname,
+ plugins: [codspeedPlugin()],
+ test: {
+ environment: 'node',
+ globals: false,
+ benchmark: {
+ include: ['**/*.bench.ts'],
+ },
+ },
+})
diff --git a/package.json b/package.json
index ab3d628a5..b53abcf92 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"build:comark": "cd packages/comark && pnpm build",
"test": "pnpm --filter 'comark' run test && pnpm --filter '@comark/*' run test",
"test:spec": "cd packages/comark && pnpm vitest run test/index.test.ts -- -spec",
+ "bench": "vitest bench --run --config benchmarks/vitest.config.ts",
"lint": "oxlint . && oxfmt --check .",
"lint:fix": "oxlint --fix . && oxfmt .",
"typecheck": "tsc --noEmit",
@@ -49,7 +50,9 @@
"postinstall": "pnpm stub"
},
"devDependencies": {
+ "@codspeed/vitest-plugin": "catalog:",
"@comark/ansi": "workspace:*",
+ "@comark/html": "workspace:*",
"@release-it/conventional-changelog": "catalog:",
"@types/node": "catalog:",
"@vitejs/plugin-vue": "catalog:",
@@ -67,6 +70,7 @@
"remark-parse": "catalog:",
"remark-rehype": "catalog:",
"scule": "catalog:",
+ "tinybench": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"unified": "catalog:",
diff --git a/packages/comark/README.md b/packages/comark/README.md
index a829f0387..361c406f9 100644
--- a/packages/comark/README.md
+++ b/packages/comark/README.md
@@ -5,6 +5,7 @@
[](https://npmx.dev/comark)
[](https://npm.chart.dev/comark)
[](https://github.com/comarkdown/comark/actions/workflows/ci.yml)
+[](https://app.codspeed.io/comarkdown/comark)
[](https://comark.dev)
[](https://github.com/comarkdown/comark/blob/main/LICENSE)
diff --git a/packages/comark/test/benchmark.test.ts b/packages/comark/test/benchmark.test.ts
deleted file mode 100644
index 27f998253..000000000
--- a/packages/comark/test/benchmark.test.ts
+++ /dev/null
@@ -1,330 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { parseMarkdown } from '../src/index'
-
-// Helper to measure memory usage
-function getMemoryUsage(): number {
- if (typeof process !== 'undefined' && process.memoryUsage) {
- const usage = process.memoryUsage()
- return usage.heapUsed
- }
- // Fallback for environments without process.memoryUsage
- if (typeof performance !== 'undefined' && (performance as any).memory) {
- return (performance as any).memory.usedJSHeapSize
- }
- return 0
-}
-
-// Helper to format bytes
-function formatBytes(bytes: number): string {
- if (bytes === 0) return '0 B'
- const k = 1024
- const sizes = ['B', 'KB', 'MB', 'GB']
- const i = Math.floor(Math.log(bytes) / Math.log(k))
- return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`
-}
-
-// Helper to measure execution time
-function measureTime(fn: () => void): number {
- const start = performance.now()
- fn()
- const end = performance.now()
- return end - start
-}
-
-// Test cases for benchmarking
-const benchmarkCases = [
- {
- name: 'simple heading',
- content: '# Hello World',
- },
- {
- name: 'multiple paragraphs',
- content: 'First paragraph.\n\nSecond paragraph.\n\nThird paragraph.',
- },
- {
- name: 'complex markdown',
- content: `# Main Title
-
-This is a paragraph with **bold** and *italic* text.
-
-## Subsection
-
-- List item 1
-- List item 2
-- List item 3
-
-\`\`\`javascript
-const x = 1;
-\`\`\`
-
-> This is a blockquote.
-
-[Link](https://example.com)
-
-
-`,
- },
- {
- name: 'component with content',
- content: '::component\nThis is a component with content.\n::',
- },
- {
- name: 'nested components',
- content: ':::parent\n::child\n::\n:::',
- },
- {
- name: 'inline components',
- content: 'Hello :world[Inline Component Content]{data-component="test"}',
- },
- {
- name: 'large document',
- content: Array.from({ length: 100 }).fill('# Heading\n\nThis is a paragraph with some content.').join('\n\n'),
- },
-]
-
-describe('benchmark parse vs parse', () => {
- it('should benchmark execution time', () => {
- const results: Array<{
- testCase: string
- parseTime: number
- markdownItTime: number
- speedup: number
- }> = []
-
- for (const testCase of benchmarkCases) {
- // Warm up
- parseMarkdown(testCase.content)
- parseMarkdown(testCase.content)
-
- // Measure parse
- const parseTimes: number[] = []
- for (let i = 0; i < 10; i++) {
- parseTimes.push(measureTime(() => parseMarkdown(testCase.content)))
- }
- const avgParseTime = parseTimes.reduce((a, b) => a + b, 0) / parseTimes.length
-
- // Measure parse
- const markdownItTimes: number[] = []
- for (let i = 0; i < 10; i++) {
- markdownItTimes.push(measureTime(() => parseMarkdown(testCase.content)))
- }
- const avgMarkdownItTime = markdownItTimes.reduce((a, b) => a + b, 0) / markdownItTimes.length
-
- const speedup = avgParseTime / avgMarkdownItTime
-
- results.push({
- testCase: testCase.name,
- parseTime: avgParseTime,
- markdownItTime: avgMarkdownItTime,
- speedup,
- })
- }
-
- // Log results
- console.log('\n=== Execution Time Benchmark ===')
- console.log('Test Case'.padEnd(30), 'parseMarkdown(ms)'.padEnd(15), 'markdown-it (ms)'.padEnd(15), 'Speedup')
- console.log('-'.repeat(80))
- for (const result of results) {
- const speedupStr =
- result.speedup > 1
- ? `${result.speedup.toFixed(2)}x faster (markdown-it)`
- : `${(1 / result.speedup).toFixed(2)}x faster (parse)`
- console.log(
- result.testCase.padEnd(30),
- result.parseTime.toFixed(3).padEnd(15),
- result.markdownItTime.toFixed(3).padEnd(15),
- speedupStr
- )
- }
-
- // Test passes - we're just benchmarking
- expect(results.length).toBeGreaterThan(0)
- })
-
- it('should benchmark memory usage', async () => {
- const results: Array<{
- testCase: string
- parseMemory: number
- markdownItMemory: number
- memoryDiff: number
- }> = []
-
- for (const testCase of benchmarkCases) {
- // Measure parse memory (run multiple times and average)
- let parseMemorySum = 0
- let parseMemoryCount = 0
- for (let i = 0; i < 5; i++) {
- if (global.gc) global.gc()
- const beforeParse = getMemoryUsage()
- const parseResult = await parseMarkdown(testCase.content)
- const afterParse = getMemoryUsage()
- const memory = afterParse - beforeParse
- if (memory > 0 && Number.isFinite(memory)) {
- parseMemorySum += memory
- parseMemoryCount++
- }
- // Keep reference
- const _ = parseResult
- }
- const parseMemory = parseMemoryCount > 0 ? parseMemorySum / parseMemoryCount : 0
-
- // Measure parse memory (run multiple times and average)
- let markdownItMemorySum = 0
- let markdownItMemoryCount = 0
- for (let i = 0; i < 5; i++) {
- if (global.gc) global.gc()
- const beforeMarkdownIt = getMemoryUsage()
- const markdownItResult = await parseMarkdown(testCase.content)
- const afterMarkdownIt = getMemoryUsage()
- const memory = afterMarkdownIt - beforeMarkdownIt
- if (memory > 0 && Number.isFinite(memory)) {
- markdownItMemorySum += memory
- markdownItMemoryCount++
- }
- // Keep reference
- const _ = markdownItResult
- }
- const markdownItMemory = markdownItMemoryCount > 0 ? markdownItMemorySum / markdownItMemoryCount : 0
-
- const memoryDiff = parseMemory - markdownItMemory
-
- results.push({
- testCase: testCase.name,
- parseMemory,
- markdownItMemory,
- memoryDiff,
- })
- }
-
- // Log results
- console.log('\n=== Memory Usage Benchmark ===')
- console.log('Test Case'.padEnd(30), 'parse'.padEnd(20), 'markdown-it'.padEnd(20), 'Difference')
- console.log('-'.repeat(90))
- for (const result of results) {
- let diffStr = 'N/A'
- if (Number.isFinite(result.memoryDiff) && !Number.isNaN(result.memoryDiff)) {
- if (result.memoryDiff > 0) {
- diffStr = `+${formatBytes(result.memoryDiff)} (markdown-it uses less)`
- } else if (result.memoryDiff < 0) {
- diffStr = `${formatBytes(Math.abs(result.memoryDiff))} (parse uses less)`
- } else {
- diffStr = 'Equal'
- }
- }
- const parseMemStr =
- result.parseMemory > 0 && Number.isFinite(result.parseMemory) ? formatBytes(result.parseMemory) : 'N/A'
- const markdownItMemStr =
- result.markdownItMemory > 0 && Number.isFinite(result.markdownItMemory)
- ? formatBytes(result.markdownItMemory)
- : 'N/A'
- console.log(result.testCase.padEnd(30), parseMemStr.padEnd(20), markdownItMemStr.padEnd(20), diffStr)
- }
-
- // Test passes - we're just benchmarking
- expect(results.length).toBeGreaterThan(0)
- })
-
- it('should benchmark combined time and memory', async () => {
- const results: Array<{
- testCase: string
- parseTime: number
- markdownItTime: number
- parseMemory: number
- markdownItMemory: number
- parseScore: number // Lower is better (time * memory)
- markdownItScore: number
- }> = []
-
- for (const testCase of benchmarkCases) {
- // Warm up
- parseMarkdown(testCase.content)
- parseMarkdown(testCase.content)
-
- // Measure parse time
- const parseTimes: number[] = []
- for (let i = 0; i < 10; i++) {
- parseTimes.push(measureTime(() => parseMarkdown(testCase.content)))
- }
- const avgParseTime = parseTimes.reduce((a, b) => a + b, 0) / parseTimes.length
-
- // Measure parse memory (average of multiple runs)
- let parseMemorySum = 0
- let parseMemoryCount = 0
- for (let i = 0; i < 5; i++) {
- if (global.gc) global.gc()
- const beforeParse = getMemoryUsage()
- const parseResult = await parseMarkdown(testCase.content)
- const afterParse = getMemoryUsage()
- const memory = afterParse - beforeParse
- if (memory > 0 && Number.isFinite(memory)) {
- parseMemorySum += memory
- parseMemoryCount++
- }
- const _ = parseResult
- }
- const parseMemory = parseMemoryCount > 0 ? parseMemorySum / parseMemoryCount : 0
-
- // Measure parse time
- const markdownItTimes: number[] = []
- for (let i = 0; i < 10; i++) {
- markdownItTimes.push(measureTime(() => parseMarkdown(testCase.content)))
- }
- const avgMarkdownItTime = markdownItTimes.reduce((a, b) => a + b, 0) / markdownItTimes.length
-
- // Measure parse memory (average of multiple runs)
- let markdownItMemorySum = 0
- let markdownItMemoryCount = 0
- for (let i = 0; i < 5; i++) {
- if (global.gc) global.gc()
- const beforeMarkdownIt = getMemoryUsage()
- const markdownItResult = await parseMarkdown(testCase.content)
- const afterMarkdownIt = getMemoryUsage()
- const memory = afterMarkdownIt - beforeMarkdownIt
- if (memory > 0 && Number.isFinite(memory)) {
- markdownItMemorySum += memory
- markdownItMemoryCount++
- }
- const _ = markdownItResult
- }
- const markdownItMemory = markdownItMemoryCount > 0 ? markdownItMemorySum / markdownItMemoryCount : 0
-
- // Calculate scores (time * memory, lower is better)
- const parseScore = avgParseTime * parseMemory
- const markdownItScore = avgMarkdownItTime * markdownItMemory
-
- results.push({
- testCase: testCase.name,
- parseTime: avgParseTime,
- markdownItTime: avgMarkdownItTime,
- parseMemory,
- markdownItMemory,
- parseScore,
- markdownItScore,
- })
- }
-
- // Log results
- console.log('\n=== Combined Time & Memory Benchmark ===')
- console.log(
- 'Test Case'.padEnd(30),
- 'parseMarkdown(time×mem)'.padEnd(20),
- 'markdown-it (time×mem)'.padEnd(20),
- 'Winner'
- )
- console.log('-'.repeat(90))
- for (const result of results) {
- const winner = result.parseScore < result.markdownItScore ? 'parse' : 'markdown-it'
- const scoreDiff = Math.abs(result.parseScore - result.markdownItScore)
- const scoreDiffPercent = ((scoreDiff / Math.max(result.parseScore, result.markdownItScore)) * 100).toFixed(1)
- console.log(
- result.testCase.padEnd(30),
- result.parseScore.toFixed(2).padEnd(20),
- result.markdownItScore.toFixed(2).padEnd(20),
- `${winner} (${scoreDiffPercent}% difference)`
- )
- }
-
- // Test passes - we're just benchmarking
- expect(results.length).toBeGreaterThan(0)
- })
-})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8bda55331..728b8718f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -203,6 +203,9 @@ catalogs:
'@astrojs/react':
specifier: ^6.0.6
version: 6.0.6
+ '@codspeed/vitest-plugin':
+ specifier: ^6.0.0
+ version: 6.0.1
'@comark/vue':
specifier: ^0.7.0
version: 0.7.0
@@ -404,6 +407,9 @@ catalogs:
tailwindcss:
specifier: ^4.3.3
version: 4.3.3
+ tinybench:
+ specifier: ^2.9.0
+ version: 2.9.0
tslib:
specifier: ^2.8.1
version: 2.8.1
@@ -463,9 +469,15 @@ importers:
.:
devDependencies:
+ '@codspeed/vitest-plugin':
+ specifier: 'catalog:'
+ version: 6.0.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)(tinybench@2.9.0)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11)
'@comark/ansi':
specifier: workspace:*
version: link:packages/comark-ansi
+ '@comark/html':
+ specifier: workspace:*
+ version: link:packages/comark-html
'@release-it/conventional-changelog':
specifier: 'catalog:'
version: 12.0.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)(release-it@20.2.1(@types/node@26.6.5)(magicast@0.5.5)(supports-color@10.2.2))
@@ -517,6 +529,9 @@ importers:
scule:
specifier: 'catalog:'
version: 1.3.0
+ tinybench:
+ specifier: 'catalog:'
+ version: 2.9.0
tsx:
specifier: 'catalog:'
version: 4.23.15
@@ -573,7 +588,7 @@ importers:
version: link:../packages/comark
comark-docs:
specifier: github:comarkdown/comark-docs#ab34f83fd3bb4f6c139a0ebb048444ae14600ddc
- version: https://codeload.github.com/comarkdown/comark-docs/tar.gz/ab34f83fd3bb4f6c139a0ebb048444ae14600ddc(47c66161b30483af3d57baceabb01916)
+ version: https://codeload.github.com/comarkdown/comark-docs/tar.gz/ab34f83fd3bb4f6c139a0ebb048444ae14600ddc(1dc354a5842c2d4fe473e5dcbb28bdcd)
katex:
specifier: 'catalog:'
version: 0.18.7
@@ -690,7 +705,7 @@ importers:
version: link:../../../packages/comark-vue
'@nuxt/ui':
specifier: 'catalog:'
- version: 4.11.2(4b0897582682e6d02cf53b6c671c253a)
+ version: 4.11.2(273273d1a6333fd3d39b35997f593405)
nuxt:
specifier: 'catalog:'
version: 4.6.1(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.150.0)(@parcel/watcher@2.6.0)(@types/node@26.6.5)(@vercel/functions@3.9.8(ws@8.21.3))(@vue/compiler-sfc@3.5.43)(acorn@8.18.0)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(mlly@1.8.2)(oxc-parser@0.149.0)(oxlint@1.87.0)(rolldown@1.2.13)(rollup-plugin-visualizer@7.1.1(rolldown@1.2.13)(rollup@4.63.4))(rollup@4.63.4)(sass-embedded@1.104.1)(sass@1.104.1)(srvx@1.0.5)(supports-color@10.2.2)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue-tsc@3.3.11(typescript@6.0.3))(yaml@2.9.1)
@@ -740,7 +755,7 @@ importers:
devDependencies:
vitepress:
specifier: 'catalog:'
- version: 1.6.4(@algolia/client-search@5.59.0)(@types/node@26.6.5)(fuse.js@7.5.0)(lightningcss@1.33.0)(postcss@8.5.30)(sass-embedded@1.104.1)(sass@1.104.1)(search-insights@2.17.3)(terser@5.51.2)(typescript@6.0.3)
+ version: 1.6.4(@algolia/client-search@5.59.0)(@types/node@26.6.5)(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(fuse.js@7.5.0)(lightningcss@1.33.0)(postcss@8.5.30)(sass-embedded@1.104.1)(sass@1.104.1)(search-insights@2.17.3)(terser@5.51.2)(typescript@6.0.3)
examples/2.vite/angular:
dependencies:
@@ -937,7 +952,7 @@ importers:
version: link:../../../packages/comark-vue
'@nuxt/ui':
specifier: 'catalog:'
- version: 4.11.2(4b0897582682e6d02cf53b6c671c253a)
+ version: 4.11.2(273273d1a6333fd3d39b35997f593405)
'@shikijs/langs':
specifier: 'catalog:'
version: 4.4.3
@@ -1187,7 +1202,7 @@ importers:
version: link:../../../packages/comark-vue
'@nuxt/ui':
specifier: 'catalog:'
- version: 4.11.2(1a8b2b83617866e23a59431b12d70dbe)
+ version: 4.11.2(c576b84be0c45ccbc0bd480dd9c31047)
'@shikijs/themes':
specifier: 'catalog:'
version: 4.4.3
@@ -1339,7 +1354,7 @@ importers:
version: link:../../../packages/comark-vue
'@nuxt/ui':
specifier: 'catalog:'
- version: 4.11.2(1a8b2b83617866e23a59431b12d70dbe)
+ version: 4.11.2(c576b84be0c45ccbc0bd480dd9c31047)
'@shikijs/langs':
specifier: 'catalog:'
version: 4.4.3
@@ -1391,7 +1406,7 @@ importers:
version: link:../../../packages/comark-nuxt
'@nuxt/ui':
specifier: 'catalog:'
- version: 4.11.2(4b0897582682e6d02cf53b6c671c253a)
+ version: 4.11.2(273273d1a6333fd3d39b35997f593405)
ai:
specifier: 'catalog:'
version: 7.0.112(zod@4.6.5)
@@ -2283,6 +2298,16 @@ packages:
resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==}
engines: {node: '>=18.0.0'}
+ '@codspeed/core@6.0.1':
+ resolution: {integrity: sha512-p6yrZIoz/Bf5vW/QfGhiPOCKccGPES3jQ8NA+03qGcUYR3/U0tZ9h6ZkxVg2zLN10YaHrABEY4GtmTEEE5xF5Q==}
+
+ '@codspeed/vitest-plugin@6.0.1':
+ resolution: {integrity: sha512-QEIkp3UtXwbT3eB7NZ49+Jz5xpzHopSFMscASF4lpbjTgbOt/Xax6vIPShDSPKB5JErexyJBze7AwCOPgkoNqQ==}
+ peerDependencies:
+ tinybench: '>=2.9.0'
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+ vitest: ^3.2 || ^4 || ^5
+
'@colordx/core@7.3.0':
resolution: {integrity: sha512-LVzTTPhX7wpy7TkGjouA9zbbCBIw92RGm7ZQqSKFE0oYE3sMx2FGXz90eVpgw7k/+wrrY+qJ86nHXifZWMS5jA==}
@@ -6313,6 +6338,10 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
@@ -6448,6 +6477,9 @@ packages:
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
autoprefixer@10.6.1:
resolution: {integrity: sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==}
engines: {node: ^10 || ^12 || >=14}
@@ -6455,6 +6487,9 @@ packages:
peerDependencies:
postcss: ^8.1.0
+ axios@1.20.0:
+ resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
+
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -6763,6 +6798,10 @@ packages:
shiki:
optional: true
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@@ -7065,6 +7104,10 @@ packages:
peerDependencies:
quickjs-wasi: ^0.0.1
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
@@ -7280,6 +7323,10 @@ packages:
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
engines: {node: '>= 0.4'}
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
es-toolkit@1.52.0:
resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==}
@@ -7494,6 +7541,15 @@ packages:
focus-trap@7.8.0:
resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==}
+ follow-redirects@1.16.1:
+ resolution: {integrity: sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
fontace@0.4.1:
resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==}
@@ -7518,6 +7574,10 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
+ engines: {node: '>= 6'}
+
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -7660,6 +7720,10 @@ packages:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
@@ -7715,6 +7779,10 @@ packages:
resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -8444,10 +8512,18 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
@@ -9399,6 +9475,10 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
+
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
@@ -9955,6 +10035,10 @@ packages:
engines: {node: '>=20.16.0'}
hasBin: true
+ stack-trace@1.0.0-pre2:
+ resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==}
+ engines: {node: '>=16'}
+
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -11822,6 +11906,26 @@ snapshots:
'@cloudflare/kv-asset-handler@0.4.2': {}
+ '@codspeed/core@6.0.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)':
+ dependencies:
+ axios: 1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)
+ form-data: 4.0.6
+ node-gyp-build: 4.8.4
+ stack-trace: 1.0.0-pre2
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
+ '@codspeed/vitest-plugin@6.0.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)(tinybench@2.9.0)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11)':
+ dependencies:
+ '@codspeed/core': 6.0.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)
+ tinybench: 2.9.0
+ vite: 8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)
+ vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/browser-playwright@4.1.11)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
'@colordx/core@7.3.0': {}
'@comark/nuxt@0.7.0(21001827894c27d28f497a21a5402cf1)':
@@ -13219,7 +13323,7 @@ snapshots:
rc9: 3.1.0
std-env: 4.3.0
- '@nuxt/ui@4.11.2(1a8b2b83617866e23a59431b12d70dbe)':
+ '@nuxt/ui@4.11.2(273273d1a6333fd3d39b35997f593405)':
dependencies:
'@floating-ui/dom': 1.8.0
'@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3))
@@ -13252,9 +13356,9 @@ snapshots:
'@tiptap/starter-kit': 3.31.3
'@tiptap/suggestion': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)
'@tiptap/vue-3': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(vue@3.5.43(typescript@6.0.3))
- '@unhead/vue': 3.4.3(@oxc-project/types@0.153.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.149.0)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))
+ '@unhead/vue': 3.4.3(@oxc-project/types@0.150.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.149.0)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))
'@vueuse/core': 14.4.0(vue@3.5.43(typescript@6.0.3))
- '@vueuse/integrations': 14.4.0(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))
+ '@vueuse/integrations': 14.4.0(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))
'@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3))
colortranslator: 5.0.0
consola: 3.4.2
@@ -13346,7 +13450,7 @@ snapshots:
- vue
- webpack
- '@nuxt/ui@4.11.2(4b0897582682e6d02cf53b6c671c253a)':
+ '@nuxt/ui@4.11.2(c576b84be0c45ccbc0bd480dd9c31047)':
dependencies:
'@floating-ui/dom': 1.8.0
'@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3))
@@ -13379,9 +13483,9 @@ snapshots:
'@tiptap/starter-kit': 3.31.3
'@tiptap/suggestion': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)
'@tiptap/vue-3': 3.31.3(@floating-ui/dom@1.8.0)(@tiptap/core@3.31.3(@tiptap/pm@3.31.3))(@tiptap/pm@3.31.3)(vue@3.5.43(typescript@6.0.3))
- '@unhead/vue': 3.4.3(@oxc-project/types@0.150.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.149.0)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))
+ '@unhead/vue': 3.4.3(@oxc-project/types@0.153.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.149.0)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))
'@vueuse/core': 14.4.0(vue@3.5.43(typescript@6.0.3))
- '@vueuse/integrations': 14.4.0(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))
+ '@vueuse/integrations': 14.4.0(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))
'@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3))
colortranslator: 5.0.0
consola: 3.4.2
@@ -15672,23 +15776,25 @@ snapshots:
'@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3))
vue: 3.5.43(typescript@6.0.3)
- '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(fuse.js@7.5.0)(typescript@6.0.3)':
+ '@vueuse/integrations@12.8.2(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(focus-trap@7.8.0)(fuse.js@7.5.0)(typescript@6.0.3)':
dependencies:
'@vueuse/core': 12.8.2(typescript@6.0.3)
'@vueuse/shared': 12.8.2(typescript@6.0.3)
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
+ axios: 1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)
focus-trap: 7.8.0
fuse.js: 7.5.0
transitivePeerDependencies:
- typescript
- '@vueuse/integrations@14.4.0(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))':
+ '@vueuse/integrations@14.4.0(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(focus-trap@7.8.0)(fuse.js@7.5.0)(vue@3.5.43(typescript@6.0.3))':
dependencies:
'@vueuse/core': 14.4.0(vue@3.5.43(typescript@6.0.3))
'@vueuse/shared': 14.4.0(vue@3.5.43(typescript@6.0.3))
vue: 3.5.43(typescript@6.0.3)
optionalDependencies:
+ axios: 1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)
focus-trap: 7.8.0
fuse.js: 7.5.0
@@ -15738,6 +15844,12 @@ snapshots:
acorn@8.18.0: {}
+ agent-base@6.0.2(supports-color@10.2.2):
+ dependencies:
+ debug: 4.4.3(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
agent-base@7.1.4: {}
agent-base@8.0.0: {}
@@ -15968,6 +16080,8 @@ snapshots:
async@3.2.6: {}
+ asynckit@0.4.0: {}
+
autoprefixer@10.6.1(postcss@8.5.30):
dependencies:
browserslist: 4.29.3
@@ -15977,6 +16091,16 @@ snapshots:
postcss: 8.5.30
postcss-value-parser: 4.2.0
+ axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2):
+ dependencies:
+ follow-redirects: 1.16.1(debug@4.4.3(supports-color@10.2.2))
+ form-data: 4.0.6
+ https-proxy-agent: 5.0.1(supports-color@10.2.2)
+ proxy-from-env: 2.1.0
+ transitivePeerDependencies:
+ - debug
+ - supports-color
+
axobject-query@4.1.0: {}
b4a@1.9.0: {}
@@ -16278,7 +16402,7 @@ snapshots:
- shiki
- uploadthing
- comark-docs@https://codeload.github.com/comarkdown/comark-docs/tar.gz/ab34f83fd3bb4f6c139a0ebb048444ae14600ddc(47c66161b30483af3d57baceabb01916):
+ comark-docs@https://codeload.github.com/comarkdown/comark-docs/tar.gz/ab34f83fd3bb4f6c139a0ebb048444ae14600ddc(1dc354a5842c2d4fe473e5dcbb28bdcd):
dependencies:
'@ai-sdk/gateway': 4.0.90(zod@4.6.5)
'@ai-sdk/vue': 4.0.112(vue@3.5.43(typescript@6.0.3))(zod@4.6.5)
@@ -16290,7 +16414,7 @@ snapshots:
'@iconify-json/vscode-icons': 1.2.79
'@iconify/vue': 5.0.2(vue@3.5.43(typescript@6.0.3))
'@nuxt/kit': 4.6.1(@nuxt/schema@4.6.1)(confbox@0.3.1)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.4.3)(mlly@1.8.2)(oxc-parser@0.149.0)(rolldown@1.2.13)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))
- '@nuxt/ui': 4.11.2(4b0897582682e6d02cf53b6c671c253a)
+ '@nuxt/ui': 4.11.2(273273d1a6333fd3d39b35997f593405)
'@nuxtjs/mcp-toolkit': 0.21.0(@nuxt/schema@4.6.1)(@vue/compiler-sfc@3.5.43)(confbox@0.3.1)(dotenv@17.4.2)(giget@3.3.1)(h3@1.15.11)(jiti@2.7.0)(magic-string@1.4.3)(mlly@1.8.2)(oxc-parser@0.149.0)(rolldown@1.2.13)(rollup@4.63.4)(supports-color@10.2.2)(unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.13)(rollup@4.63.4)(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(vite@8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(vue@3.5.43(typescript@6.0.3))(zod@4.6.5)
'@nuxtjs/robots': 6.2.3(6a1be41e09627cb8c3da328ae62b758c)
'@nuxtjs/sitemap': 8.5.1(6a1be41e09627cb8c3da328ae62b758c)
@@ -16468,6 +16592,10 @@ snapshots:
rangi: 2.2.0
shiki: 4.4.3
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
comma-separated-tokens@2.0.3: {}
commander@11.1.0: {}
@@ -16739,6 +16867,8 @@ snapshots:
esprima: 4.0.1
quickjs-wasi: 0.0.1
+ delayed-stream@1.0.0: {}
+
denque@2.1.0: {}
depd@2.0.0: {}
@@ -16905,6 +17035,13 @@ snapshots:
dependencies:
es-errors: 1.3.0
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
es-toolkit@1.52.0: {}
esbuild@0.21.5:
@@ -17207,6 +17344,10 @@ snapshots:
dependencies:
tabbable: 6.5.0
+ follow-redirects@1.16.1(debug@4.4.3(supports-color@10.2.2)):
+ optionalDependencies:
+ debug: 4.4.3(supports-color@10.2.2)
+
fontace@0.4.1:
dependencies:
fontkitten: 1.0.3
@@ -17268,6 +17409,14 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ form-data@4.0.6:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.4
+ mime-types: 2.1.35
+
forwarded@0.2.0: {}
fraction.js@5.3.4: {}
@@ -17419,6 +17568,10 @@ snapshots:
has-symbols@1.1.0: {}
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@@ -17485,6 +17638,13 @@ snapshots:
http-shutdown@1.2.2: {}
+ https-proxy-agent@5.0.1(supports-color@10.2.2):
+ dependencies:
+ agent-base: 6.0.2(supports-color@10.2.2)
+ debug: 4.4.3(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
https-proxy-agent@7.0.6(supports-color@10.2.2):
dependencies:
agent-base: 7.1.4
@@ -18361,8 +18521,14 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.2
+ mime-db@1.52.0: {}
+
mime-db@1.54.0: {}
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
@@ -19820,6 +19986,8 @@ snapshots:
proxy-from-env@1.1.0: {}
+ proxy-from-env@2.1.0: {}
+
punycode.js@2.3.1: {}
qs@6.16.0:
@@ -20538,6 +20706,8 @@ snapshots:
srvx@1.0.5: {}
+ stack-trace@1.0.0-pre2: {}
+
stackback@0.0.2: {}
standard-as-callback@2.1.0: {}
@@ -21304,7 +21474,7 @@ snapshots:
optionalDependencies:
vite: 8.3.4(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)
- vitepress@1.6.4(@algolia/client-search@5.59.0)(@types/node@26.6.5)(fuse.js@7.5.0)(lightningcss@1.33.0)(postcss@8.5.30)(sass-embedded@1.104.1)(sass@1.104.1)(search-insights@2.17.3)(terser@5.51.2)(typescript@6.0.3):
+ vitepress@1.6.4(@algolia/client-search@5.59.0)(@types/node@26.6.5)(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(fuse.js@7.5.0)(lightningcss@1.33.0)(postcss@8.5.30)(sass-embedded@1.104.1)(sass@1.104.1)(search-insights@2.17.3)(terser@5.51.2)(typescript@6.0.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.59.0)(search-insights@2.17.3)
@@ -21317,7 +21487,7 @@ snapshots:
'@vue/devtools-api': 7.7.10
'@vue/shared': 3.5.43
'@vueuse/core': 12.8.2(typescript@6.0.3)
- '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(fuse.js@7.5.0)(typescript@6.0.3)
+ '@vueuse/integrations': 12.8.2(axios@1.20.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(focus-trap@7.8.0)(fuse.js@7.5.0)(typescript@6.0.3)
focus-trap: 7.8.0
mark.js: 8.11.1
minisearch: 7.2.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 05ab0260b..a4e1709d4 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -161,6 +161,8 @@ catalog:
vitepress: ^1.6.4
# Testing
+ "@codspeed/vitest-plugin": ^6.0.0
+ tinybench: ^2.9.0
vitest: ^4.1.11
vitest-browser-svelte: ^3.1.0
diff --git a/test/bundle.test.ts b/test/bundle.test.ts
index 7da943b9a..52349dcc1 100644
--- a/test/bundle.test.ts
+++ b/test/bundle.test.ts
@@ -67,7 +67,7 @@ describe('package bundle size', { timeout: 60_000 }, () => {
"@comark/react": "38.0k (76 files)",
"@comark/svelte": "45.8k (84 files)",
"@comark/vue": "56.2k (80 files)",
- "comark": "384k (152 files)",
+ "comark": "385k (152 files)",
}
`)
})
diff --git a/tsconfig.json b/tsconfig.json
index ac6b823c6..34d34f21c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,6 +13,6 @@
"allowImportingTsExtensions": true,
"noEmit": true
},
- "include": ["packages/**/*"],
+ "include": ["packages/**/*", "benchmarks/*.bench.ts", "benchmarks/fixtures.ts", "benchmarks/vitest.config.ts"],
"exclude": ["playground", "examples", "node_modules", "dist", "**/dist", "**/node_modules"]
}