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 ![alt](https://example.com/img.png). + +### 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 + + + +\`\`\` + +\`\`\`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 @@ [![npm version](https://img.shields.io/npm/v/comark?color=black)](https://npmx.dev/comark) [![npm downloads](https://img.shields.io/npm/dm/comark?color=black)](https://npm.chart.dev/comark) [![CI](https://img.shields.io/github/actions/workflow/status/comarkdown/comark/ci.yml?branch=main&color=black)](https://github.com/comarkdown/comark/actions/workflows/ci.yml) +[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://app.codspeed.io/comarkdown/comark) [![Documentation](https://img.shields.io/badge/Documentation-black?logo=readme&logoColor=white)](https://comark.dev) [![license](https://img.shields.io/github/license/comarkdown/comark?color=black)](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) - -![Image](https://example.com/image.png) -`, - }, - { - 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"] }