diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9d534c..de43009 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,4 +26,9 @@ jobs: - run: pnpm build - run: pnpm vitest --coverage - run: pnpm tsc --noEmit + - name: Run benchmarks + if: matrix.os == 'ubuntu-latest' + uses: CodSpeedHQ/action@v3 + with: + run: pnpm bench - uses: codecov/codecov-action@v5 diff --git a/benchmark/fixtures/large-vite-manifest.json b/benchmark/fixtures/large-vite-manifest.json new file mode 100644 index 0000000..1764160 --- /dev/null +++ b/benchmark/fixtures/large-vite-manifest.json @@ -0,0 +1,151 @@ +{ + "entry-client.ts": { + "file": "assets/entry-client.js", + "src": "entry-client.ts", + "isEntry": true, + "imports": [ + "_vendor.js" + ], + "css": ["assets/main.css", "assets/components.css"], + "dynamicImports": [ + "pages/home.vue", + "pages/about.vue", + "pages/contact.vue", + "pages/blog.vue", + "pages/profile.vue" + ], + "assets": [ + "assets/logo.svg", + "assets/banner.jpg" + ] + }, + "_vendor.js": { + "file": "assets/vendor.js" + }, + "pages/home.vue": { + "file": "assets/home.js", + "src": "pages/home.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js", + "entry-client.ts" + ], + "css": [ + "assets/home.css" + ], + "assets": [ + "assets/hero-bg.jpg" + ] + }, + "pages/about.vue": { + "file": "assets/about.js", + "src": "pages/about.vue", + "isDynamicEntry": true, + "imports": [ + "entry-client.ts" + ], + "dynamicImports": [ + "components/Timeline.vue", + "components/TeamMember.vue" + ], + "css": [ + "assets/about.css" + ] + }, + "pages/contact.vue": { + "file": "assets/contact.js", + "src": "pages/contact.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js", + "entry-client.ts" + ], + "css": [ + "assets/contact.css", + "assets/forms.css" + ] + }, + "pages/blog.vue": { + "file": "assets/blog.js", + "src": "pages/blog.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "dynamicImports": [ + "components/BlogPost.vue", + "components/Pagination.vue", + "components/SearchBox.vue" + ], + "css": [ + "assets/blog.css" + ] + }, + "pages/profile.vue": { + "file": "assets/profile.js", + "src": "pages/profile.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js", + "entry-client.ts" + ], + "css": [ + "assets/profile.css" + ] + }, + "components/Timeline.vue": { + "file": "assets/timeline.js", + "src": "components/Timeline.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "css": [ + "assets/timeline.css" + ] + }, + "components/TeamMember.vue": { + "file": "assets/team-member.js", + "src": "components/TeamMember.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "css": [ + "assets/team-member.css" + ] + }, + "components/BlogPost.vue": { + "file": "assets/blog-post.js", + "src": "components/BlogPost.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "css": [ + "assets/blog-post.css" + ] + }, + "components/Pagination.vue": { + "file": "assets/pagination.js", + "src": "components/Pagination.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "css": [ + "assets/pagination.css" + ] + }, + "components/SearchBox.vue": { + "file": "assets/search-box.js", + "src": "components/SearchBox.vue", + "isDynamicEntry": true, + "imports": [ + "_vendor.js" + ], + "css": [ + "assets/search-box.css" + ] + } +} diff --git a/benchmark/fixtures/large-webpack-manifest.json b/benchmark/fixtures/large-webpack-manifest.json new file mode 100644 index 0000000..941da4f --- /dev/null +++ b/benchmark/fixtures/large-webpack-manifest.json @@ -0,0 +1,84 @@ +{ + "publicPath": "/_nuxt/", + "all": [ + "runtime.js", + "commons/app.js", + "commons/vendor.js", + "commons/polyfills.js", + "app.css", + "app.js", + "pages/home.css", + "pages/home.js", + "pages/about.css", + "pages/about.js", + "pages/contact.css", + "pages/contact.js", + "pages/blog.css", + "pages/blog.js", + "pages/profile.css", + "pages/profile.js", + "components/timeline.css", + "components/timeline.js", + "components/team-member.css", + "components/team-member.js", + "components/blog-post.css", + "components/blog-post.js", + "components/pagination.css", + "components/pagination.js", + "components/search-box.css", + "components/search-box.js", + "assets/main.css", + "assets/forms.css", + "img/logo.svg", + "img/hero-bg.jpg", + "img/banner.jpg", + "fonts/inter.woff2", + "fonts/inter-bold.woff2" + ], + "initial": [ + "runtime.js", + "commons/polyfills.js", + "commons/vendor.js", + "commons/app.js", + "app.css", + "assets/main.css", + "app.js" + ], + "async": [ + "pages/home.css", + "pages/home.js", + "pages/about.css", + "pages/about.js", + "pages/contact.css", + "pages/contact.js", + "pages/blog.css", + "pages/blog.js", + "pages/profile.css", + "pages/profile.js", + "components/timeline.css", + "components/timeline.js", + "components/team-member.css", + "components/team-member.js", + "components/blog-post.css", + "components/blog-post.js", + "components/pagination.css", + "components/pagination.js", + "components/search-box.css", + "components/search-box.js", + "assets/forms.css" + ], + "modules": { + "4d87aad8": [1, 6], + "630f1d84": [2, 6], + "56940b2e": [7, 8, 26, 28], + "ab12cd34": [9, 10, 16], + "ef56gh78": [11, 12, 27], + "ij90kl12": [13, 14], + "mn34op56": [15, 16], + "qr78st90": [17, 18], + "uv12wx34": [19, 20], + "yz56ab78": [21, 22], + "cd90ef12": [23, 24], + "gh34ij56": [25, 26] + } +} diff --git a/benchmark/utils.bench.ts b/benchmark/utils.bench.ts new file mode 100644 index 0000000..390fe2c --- /dev/null +++ b/benchmark/utils.bench.ts @@ -0,0 +1,143 @@ +import { bench, describe } from 'vitest' +import { isJS, isCSS, getAsType, parseResource } from '../src/utils' +import { extname } from 'node:path' + +// Sample file names for testing +const jsFiles = [ + 'app.js', + 'vendor.mjs', + 'chunk.cjs', + 'module.js?v=123', + 'script', +] + +const cssFiles = [ + 'main.css', + 'components.scss', + 'styles.less', + 'theme.stylus', + 'layout.css?v=456', +] + +const assetFiles = [ + 'logo.svg', + 'banner.jpg', + 'icon.png', + 'font.woff2', + 'video.mp4', + 'audio.mp3', + 'document.pdf', +] + +const mixedFiles = [...jsFiles, ...cssFiles, ...assetFiles] + +describe('File Type Detection Benchmarks', () => { + bench('isJS detection on JS files', () => { + for (const file of jsFiles) { + isJS(file) + } + }) + + bench('isJS detection on mixed files', () => { + for (const file of mixedFiles) { + isJS(file) + } + }) + + bench('isCSS detection on CSS files', () => { + for (const file of cssFiles) { + isCSS(file) + } + }) + + bench('isCSS detection on mixed files', () => { + for (const file of mixedFiles) { + isCSS(file) + } + }) +}) + +describe('Asset Type Detection Benchmarks', () => { + bench('getAsType on mixed files', () => { + for (const file of mixedFiles) { + const base = file.split('?', 1)[0] + const ext = extname(base).slice(1) + getAsType(ext) + } + }) + + bench('getAsType on JS extensions', () => { + const extensions = ['js', 'mjs', 'cjs'] + for (const ext of extensions) { + getAsType(ext) + } + }) + + bench('getAsType on CSS extensions', () => { + const extensions = ['css', 'scss', 'less', 'stylus'] + for (const ext of extensions) { + getAsType(ext) + } + }) +}) + +describe('Resource Parsing Benchmarks', () => { + bench('parseResource on JS files', () => { + for (const file of jsFiles) { + parseResource(file) + } + }) + + bench('parseResource on CSS files', () => { + for (const file of cssFiles) { + parseResource(file) + } + }) + + bench('parseResource on asset files', () => { + for (const file of assetFiles) { + parseResource(file) + } + }) + + bench('parseResource on mixed files', () => { + for (const file of mixedFiles) { + parseResource(file) + } + }) + + bench('parseResource on mixed files (1000 iterations)', () => { + for (let i = 0; i < 1000; i++) { + for (const file of mixedFiles) { + parseResource(file) + } + } + }) +}) + +// Test with dynamically generated file names +describe('Dynamic File Generation Benchmarks', () => { + bench('parseResource on generated JS files', () => { + for (let i = 0; i < 100; i++) { + parseResource(`chunk-${i}.js`) + parseResource(`module-${i}.mjs`) + parseResource(`bundle-${i}.cjs`) + } + }) + + bench('parseResource on generated CSS files', () => { + for (let i = 0; i < 100; i++) { + parseResource(`styles-${i}.css`) + parseResource(`theme-${i}.scss`) + parseResource(`layout-${i}.less`) + } + }) + + bench('parseResource on generated assets', () => { + for (let i = 0; i < 100; i++) { + parseResource(`image-${i}.png`) + parseResource(`icon-${i}.svg`) + parseResource(`font-${i}.woff2`) + } + }) +}) diff --git a/benchmark/vite.bench.ts b/benchmark/vite.bench.ts new file mode 100644 index 0000000..fbb2d1d --- /dev/null +++ b/benchmark/vite.bench.ts @@ -0,0 +1,131 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { bench, describe } from 'vitest' +import { normalizeViteManifest } from '../src/vite' +import type { Manifest as ViteManifest } from 'vite' + +// Load test fixtures +const smallViteManifest = JSON.parse( + readFileSync(resolve(__dirname, '../test/fixtures/vite-manifest.json'), 'utf-8'), +) as ViteManifest + +const largeViteManifest = JSON.parse( + readFileSync(resolve(__dirname, 'fixtures/large-vite-manifest.json'), 'utf-8'), +) as ViteManifest + +describe('Vite Manifest Normalization Benchmarks', () => { + bench('normalize small Vite manifest', () => { + normalizeViteManifest(smallViteManifest) + }) + + bench('normalize large Vite manifest', () => { + normalizeViteManifest(largeViteManifest) + }) + + // Create a very complex manifest with deep nesting + const createComplexViteManifest = (): ViteManifest => { + const manifest: ViteManifest = { + 'main.ts': { + file: 'main.js', + src: 'main.ts', + isEntry: true, + imports: ['_vendor.js', '_polyfills.js'], + css: ['main.css', 'global.css'], + dynamicImports: [], + assets: ['logo.svg', 'favicon.ico'], + }, + } + + // Create nested structure + for (let i = 0; i < 20; i++) { + const pageKey = `pages/page-${i}.vue` + manifest[pageKey] = { + file: `pages/page-${i}.js`, + src: pageKey, + isDynamicEntry: true, + imports: ['_vendor.js'], + css: [`pages/page-${i}.css`], + dynamicImports: [], + assets: [`pages/assets/bg-${i}.jpg`], + } + + // Add components for each page + for (let j = 0; j < 5; j++) { + const componentKey = `components/page-${i}/comp-${j}.vue` + manifest[componentKey] = { + file: `components/page-${i}/comp-${j}.js`, + src: componentKey, + isDynamicEntry: true, + imports: ['_vendor.js'], + css: [`components/page-${i}/comp-${j}.css`], + } + manifest[pageKey].dynamicImports!.push(componentKey) + } + + manifest['main.ts'].dynamicImports!.push(pageKey) + } + + return manifest + } + + const complexViteManifest = createComplexViteManifest() + + bench('normalize complex nested Vite manifest', () => { + normalizeViteManifest(complexViteManifest) + }) +}) + +// Benchmark with different manifest sizes +describe('Vite Manifest Size Scaling', () => { + // Generate manifests of different sizes + const generateManifest = (entryCount: number): ViteManifest => { + const manifest: ViteManifest = {} + + // Add main entry + manifest['main.ts'] = { + file: 'main.js', + src: 'main.ts', + isEntry: true, + imports: ['_vendor.js'], + css: ['main.css'], + dynamicImports: [], + assets: [], + } + + // Add entries + for (let i = 0; i < entryCount; i++) { + const key = `page-${i}.vue` + manifest[key] = { + file: `page-${i}.js`, + src: key, + isDynamicEntry: true, + imports: ['_vendor.js'], + css: [`page-${i}.css`], + assets: [`asset-${i}.png`], + } + manifest['main.ts'].dynamicImports!.push(key) + } + + return manifest + } + + const manifest5 = generateManifest(5) + bench('normalize 5 entries', () => { + normalizeViteManifest(manifest5) + }) + + const manifest25 = generateManifest(25) + bench('normalize 25 entries', () => { + normalizeViteManifest(manifest25) + }) + + const manifest50 = generateManifest(50) + bench('normalize 50 entries', () => { + normalizeViteManifest(manifest50) + }) + + const manifest100 = generateManifest(100) + bench('normalize 100 entries', () => { + normalizeViteManifest(manifest100) + }) +}) diff --git a/benchmark/webpack.bench.ts b/benchmark/webpack.bench.ts new file mode 100644 index 0000000..4ad3201 --- /dev/null +++ b/benchmark/webpack.bench.ts @@ -0,0 +1,86 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { bench, describe } from 'vitest' +import { normalizeWebpackManifest, type WebpackClientManifest } from '../src/webpack' + +// Load test fixtures +const smallWebpackManifest = JSON.parse( + readFileSync(resolve(__dirname, '../test/fixtures/webpack-manifest.json'), 'utf-8'), +) as WebpackClientManifest + +const largeWebpackManifest = JSON.parse( + readFileSync(resolve(__dirname, 'fixtures/large-webpack-manifest.json'), 'utf-8'), +) as WebpackClientManifest + +describe('Webpack Manifest Normalization Benchmarks', () => { + bench('normalize small Webpack manifest', () => { + normalizeWebpackManifest(smallWebpackManifest) + }) + + bench('normalize large Webpack manifest', () => { + normalizeWebpackManifest(largeWebpackManifest) + }) + + bench('normalize small Webpack manifest (100 iterations)', () => { + for (let i = 0; i < 100; i++) { + normalizeWebpackManifest(smallWebpackManifest) + } + }) + + bench('normalize large Webpack manifest (10 iterations)', () => { + for (let i = 0; i < 10; i++) { + normalizeWebpackManifest(largeWebpackManifest) + } + }) +}) + +// Benchmark with different manifest sizes +describe('Webpack Manifest Size Scaling', () => { + // Generate manifests of different sizes + const generateManifest = (entryCount: number): WebpackClientManifest => { + const all: string[] = ['runtime.js', 'commons/app.js', 'app.css', 'app.js'] + const async: string[] = [] + const modules: Record = {} + + // Add entries + for (let i = 0; i < entryCount; i++) { + const jsFile = `pages/page-${i}.js` + const cssFile = `pages/page-${i}.css` + const assetFile = `assets/asset-${i}.png` + + all.push(jsFile, cssFile, assetFile) + async.push(jsFile, cssFile) + + // Create module mapping + modules[`module-${i}`] = [all.length - 3, all.length - 2] // JS and CSS indices + } + + return { + publicPath: '/_nuxt/', + all, + initial: ['runtime.js', 'commons/app.js', 'app.css', 'app.js'], + async, + modules, + } + } + + const manifest5 = generateManifest(5) + bench('normalize 5 entries', () => { + normalizeWebpackManifest(manifest5) + }) + + const manifest25 = generateManifest(25) + bench('normalize 25 entries', () => { + normalizeWebpackManifest(manifest25) + }) + + const manifest50 = generateManifest(50) + bench('normalize 50 entries', () => { + normalizeWebpackManifest(manifest50) + }) + + const manifest100 = generateManifest(100) + bench('normalize 100 entries', () => { + normalizeWebpackManifest(manifest100) + }) +}) diff --git a/package.json b/package.json index d00e2a8..e854003 100644 --- a/package.json +++ b/package.json @@ -29,22 +29,25 @@ "lint": "eslint src", "prepack": "unbuild", "release": "pnpm test && pnpm build && changelogen --release --push && npm publish", - "test": "pnpm lint && pnpm vitest run --coverage && tsc --noEmit" + "test": "pnpm lint && pnpm vitest run --coverage && tsc --noEmit", + "bench": "vitest bench" }, "dependencies": { "ufo": "^1.6.1" }, "devDependencies": { + "@codspeed/vitest-plugin": "^3.1.1", + "@nuxt/eslint-config": "^1.9.0", "@types/node": "^22.18.0", "@vitest/coverage-v8": "^3.2.4", "changelogen": "^0.6.2", "eslint": "^9.34.0", + "std-env": "^3.9.0", "typescript": "^5.9.2", "unbuild": "^3.6.1", "vite": "^7.1.3", "vitest": "3.2.4", - "vue": "3.5.20", - "@nuxt/eslint-config": "^1.9.0" + "vue": "3.5.20" }, "packageManager": "pnpm@10.15.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 034b1c7..af50d72 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,9 @@ importers: specifier: ^1.6.1 version: 1.6.1 devDependencies: + '@codspeed/vitest-plugin': + specifier: ^3.1.1 + version: 3.1.1(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1))(vitest@3.2.4(@types/node@22.18.0)(jiti@2.5.1)) '@nuxt/eslint-config': specifier: ^1.9.0 version: 1.9.0(@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(@vue/compiler-sfc@3.5.20)(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) @@ -27,6 +30,9 @@ importers: eslint: specifier: ^9.34.0 version: 9.34.0(jiti@2.5.1) + std-env: + specifier: ^3.9.0 + version: 3.9.0 typescript: specifier: ^5.9.2 version: 5.9.2 @@ -83,6 +89,15 @@ packages: '@clack/prompts@0.11.0': resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@codspeed/core@3.1.1': + resolution: {integrity: sha512-ONhERVDAtkm0nc+FYPivDozoMOlNUP2BWRBFDJYATGA18Iap5Kd2mZ1/Lwz54RB5+g+3YDOpsvotHa4hd3Q+7Q==} + + '@codspeed/vitest-plugin@3.1.1': + resolution: {integrity: sha512-/PJUgxIfuRqpBSbaD8bgWXtbXxCqgnW89dzr3220fMkx/LA6z6oUb4tJGjeVsOWAzAgu0VBdSA+8hC+7D9BIuQ==} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + vitest: '>=1.2.2' + '@emnapi/core@1.4.5': resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} @@ -826,6 +841,9 @@ packages: ast-v8-to-istanbul@0.3.4: resolution: {integrity: sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -833,6 +851,9 @@ packages: peerDependencies: postcss: ^8.1.0 + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -874,6 +895,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -928,6 +953,10 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -1048,6 +1077,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -1068,6 +1101,10 @@ packages: resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1084,9 +1121,25 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} @@ -1280,6 +1333,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -1290,10 +1347,23 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1305,6 +1375,14 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} @@ -1332,6 +1410,10 @@ packages: resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1339,6 +1421,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + 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.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1498,6 +1588,10 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -1523,6 +1617,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -1537,6 +1635,14 @@ 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-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1603,6 +1709,10 @@ packages: node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -1636,10 +1746,18 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1660,6 +1778,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1893,6 +2015,9 @@ packages: resolution: {integrity: sha512-285/jRCYIbMGDciDdrw0KPNC4LKEEwz/bwErcYNxSJOi4CpGUuLpb9gQpg3XJP0XYj9ldSRluXxih4lX2YN8Xw==} engines: {node: '>=20'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2269,6 +2394,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + snapshots: '@ampproject/remapping@2.3.0': @@ -2314,6 +2443,23 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 + '@codspeed/core@3.1.1': + dependencies: + axios: 1.11.0 + find-up: 6.3.0 + form-data: 4.0.4 + node-gyp-build: 4.8.4 + transitivePeerDependencies: + - debug + + '@codspeed/vitest-plugin@3.1.1(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1))(vitest@3.2.4(@types/node@22.18.0)(jiti@2.5.1))': + dependencies: + '@codspeed/core': 3.1.1 + vite: 7.1.3(@types/node@22.18.0)(jiti@2.5.1) + vitest: 3.2.4(@types/node@22.18.0)(jiti@2.5.1) + transitivePeerDependencies: + - debug + '@emnapi/core@1.4.5': dependencies: '@emnapi/wasi-threads': 1.0.4 @@ -3010,6 +3156,8 @@ snapshots: estree-walker: 3.0.3 js-tokens: 9.0.1 + asynckit@0.4.0: {} + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.3 @@ -3020,6 +3168,14 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} boolbase@1.0.0: {} @@ -3069,6 +3225,11 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} caniuse-api@3.0.0: @@ -3137,6 +3298,10 @@ snapshots: colord@2.9.3: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@11.1.0: {} comment-parser@1.4.1: {} @@ -3260,6 +3425,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + destr@2.0.5: {} dom-serializer@2.0.0: @@ -3282,6 +3449,12 @@ snapshots: dotenv@17.2.1: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.208: {} @@ -3292,8 +3465,23 @@ snapshots: entities@4.5.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + 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.2 + esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9 @@ -3557,6 +3745,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.18 @@ -3570,11 +3763,21 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.11: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fsevents@2.3.3: @@ -3582,6 +3785,24 @@ snapshots: function-bind@1.1.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -3616,10 +3837,18 @@ snapshots: globals@16.3.0: {} + gopd@1.2.0: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -3752,6 +3981,10 @@ snapshots: dependencies: p-locate: 5.0.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} @@ -3776,6 +4009,8 @@ snapshots: dependencies: semver: 7.7.2 + math-intrinsics@1.1.0: {} + mdn-data@2.0.28: {} mdn-data@2.12.2: {} @@ -3787,6 +4022,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + min-indent@1.0.1: {} minimatch@10.0.3: @@ -3841,6 +4082,8 @@ snapshots: node-fetch-native@1.6.7: {} + node-gyp-build@4.8.4: {} + node-releases@2.0.19: {} normalize-range@0.1.2: {} @@ -3885,10 +4128,18 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.1 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + package-json-from-dist@1.0.1: {} package-manager-detector@1.3.0: {} @@ -3905,6 +4156,8 @@ snapshots: path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -4116,6 +4369,8 @@ snapshots: pretty-bytes@7.0.1: {} + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} quansync@0.2.11: {} @@ -4538,3 +4793,5 @@ snapshots: xml-name-validator@4.0.0: {} yocto-queue@0.1.0: {} + + yocto-queue@1.2.1: {} diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000..9bc2214 --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' +import codspeed from '@codspeed/vitest-plugin' +import { isCI } from 'std-env' + +export default defineConfig({ + test: { + coverage: { + include: ['src/**'], + }, + }, + plugins: isCI ? [codspeed()] : [], +})