From 899a1df493928272de99980148e97c02c45ebd06 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 31 Jul 2026 14:47:40 +0200 Subject: [PATCH 1/4] ci: add CodSpeed benchmark workflow --- .github/workflows/codspeed.yml | 54 ++++++++ package.json | 1 + pnpm-lock.yaml | 228 +++++++++++++++++++++++++++++++-- pnpm-workspace.yaml | 5 + vitest.config.ts | 4 + 5 files changed, 282 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/codspeed.yml diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 0000000000..11f973937d --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,54 @@ +name: codspeed + +on: + push: + branches: + - v4 + pull_request: + branches: + - v4 + # `workflow_dispatch` allows CodSpeed to trigger backtest + # performance analysis in order to generate initial data. + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + id-token: write # for OpenID Connect authentication with CodSpeed + +jobs: + benchmarks: + runs-on: ubuntu-latest + + env: + NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v6 + + - name: Install node + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Prepare + run: pnpm run dev:prepare + + - name: Run benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: pnpm run bench diff --git a/package.json b/package.json index c288fff369..b3f8843c76 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "vue-component-type-helpers": "^3.3.8" }, "devDependencies": { + "@codspeed/vitest-plugin": "^5.7.1", "@nuxt/eslint-config": "^1.16.0", "@nuxt/module-builder": "^1.0.3", "@nuxt/test-utils": "^4.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea8d05de25..b8c1cf0125 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: + '@codspeed/vitest-plugin>vite': ^7.0.0 || ^8.0.0 '@nuxt/kit': ^4.5.1 '@nuxt/ui': workspace:* unplugin: ^3.3.0 @@ -24,7 +25,7 @@ importers: version: 5.0.1(vue@3.5.40(typescript@6.0.3)) '@inertiajs/vue3': specifier: ^2.0.7 || ^3.0.0 - version: 3.1.1(vue@3.5.40(typescript@6.0.3)) + version: 3.1.1(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(vue@3.5.40(typescript@6.0.3)) '@internationalized/date': specifier: ^3.12.2 version: 3.12.2 @@ -123,7 +124,7 @@ importers: version: 14.3.0(vue@3.5.40(typescript@6.0.3)) '@vueuse/integrations': specifier: ^14.3.0 - version: 14.3.0(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3)) + version: 14.3.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3)) '@vueuse/shared': specifier: ^14.3.0 version: 14.3.0(vue@3.5.40(typescript@6.0.3)) @@ -239,6 +240,9 @@ importers: specifier: ^3.24.0 || ^4.0.0 version: 4.4.3 devDependencies: + '@codspeed/vitest-plugin': + specifier: ^5.7.1 + version: 5.7.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)(tinybench@2.9.0)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.0)(happy-dom@20.11.1)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0))) '@nuxt/eslint-config': specifier: ^1.16.0 version: 1.16.0(@typescript-eslint/utils@8.61.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.40)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) @@ -379,7 +383,7 @@ importers: version: 2.0.0(b5ac78be9495707b91a6e3028f89212f) '@vueuse/integrations': specifier: ^14.3.0 - version: 14.3.0(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3)) + version: 14.3.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3)) '@vueuse/nuxt': specifier: ^14.3.0 version: 14.3.0(9552aa5b84030b376195d1193cc0364f) @@ -807,6 +811,16 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} + '@codspeed/core@5.7.1': + resolution: {integrity: sha512-7mrFkqaY14rXu3XHv3o7bK3btLK0LFRxfeK3bQC8dLduI/Ty9eq8Hv8rx04FAY/onQbRTx3rUUQg5xprYHpATQ==} + + '@codspeed/vitest-plugin@5.7.1': + resolution: {integrity: sha512-yKNqaYVxZZPJYkhVHpz30Oem4TUgcI+/IRDD9YHvcAgi0P42CL6TB/+qYjuFOWghFzTPrm3TUbNFoBsevSTbUA==} + peerDependencies: + tinybench: '>=2.9.0' + vite: ^7.0.0 || ^8.0.0 + vitest: ^3.2 || ^4 + '@colordx/core@5.4.3': resolution: {integrity: sha512-kIxYSfA5T8HXjav55UaaH/o/cKivF6jCCGIb8eqtcsfI46wsvlSiT8jMDyrl779qLec3c2c2oHBZo4oAhvbjrQ==} @@ -4150,6 +4164,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'} @@ -4259,6 +4277,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.5.4: resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==} engines: {node: ^10 || ^12 || >=14} @@ -4274,6 +4295,9 @@ packages: resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} engines: {node: '>=4'} + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} + b4a@1.8.1: resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: @@ -4585,6 +4609,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==} @@ -4915,6 +4943,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'} @@ -5163,6 +5195,10 @@ packages: 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'} + es-toolkit@1.46.1: resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} @@ -5488,6 +5524,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==} @@ -5506,6 +5546,15 @@ packages: fnv1a-64@0.1.1: resolution: {integrity: sha512-qMpkqWyaiNxwYG7Dt40Bxtp2wV6KW/1woXVJOEvR1KA2ffsZf8pyvlNwJgFHha3teh8iC3d4arvrJ0qTcU445A==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + fontaine@0.8.0: resolution: {integrity: sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg==} engines: {node: '>=18.12.0'} @@ -5531,6 +5580,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'} @@ -5702,6 +5755,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hast-util-embedded@3.0.0: resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} @@ -5802,6 +5859,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'} @@ -6269,6 +6330,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-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} @@ -6522,10 +6587,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'} @@ -6954,10 +7027,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} + pac-proxy-agent@7.2.0: resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} engines: {node: '>= 14'} @@ -7026,6 +7107,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'} @@ -7538,6 +7623,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'} + pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -8032,6 +8121,10 @@ packages: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} + 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==} @@ -9098,6 +9191,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -9392,6 +9489,27 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} + '@codspeed/core@5.7.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)': + dependencies: + axios: 1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) + find-up: 6.3.0 + form-data: 4.0.6 + node-gyp-build: 4.8.4 + stack-trace: 1.0.0-pre2 + transitivePeerDependencies: + - debug + - supports-color + + '@codspeed/vitest-plugin@5.7.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)(tinybench@2.9.0)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.0)(happy-dom@20.11.1)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0)))': + dependencies: + '@codspeed/core': 5.7.1(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) + tinybench: 2.9.0 + vite: 8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.0)(happy-dom@20.11.1)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0)) + transitivePeerDependencies: + - debug + - supports-color + '@colordx/core@5.4.3': {} '@comark/vue@0.5.1(shiki@4.3.1)(vue@3.5.40(typescript@6.0.3))': @@ -10003,17 +10121,19 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inertiajs/core@3.1.1': + '@inertiajs/core@3.1.1(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))': dependencies: '@jridgewell/trace-mapping': 0.3.31 es-toolkit: 1.46.1 - laravel-precognition: 2.0.0 + laravel-precognition: 2.0.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)) + optionalDependencies: + axios: 1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) - '@inertiajs/vue3@3.1.1(vue@3.5.40(typescript@6.0.3))': + '@inertiajs/vue3@3.1.1(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(vue@3.5.40(typescript@6.0.3))': dependencies: - '@inertiajs/core': 3.1.1 + '@inertiajs/core': 3.1.1(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)) es-toolkit: 1.46.1 - laravel-precognition: 2.0.0 + laravel-precognition: 2.0.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)) vue: 3.5.40(typescript@6.0.3) transitivePeerDependencies: - axios @@ -12712,12 +12832,13 @@ snapshots: '@vueuse/shared': 14.3.0(vue@3.5.40(typescript@6.0.3)) vue: 3.5.40(typescript@6.0.3) - '@vueuse/integrations@14.3.0(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3))': + '@vueuse/integrations@14.3.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2))(change-case@5.4.4)(fuse.js@7.5.0)(sortablejs@1.15.7)(vue@3.5.40(typescript@6.0.3))': dependencies: '@vueuse/core': 14.3.0(vue@3.5.40(typescript@6.0.3)) '@vueuse/shared': 14.3.0(vue@3.5.40(typescript@6.0.3)) vue: 3.5.40(typescript@6.0.3) optionalDependencies: + axios: 1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) change-case: 5.4.4 fuse.js: 7.5.0 sortablejs: 1.15.7 @@ -12779,6 +12900,12 @@ snapshots: acorn@8.16.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: {} @@ -12890,6 +13017,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + autoprefixer@10.5.4(postcss@8.5.23): dependencies: browserslist: 4.28.7 @@ -12905,6 +13034,16 @@ snapshots: axe-core@4.11.4: {} + axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2): + dependencies: + follow-redirects: 1.16.0(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 + b4a@1.8.1: {} bail@2.0.2: {} @@ -13202,6 +13341,10 @@ snapshots: optionalDependencies: shiki: 4.3.1 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} commander@10.0.1: {} @@ -13541,6 +13684,8 @@ snapshots: esprima: 4.0.1 quickjs-wasi: 0.0.1 + delayed-stream@1.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} @@ -13761,6 +13906,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.3 + es-toolkit@1.46.1: {} esbuild@0.25.12: @@ -14230,6 +14382,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.21 @@ -14247,6 +14404,10 @@ snapshots: fnv1a-64@0.1.1: {} + follow-redirects@1.16.0(debug@4.4.3(supports-color@10.2.2)): + optionalDependencies: + debug: 4.4.3(supports-color@10.2.2) + fontaine@0.8.0(esbuild@0.27.7)(rolldown@1.2.0)(rollup@4.60.4)(vite@8.1.5(@types/node@25.9.0)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(yaml@2.9.0)): dependencies: '@capsizecss/unpack': 4.0.0 @@ -14325,6 +14486,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: {} @@ -14513,6 +14682,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hast-util-embedded@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -14704,6 +14877,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 @@ -15039,9 +15219,11 @@ snapshots: knitwork@1.3.0: {} - laravel-precognition@2.0.0: + laravel-precognition@2.0.0(axios@1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2)): dependencies: es-toolkit: 1.46.1 + optionalDependencies: + axios: 1.19.0(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2) launch-editor@2.13.2: dependencies: @@ -15162,6 +15344,10 @@ snapshots: dependencies: p-locate: 5.0.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + lodash-es@4.18.1: {} lodash.capitalize@4.2.1: {} @@ -15626,8 +15812,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 @@ -16429,10 +16621,18 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + pac-proxy-agent@7.2.0(supports-color@10.2.2): dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 @@ -16526,6 +16726,8 @@ snapshots: path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -17046,6 +17248,8 @@ snapshots: proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} + pump@3.0.4: dependencies: end-of-stream: 1.4.5 @@ -17789,6 +17993,8 @@ snapshots: stable-hash-x@0.2.0: {} + stack-trace@1.0.0-pre2: {} + stackback@0.0.2: {} standard-as-callback@2.1.0: {} @@ -18933,6 +19139,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + yoctocolors@2.1.2: {} youch-core@0.3.3: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c9ff777e19..4fe0f6809c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,6 +11,11 @@ ignoreWorkspaceRootCheck: true linkWorkspacePackages: true overrides: + # `@codspeed/vitest-plugin` caps its `vite` peer at ^7 while the repo resolves + # vite 8 for the Nuxt modules; without widening it, pnpm's peer dedupe re-keys + # the whole vite peer group down to vitest's vite 7. The plugin only ever runs + # inside vitest's own vite, so the wider range is bookkeeping, not runtime. + "@codspeed/vitest-plugin>vite": ^7.0.0 || ^8.0.0 "@nuxt/kit": ^4.5.1 "@nuxt/ui": workspace:* unplugin: ^3.3.0 diff --git a/vitest.config.ts b/vitest.config.ts index 287911270a..3799616416 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url' import { defineVitestProject } from '@nuxt/test-utils/config' import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' +import codspeedPlugin from '@codspeed/vitest-plugin' import ui from './src/vite' import { glob } from 'tinyglobby' @@ -51,6 +52,9 @@ export default defineConfig({ setupFiles: ['./test/utils/setup.ts'] }, plugins: [ + // Instruments benchmarks when running under the CodSpeed runner in CI, + // inactive for a local `pnpm bench`. + codspeedPlugin(), vue(), ui({ dts: false }), { From bf762c88adc1d19156fa1a9d10f7eb9683dd555b Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 31 Jul 2026 14:55:24 +0200 Subject: [PATCH 2/4] ci: run benchmarks from module.yml --- .github/workflows/codspeed.yml | 54 ---------------------------------- .github/workflows/module.yml | 37 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/codspeed.yml diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml deleted file mode 100644 index 11f973937d..0000000000 --- a/.github/workflows/codspeed.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: codspeed - -on: - push: - branches: - - v4 - pull_request: - branches: - - v4 - # `workflow_dispatch` allows CodSpeed to trigger backtest - # performance analysis in order to generate initial data. - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read - id-token: write # for OpenID Connect authentication with CodSpeed - -jobs: - benchmarks: - runs-on: ubuntu-latest - - env: - NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }} - - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Install pnpm - uses: pnpm/action-setup@v6 - - - name: Install node - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install - - - name: Prepare - run: pnpm run dev:prepare - - - name: Run benchmarks - uses: CodSpeedHQ/action@v4 - with: - mode: simulation - run: pnpm run bench diff --git a/.github/workflows/module.yml b/.github/workflows/module.yml index 8ce394f2e0..ef31e33f3b 100644 --- a/.github/workflows/module.yml +++ b/.github/workflows/module.yml @@ -190,3 +190,40 @@ jobs: - name: Typecheck run: pnpm run typecheck + + benchmarks: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # for OpenID Connect authentication with CodSpeed + + env: + NUXT_GITHUB_TOKEN: ${{ secrets.NUXT_GITHUB_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install pnpm + uses: pnpm/action-setup@v6 + + - name: Install node + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Prepare + run: pnpm run dev:prepare + + - name: Run benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: pnpm run bench From ea4efc123b5fb78516266bf6a93b001195264dbd Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 31 Jul 2026 14:56:04 +0200 Subject: [PATCH 3/4] ci: skip benchmarks on forks --- .github/workflows/module.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/module.yml b/.github/workflows/module.yml index ef31e33f3b..254b9f3213 100644 --- a/.github/workflows/module.yml +++ b/.github/workflows/module.yml @@ -194,6 +194,9 @@ jobs: benchmarks: runs-on: ubuntu-latest + # Skip forks of the repo, they can't authenticate with CodSpeed + if: github.repository_owner == 'nuxt' + permissions: contents: read id-token: write # for OpenID Connect authentication with CodSpeed From 6b2cd9bcfbe496f902997d1faad7d688ed7ea15c Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 31 Jul 2026 15:04:26 +0200 Subject: [PATCH 4/4] test(bench): mount re-render wrappers inside the bench function --- test/bench/button-link.bench.ts | 12 ++++-------- test/bench/components.bench.ts | 23 +++++++---------------- test/bench/link-stack.bench.ts | 12 ++++-------- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/test/bench/button-link.bench.ts b/test/bench/button-link.bench.ts index d2acaa3329..d15a680a4e 100644 --- a/test/bench/button-link.bench.ts +++ b/test/bench/button-link.bench.ts @@ -33,18 +33,14 @@ describe('mount', () => { // ends in the initial state), re-rendering the whole subtree. function reRenderBench(name: string, comp: any, props: Record = {}) { describe(`re-render: ${name}`, () => { - let wrapper: Awaited> + let wrapper: Awaited> | undefined + // Mounted lazily on the first call: CodSpeed's analysis runner invokes the + // bench function without tinybench's `setup`/`teardown` options. bench(name, async () => { + wrapper ??= await mountSuspended(comp, { props }) await wrapper.setProps({ loading: true }) await wrapper.setProps({ loading: false }) - }, { - async setup() { - wrapper = await mountSuspended(comp, { props }) - }, - teardown() { - wrapper?.unmount() - } }) }) } diff --git a/test/bench/components.bench.ts b/test/bench/components.bench.ts index 3b03ece7f7..8cb5f3e57b 100644 --- a/test/bench/components.bench.ts +++ b/test/bench/components.bench.ts @@ -23,18 +23,15 @@ describe('Button mount', () => { // its subtree. Each iteration performs a full on/off cycle so the work measured // is deterministic and always ends back in the initial `false` state. describe('Button re-render (variant prop change)', () => { - let wrapper: Awaited> + let wrapper: Awaited> | undefined + // Mounted lazily on the first call: CodSpeed's analysis runner invokes the + // bench function without tinybench's `setup`/`teardown` options, so a mount + // there never happens under instrumentation. The warmup pass absorbs it. bench('toggle loading', async () => { + wrapper ??= await mountSuspended(Button, { props: { label: 'Button' } }) await wrapper.setProps({ loading: true }) await wrapper.setProps({ loading: false }) - }, { - async setup() { - wrapper = await mountSuspended(Button, { props: { label: 'Button' } }) - }, - teardown() { - wrapper?.unmount() - } }) }) @@ -73,16 +70,10 @@ describe('Table mount (200 x 5)', () => { }) describe('Table re-render (new data identity)', () => { - let wrapper: Awaited> + let wrapper: Awaited> | undefined bench('set data', async () => { + wrapper ??= await mountSuspended(Table, { props: tableProps(makeData(200)) }) await wrapper.setProps({ data: makeData(200) }) - }, { - async setup() { - wrapper = await mountSuspended(Table, { props: tableProps(makeData(200)) }) - }, - teardown() { - wrapper?.unmount() - } }) }) diff --git a/test/bench/link-stack.bench.ts b/test/bench/link-stack.bench.ts index 32594cc0eb..0a62a18b09 100644 --- a/test/bench/link-stack.bench.ts +++ b/test/bench/link-stack.bench.ts @@ -45,18 +45,14 @@ describe('mount', () => { describe('re-render', () => { for (const [name, render] of CASES) { describe(name, () => { - let wrapper: Awaited> + let wrapper: Awaited> | undefined + // Mounted lazily on the first call: CodSpeed's analysis runner invokes the + // bench function without tinybench's `setup`/`teardown` options. bench(name, async () => { + wrapper ??= await mountSuspended(makeParent(render), { props: { cls: 'p-2' } }) await wrapper.setProps({ cls: 'p-3' }) await wrapper.setProps({ cls: 'p-2' }) - }, { - async setup() { - wrapper = await mountSuspended(makeParent(render), { props: { cls: 'p-2' } }) - }, - teardown() { - wrapper?.unmount() - } }) }) }