diff --git a/.changeset/cold-mirrors-joke.md b/.changeset/cold-mirrors-joke.md new file mode 100644 index 000000000000..efdee39399e5 --- /dev/null +++ b/.changeset/cold-mirrors-joke.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Run astro sync in build mode diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2652a454ee2b..a7ec9c8eaccf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -38,5 +38,11 @@ module.exports = { 'no-console': ['error', { allow: ['warn', 'error', 'info', 'debug'] }], }, }, + { + files: ['benchmark/**/*.js'], + rules: { + 'no-console': 'off', + }, + }, ], }; diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 54b33e9d3c9d..13a2ad39fc6b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,6 +3,7 @@ name: Benchmark on: issue_comment: types: [created] + workflow_dispatch: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} @@ -16,19 +17,14 @@ jobs: permissions: contents: read outputs: - PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }} - PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }} - MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }} - MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }} - strategy: - matrix: - node-version: [16, 18] + PR-BENCH: ${{ steps.benchmark-pr.outputs.BENCH_RESULT }} + MAIN-BENCH: ${{ steps.benchmark-main.outputs.BENCH_RESULT }} steps: + # https://github.com/actions/checkout/issues/331#issuecomment-1438220926 - uses: actions/checkout@v3 with: persist-credentials: false - ref: ${{github.event.pull_request.head.sha}} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: refs/pull/${{ github.event.issue.number }}/head - name: Setup PNPM uses: pnpm/action-setup@v2 @@ -45,13 +41,22 @@ jobs: - name: Build Packages run: pnpm run build + - name: Get bench command + id: bench-command + run: | + benchcmd=$(echo "${{ github.event.comment.body }}" | grep '!bench' | awk -F ' ' '{print $2}') + echo "bench=$benchcmd" >> $GITHUB_OUTPUT + shell: bash + - name: Run benchmark id: benchmark-pr run: | - pnpm run --silent benchmark 2> ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" - echo "$result" + result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }}) + processed=$(node ./benchmark/ci-helper.js "$result") + echo "BENCH_RESULT<> $GITHUB_OUTPUT + echo "### PR Benchmark" >> $GITHUB_OUTPUT + echo "$processed" >> $GITHUB_OUTPUT + echo "BENCHEOF" >> $GITHUB_OUTPUT shell: bash # main benchmark @@ -70,10 +75,12 @@ jobs: - name: Run benchmark id: benchmark-main run: | - pnpm run --silent benchmark 2> ./bench-result.md - result=$(awk '/requests in/' ./bench-result.md) - echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result" - echo "$result" + result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }}) + processed=$(node ./benchmark/ci-helper.js "$result") + echo "BENCH_RESULT<> $GITHUB_OUTPUT + echo "### Main Benchmark" >> $GITHUB_OUTPUT + echo "$processed" >> $GITHUB_OUTPUT + echo "BENCHEOF" >> $GITHUB_OUTPUT shell: bash output-benchmark: @@ -89,12 +96,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pr_number: ${{ github.event.issue.number }} message: | - **Node**: 16 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }} - - --- + ${{ needs.benchmark.outputs.PR-BENCH }} - **Node**: 18 - **PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }} - **MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }} + ${{ needs.benchmark.outputs.MAIN-BENCH }} diff --git a/.github/workflows/check-merge.yml b/.github/workflows/check-merge.yml index 9dc60dfd27e1..98f882a0072b 100644 --- a/.github/workflows/check-merge.yml +++ b/.github/workflows/check-merge.yml @@ -62,12 +62,12 @@ jobs: --header 'content-type: application/json' \ -d '["semver minor"]' - - name: Send PR review if: steps.find-blockers.outputs.found == 'true' - run: | # approve the pull request - curl --request POST \ - --url https://api.github.com/repos/${{github.repository}}/pulls/${{github.event.number}}/reviews \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - -d '{"event":"REQUEST_CHANGES","body":"This PR is blocked because it contains a `minor` changeset. A reviewer will merge this at the next release if approved."}' + uses: peter-evans/create-or-update-comment@v2 + continue-on-error: true + with: + issue-number: ${{ github.event.issue.number }} + body: | + This PR is blocked because it contains a `minor` changeset. A reviewer will merge this at the next release if approved. + edit-mode: replace diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 359dd579b68d..1f6f01e76691 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,6 +1,7 @@ name: Examples astro check on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0290fc8fd828..6f7c2aa2a7a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 06bc83311ace..7565eff4ad6e 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,6 +1,7 @@ name: "Format Code" on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db550275a990..8f686c577411 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: Main Checks on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index b3b90580e951..056bf4e0acce 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -1,6 +1,7 @@ name: Scripts on: + workflow_dispatch: pull_request: branches: - "main" diff --git a/.github/workflows/snapshot-release.yml b/.github/workflows/snapshot-release.yml index 3aea333cde0d..5504978c61ee 100644 --- a/.github/workflows/snapshot-release.yml +++ b/.github/workflows/snapshot-release.yml @@ -1,6 +1,7 @@ name: Create a Snapshot Release on: + workflow_dispatch: issue_comment: types: [created] diff --git a/.gitignore b/.gitignore index df661ac83e9a..00a4a77236c2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ dist/ _site/ scripts/smoke/*-main/ scripts/memory/project/src/pages/ +benchmark/projects/ +benchmark/results/ *.log package-lock.json .turbo/ diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000000..79d63f4da26c --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,5 @@ +# benchmark + +Astro's main benchmark suite. It exposes the `astro-benchmark` CLI command. Run `astro-benchmark --help` to see all available commands! + +If you'd like to understand how the benchmark works, check out the other READMEs in the subfolders. diff --git a/benchmark/bench/README.md b/benchmark/bench/README.md new file mode 100644 index 000000000000..9d3312880bb0 --- /dev/null +++ b/benchmark/bench/README.md @@ -0,0 +1,7 @@ +# bench + +This `bench` folder contains different benchmarking files that you can run via `astro-benchmark `, e.g. `astro-benchmark memory`. Files that start with an underscore are not benchmarking files. + +Benchmarking files will run against a project to measure its performance, and write the results down as JSON in the `results` folder. The `results` folder is gitignored and its result files can be safely deleted if you're not using them. + +You can duplicate `_template.js` to start a new benchmark test. All shared utilities are kept in `_util.js`. diff --git a/benchmark/bench/_template.js b/benchmark/bench/_template.js new file mode 100644 index 000000000000..867ecf13b6c3 --- /dev/null +++ b/benchmark/bench/_template.js @@ -0,0 +1,12 @@ +/** Default project to run for this benchmark if not specified */ +export const defaultProject = 'project-name'; + +/** + * Run benchmark on `projectDir` and write results to `outputFile`. + * Use `console.log` to report the results too. Logs that start with 10 `=` + * and end with 10 `=` will be extracted by CI to display in the PR comment. + * Usually after the first 10 `=` you'll want to add a title like `#### Test`. + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) {} diff --git a/benchmark/bench/_util.js b/benchmark/bench/_util.js new file mode 100644 index 000000000000..b61c79a7813b --- /dev/null +++ b/benchmark/bench/_util.js @@ -0,0 +1,3 @@ +import { createRequire } from 'module'; + +export const astroBin = createRequire(import.meta.url).resolve('astro'); diff --git a/benchmark/bench/memory.js b/benchmark/bench/memory.js new file mode 100644 index 000000000000..1f0d8ab8bc2d --- /dev/null +++ b/benchmark/bench/memory.js @@ -0,0 +1,58 @@ +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { execaCommand } from 'execa'; +import { markdownTable } from 'markdown-table'; +import { astroBin } from './_util.js'; + +/** @typedef {Record} AstroTimerStat */ + +/** Default project to run for this benchmark if not specified */ +export const defaultProject = 'memory-default'; + +/** + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) { + const root = fileURLToPath(projectDir); + const outputFilePath = fileURLToPath(outputFile); + + console.log('Building and benchmarking...'); + await execaCommand(`node --expose-gc --max_old_space_size=256 ${astroBin} build`, { + cwd: root, + stdio: 'inherit', + env: { + ASTRO_TIMER_PATH: outputFilePath, + }, + }); + + console.log('Raw results written to', outputFilePath); + + console.log('Result preview:'); + console.log('='.repeat(10)); + console.log(`#### Memory\n\n`); + console.log(printResult(JSON.parse(await fs.readFile(outputFilePath, 'utf-8')))); + console.log('='.repeat(10)); + + console.log('Done!'); +} + +/** + * @param {AstroTimerStat} output + */ +function printResult(output) { + return markdownTable( + [ + ['', 'Elapsed time (s)', 'Memory used (MB)', 'Final memory (MB)'], + ...Object.entries(output).map(([name, stat]) => [ + name, + (stat.elapsedTime / 1000).toFixed(2), + (stat.heapUsedChange / 1024 / 1024).toFixed(2), + (stat.heapUsedTotal / 1024 / 1024).toFixed(2), + ]), + ], + { + align: ['l', 'r', 'r', 'r'], + } + ); +} diff --git a/benchmark/bench/server-stress.js b/benchmark/bench/server-stress.js new file mode 100644 index 000000000000..6237b2e5f59b --- /dev/null +++ b/benchmark/bench/server-stress.js @@ -0,0 +1,85 @@ +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import autocannon from 'autocannon'; +import { execaCommand } from 'execa'; +import { waitUntilBusy } from 'port-authority'; +import { astroBin } from './_util.js'; + +const port = 4321; + +export const defaultProject = 'server-stress-default'; + +/** + * @param {URL} projectDir + * @param {URL} outputFile + */ +export async function run(projectDir, outputFile) { + const root = fileURLToPath(projectDir); + + console.log('Building...'); + await execaCommand(`${astroBin} build`, { + cwd: root, + stdio: 'inherit', + }); + + console.log('Previewing...'); + const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, { + cwd: root, + stdio: 'inherit', + }); + + console.log('Waiting for server ready...'); + await waitUntilBusy(port, { timeout: 5000 }); + + console.log('Running benchmark...'); + const result = await benchmarkCannon(); + + console.log('Killing server...'); + if (!previewProcess.kill('SIGTERM')) { + console.warn('Failed to kill server process id:', previewProcess.pid); + } + + console.log('Writing results to', fileURLToPath(outputFile)); + await fs.writeFile(outputFile, JSON.stringify(result, null, 2)); + + console.log('Result preview:'); + console.log('='.repeat(10)); + console.log(`#### Server stress\n\n`); + let text = autocannon.printResult(result); + // Truncate the logs in CI so that the generated comment from the `!bench` command + // is shortened. Also we only need this information when comparing runs. + // Full log example: https://github.com/mcollina/autocannon#command-line + if (process.env.CI) { + text = text.match(/^.*?requests in.*?read$/m)?.[0]; + } + console.log(text); + console.log('='.repeat(10)); + + console.log('Done!'); +} + +/** + * @returns {Promise} + */ +async function benchmarkCannon() { + return new Promise((resolve, reject) => { + const instance = autocannon( + { + url: `http://localhost:${port}`, + connections: 100, + duration: 30, + pipelining: 10, + }, + (err, result) => { + if (err) { + reject(err); + } else { + // @ts-expect-error untyped but documented + instance.stop(); + resolve(result); + } + } + ); + autocannon.track(instance, { renderResultsTable: false }); + }); +} diff --git a/benchmark/ci-helper.js b/benchmark/ci-helper.js new file mode 100644 index 000000000000..2dbdf5acf195 --- /dev/null +++ b/benchmark/ci-helper.js @@ -0,0 +1,13 @@ +// This script helps extract the benchmark logs that are between the `==========` lines. +// They are a convention defined in the `./bench/_template.js` file, which are used to log +// out with the `!bench` command. See `/.github/workflows/benchmark.yml` to see how it's used. +const benchLogs = process.argv[2]; +const resultRegex = /==========(.*?)==========/gs; + +let processedLog = ''; +let m; +while ((m = resultRegex.exec(benchLogs))) { + processedLog += m[1] + '\n'; +} + +console.log(processedLog); diff --git a/benchmark/index.js b/benchmark/index.js new file mode 100755 index 000000000000..6ac76759c029 --- /dev/null +++ b/benchmark/index.js @@ -0,0 +1,79 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { pathToFileURL } from 'url'; +import mri from 'mri'; + +const args = mri(process.argv.slice(2)); + +if (args.help || args.h) { + console.log(`\ +astro-benchmark [options] + +Command + [empty] Run all benchmarks + memory Run build memory and speed test + server-stress Run server stress test + +Options + --project Project to use for benchmark, see benchmark/make-project/ for available names + --output Output file to write results to +`); + process.exit(0); +} + +const commandName = args._[0]; +const benchmarks = { + memory: () => import('./bench/memory.js'), + 'server-stress': () => import('./bench/server-stress.js'), +}; + +if (commandName && !(commandName in benchmarks)) { + console.error(`Invalid benchmark name: ${commandName}`); + process.exit(1); +} + +if (commandName) { + // Run single benchmark + const bench = benchmarks[commandName]; + const benchMod = await bench(); + const projectDir = await makeProject(args.project || benchMod.defaultProject); + const outputFile = await getOutputFile(commandName); + await benchMod.run(projectDir, outputFile); +} else { + // Run all benchmarks + for (const name in benchmarks) { + const bench = benchmarks[name]; + const benchMod = await bench(); + const projectDir = await makeProject(args.project || benchMod.defaultProject); + const outputFile = await getOutputFile(name); + await benchMod.run(projectDir, outputFile); + } +} + +async function makeProject(name) { + console.log('Making project:', name); + const projectDir = new URL(`./projects/${name}/`, import.meta.url); + + const makeProjectMod = await import(`./make-project/${name}.js`); + await makeProjectMod.run(projectDir); + + console.log('Finished making project:', name); + return projectDir; +} + +/** + * @param {string} benchmarkName + */ +async function getOutputFile(benchmarkName) { + let file; + if (args.output) { + file = pathToFileURL(path.resolve(args.output)); + } else { + file = new URL(`./results/${benchmarkName}-bench-${Date.now()}.json`, import.meta.url); + } + + // Prepare output file directory + await fs.mkdir(new URL('./', file), { recursive: true }); + + return file; +} diff --git a/benchmark/make-project/README.md b/benchmark/make-project/README.md new file mode 100644 index 000000000000..3199d1b7a747 --- /dev/null +++ b/benchmark/make-project/README.md @@ -0,0 +1,7 @@ +# make-project + +This `make-project` folder contains different files to programmatically create a new Astro project. They are created inside the `projects` folder and are gitignored. These projects are used by benchmarks for testing. + +Each benchmark can specify the default project to run in its `defaultProject` export, but it can be overriden if `--project ` is passed through the CLI. + +You can duplicate `_template.js` to start a new project script. All shared utilities are kept in `_util.js`. diff --git a/benchmark/make-project/_template.js b/benchmark/make-project/_template.js new file mode 100644 index 000000000000..00ebdc4737b7 --- /dev/null +++ b/benchmark/make-project/_template.js @@ -0,0 +1,6 @@ +/** + * Create a new project in the `projectDir` directory. Make sure to clean up the + * previous artifacts here before generating files. + * @param {URL} projectDir + */ +export async function run(projectDir) {} diff --git a/benchmark/make-project/_util.js b/benchmark/make-project/_util.js new file mode 100644 index 000000000000..c0e17965b022 --- /dev/null +++ b/benchmark/make-project/_util.js @@ -0,0 +1,2 @@ +export const loremIpsum = + "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."; diff --git a/benchmark/make-project/memory-default.js b/benchmark/make-project/memory-default.js new file mode 100644 index 000000000000..021a42b0f21b --- /dev/null +++ b/benchmark/make-project/memory-default.js @@ -0,0 +1,59 @@ +import fs from 'fs/promises'; +import { loremIpsum } from './_util.js'; + +/** + * @param {URL} projectDir + */ +export async function run(projectDir) { + await fs.rm(projectDir, { recursive: true, force: true }); + await fs.mkdir(new URL('./src/pages/blog', projectDir), { recursive: true }); + await fs.mkdir(new URL('./src/content/blog', projectDir), { recursive: true }); + + const promises = []; + + for (let i = 0; i < 100; i++) { + const content = `\ +--- +const i = ${i}; +--- + +{i} +`; + promises.push( + fs.writeFile(new URL(`./src/pages/page-${i}.astro`, projectDir), content, 'utf-8') + ); + } + + for (let i = 0; i < 100; i++) { + const content = `\ +# Article ${i} + +${loremIpsum} +`; + promises.push( + fs.writeFile(new URL(`./src/content/blog/article-${i}.md`, projectDir), content, 'utf-8') + ); + } + + await fs.writeFile( + new URL(`./src/pages/blog/[...slug].astro`, projectDir), + `\ +--- +import { getCollection } from 'astro:content'; +export async function getStaticPaths() { + const blogEntries = await getCollection('blog'); + return blogEntries.map(entry => ({ + params: { slug: entry.slug }, props: { entry }, + })); +} +const { entry } = Astro.props; +const { Content } = await entry.render(); +--- +

{entry.data.title}

+ +`, + 'utf-8' + ); + + await Promise.all(promises); +} diff --git a/benchmark/make-project/server-stress-default.js b/benchmark/make-project/server-stress-default.js new file mode 100644 index 000000000000..c7ff6b2b4ee3 --- /dev/null +++ b/benchmark/make-project/server-stress-default.js @@ -0,0 +1,47 @@ +import fs from 'fs/promises'; +import { loremIpsum } from './_util.js'; + +/** + * @param {URL} projectDir + */ +export async function run(projectDir) { + await fs.rm(projectDir, { recursive: true, force: true }); + await fs.mkdir(new URL('./src/pages', projectDir), { recursive: true }); + + await fs.writeFile( + new URL('./src/pages/index.astro', projectDir), + `\ +--- +const content = "${loremIpsum}" +--- + + + + + + + Astro + + +

Astro

+
+ ${Array.from({ length: 60 }).map(() => '

{content}

')} +
+ +`, + 'utf-8' + ); + + await fs.writeFile( + new URL('./astro.config.js', projectDir), + `\ +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; + +export default defineConfig({ + output: 'server', + adapter: nodejs({ mode: 'standalone' }), +});`, + 'utf-8' + ); +} diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 000000000000..4233cbba91fb --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,18 @@ +{ + "name": "astro-benchmark", + "private": true, + "type": "module", + "version": "0.0.0", + "bin": { + "astro-benchmark": "./index.js" + }, + "dependencies": { + "@astrojs/node": "workspace:*", + "astro": "workspace:*", + "autocannon": "^7.10.0", + "execa": "^6.1.0", + "markdown-table": "^3.0.3", + "mri": "^1.2.0", + "port-authority": "^2.0.1" + } +} diff --git a/package.json b/package.json index f119d34d0598..7ca43f566a8c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:vite-ci": "turbo run test --filter=astro --output-logs=new-only --no-deps --concurrency=1", "test:e2e": "cd packages/astro && pnpm playwright install && pnpm run test:e2e", "test:e2e:match": "cd packages/astro && pnpm playwright install && pnpm run test:e2e:match", - "benchmark": "pnpm --filter @benchmark/simple run build && pnpm dlx concurrently -k -s first --raw \"node packages/astro/test/benchmark/simple/server.mjs\" \"pnpm dlx autocannon -c 100 -d 30 -p 10 localhost:3002/\"", + "benchmark": "astro-benchmark", "lint": "eslint --cache .", "version": "changeset version && pnpm install --no-frozen-lockfile && pnpm run format", "preinstall": "npx only-allow pnpm" @@ -76,7 +76,8 @@ } }, "dependencies": { - "@astrojs/webapi": "workspace:*" + "@astrojs/webapi": "workspace:*", + "astro-benchmark": "workspace:*" }, "devDependencies": { "@changesets/changelog-github": "0.4.4", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 868ba7fc2aef..199250d21efa 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -16,6 +16,7 @@ import type { z } from 'zod'; import type { SerializedSSRManifest } from '../core/app/types'; import type { PageBuildData } from '../core/build/types'; import type { AstroConfigSchema } from '../core/config'; +import type { AstroTimer } from '../core/config/timer'; import type { AstroCookies } from '../core/cookies'; import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js'; @@ -992,6 +993,7 @@ export interface AstroSettings { tsConfigPath: string | undefined; watchFiles: string[]; forceDisableTelemetry: boolean; + timer: AstroTimer; } export type AsyncRendererComponentFn = ( diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 206bed6e417e..85a0b6536810 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -167,6 +167,9 @@ class AstroBuilder { buildMode: this.settings.config.output, }); } + + // Benchmark results + this.settings.timer.writeStats(); } /** Build the given Astro project. */ diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 1a6d3e367924..2e39128f533a 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -33,6 +33,8 @@ export async function staticBuild(opts: StaticBuildOptions) { throw new AstroError(AstroErrorData.NoAdapterInstalled); } + settings.timer.start('SSR build'); + // The pages to be built for rendering purposes. const pageInput = new Set(); @@ -43,10 +45,6 @@ export async function staticBuild(opts: StaticBuildOptions) { // Build internals needed by the CSS plugin const internals = createBuildInternals(); - const timer: Record = {}; - - timer.buildStart = performance.now(); - for (const [component, pageData] of Object.entries(allPages)) { const astroModuleURL = new URL('./' + component, settings.config.root); const astroModuleId = prependForwardSlash(component); @@ -70,10 +68,13 @@ export async function staticBuild(opts: StaticBuildOptions) { registerAllPlugins(container); // Build your project (SSR application code, assets, client JS, etc.) - timer.ssr = performance.now(); + const ssrTime = performance.now(); info(opts.logging, 'build', `Building ${settings.config.output} entrypoints...`); const ssrOutput = await ssrBuild(opts, internals, pageInput, container); - info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`)); + info(opts.logging, 'build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`)); + + settings.timer.end('SSR build'); + settings.timer.start('Client build'); const rendererClientEntrypoints = settings.renderers .map((r) => r.clientEntrypoint) @@ -91,23 +92,27 @@ export async function staticBuild(opts: StaticBuildOptions) { } // Run client build first, so the assets can be fed into the SSR rendered version. - timer.clientBuild = performance.now(); const clientOutput = await clientBuild(opts, internals, clientInput, container); - timer.generate = performance.now(); await runPostBuildHooks(container, ssrOutput, clientOutput); + settings.timer.end('Client build'); + switch (settings.config.output) { case 'static': { + settings.timer.start('Static generate'); await generatePages(opts, internals); await cleanServerOutput(opts); + settings.timer.end('Static generate'); return; } case 'server': { + settings.timer.start('Server generate'); await generatePages(opts, internals); await cleanStaticOutput(opts, internals); info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`); await ssrMoveAssets(opts); + settings.timer.end('Server generate'); return; } } diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index c9ac9da5eb67..05e3da1c3745 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -5,6 +5,7 @@ import { fileURLToPath, pathToFileURL } from 'url'; import jsxRenderer from '../../jsx/renderer.js'; import { createDefaultDevConfig } from './config.js'; import { loadTSConfig } from './tsconfig.js'; +import { AstroTimer } from './timer.js'; export function createBaseSettings(config: AstroConfig): AstroSettings { return { @@ -19,6 +20,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { scripts: [], watchFiles: [], forceDisableTelemetry: false, + timer: new AstroTimer(), }; } diff --git a/packages/astro/src/core/config/timer.ts b/packages/astro/src/core/config/timer.ts new file mode 100644 index 000000000000..7360b55103ee --- /dev/null +++ b/packages/astro/src/core/config/timer.ts @@ -0,0 +1,65 @@ +import fs from 'fs'; + +// Type used by `bench-memory.js` +export interface Stat { + elapsedTime: number; + heapUsedChange: number; + heapUsedTotal: number; +} + +interface OngoingStat { + startTime: number; + startHeap: number; +} + +/** + * Timer to track certain operations' performance. Used by Astro's scripts only. + * Set `process.env.ASTRO_TIMER_PATH` truthy to enable. + */ +export class AstroTimer { + private enabled: boolean; + private ongoingTimers: Map = new Map(); + private stats: Record = {}; + + constructor() { + this.enabled = !!process.env.ASTRO_TIMER_PATH; + } + + /** + * Start a timer for a scope with a given name. + */ + start(name: string) { + if (!this.enabled) return; + globalThis.gc?.(); + this.ongoingTimers.set(name, { + startTime: performance.now(), + startHeap: process.memoryUsage().heapUsed, + }); + } + + /** + * End a timer for a scope with a given name. + */ + end(name: string) { + if (!this.enabled) return; + const stat = this.ongoingTimers.get(name); + if (!stat) return; + globalThis.gc?.(); + const endHeap = process.memoryUsage().heapUsed; + this.stats[name] = { + elapsedTime: performance.now() - stat.startTime, + heapUsedChange: endHeap - stat.startHeap, + heapUsedTotal: endHeap, + }; + this.ongoingTimers.delete(name); + } + + /** + * Write stats to `process.env.ASTRO_TIMER_PATH` + */ + writeStats() { + if (!this.enabled) return; + // @ts-expect-error + fs.writeFileSync(process.env.ASTRO_TIMER_PATH, JSON.stringify(this.stats, null, 2)); + } +} diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 7d83e731ccda..385b8a9f0804 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -30,7 +30,7 @@ interface CreateViteOptions { settings: AstroSettings; logging: LogOptions; mode: 'dev' | 'build' | string; - // will be undefined when using `sync` + // will be undefined when using `getViteConfig` command?: 'dev' | 'build'; fs?: typeof nodeFs; } @@ -180,32 +180,28 @@ export async function createVite( // We also need to filter out the plugins that are not meant to be applied to the current command: // - If the command is `build`, we filter out the plugins that are meant to be applied for `serve`. // - If the command is `dev`, we filter out the plugins that are meant to be applied for `build`. - if (command) { - let plugins = settings.config.vite?.plugins; - if (plugins) { - const { plugins: _, ...rest } = settings.config.vite; - const applyToFilter = command === 'build' ? 'serve' : 'build'; - const applyArgs = [ - { ...settings.config.vite, mode }, - { command, mode }, - ]; - // @ts-expect-error ignore TS2589: Type instantiation is excessively deep and possibly infinite. - plugins = plugins.flat(Infinity).filter((p) => { - if (!p || p?.apply === applyToFilter) { - return false; - } - - if (typeof p.apply === 'function') { - return p.apply(applyArgs[0], applyArgs[1]); - } + if (command && settings.config.vite?.plugins) { + let { plugins, ...rest } = settings.config.vite; + const applyToFilter = command === 'build' ? 'serve' : 'build'; + const applyArgs = [ + { ...settings.config.vite, mode }, + { command, mode }, + ]; + // @ts-expect-error ignore TS2589: Type instantiation is excessively deep and possibly infinite. + plugins = plugins.flat(Infinity).filter((p) => { + if (!p || p?.apply === applyToFilter) { + return false; + } - return true; - }); + if (typeof p.apply === 'function') { + return p.apply(applyArgs[0], applyArgs[1]); + } - result = vite.mergeConfig(result, { ...rest, plugins }); - } else { - result = vite.mergeConfig(result, settings.config.vite || {}); - } + return true; + }); + result = vite.mergeConfig(result, { ...rest, plugins }); + } else { + result = vite.mergeConfig(result, settings.config.vite || {}); } result = vite.mergeConfig(result, commandConfig); if (result.plugins) { diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index ae27cdb0feca..be8f84d2a77b 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -39,7 +39,7 @@ export async function sync( optimizeDeps: { entries: [] }, logLevel: 'silent', }, - { settings, logging, mode: 'build', fs } + { settings, logging, mode: 'build', command: 'build', fs } ) ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 825dd93f5d4b..cd498df0f5c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,7 @@ importers: '@types/node': ^18.7.21 '@typescript-eslint/eslint-plugin': ^5.27.1 '@typescript-eslint/parser': ^5.27.1 + astro-benchmark: workspace:* del: ^7.0.0 esbuild: ^0.15.18 eslint: ^8.17.0 @@ -38,6 +39,7 @@ importers: typescript: ~4.7.3 dependencies: '@astrojs/webapi': link:packages/webapi + astro-benchmark: link:benchmark devDependencies: '@changesets/changelog-github': 0.4.4 '@changesets/cli': 2.23.0_kcozqtpxuwjzskw6zg5royevn4 @@ -61,6 +63,24 @@ importers: turbo: 1.2.5 typescript: 4.7.4 + benchmark: + specifiers: + '@astrojs/node': workspace:* + astro: workspace:* + autocannon: ^7.10.0 + execa: ^6.1.0 + markdown-table: ^3.0.3 + mri: ^1.2.0 + port-authority: ^2.0.1 + dependencies: + '@astrojs/node': link:../packages/integrations/node + astro: link:../packages/astro + autocannon: 7.10.0 + execa: 6.1.0 + markdown-table: 3.0.3 + mri: 1.2.0 + port-authority: 2.0.1 + examples/basics: specifiers: astro: ^2.0.16 @@ -3878,6 +3898,10 @@ packages: leven: 3.1.0 dev: false + /@assemblyscript/loader/0.19.23: + resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==} + dev: false + /@astro-community/astro-embed-integration/0.1.2_astro@packages+astro: resolution: {integrity: sha512-ONBDHkOUZ7ssQNzRc5XRZtBBJR0zC68Gm2FCm5w6fxxciDkRkU9Zn9BSssgaNrLPfsXycxFLtQZT3dX9ZPsAxw==} peerDependencies: @@ -5632,6 +5656,13 @@ packages: mime: 3.0.0 dev: true + /@colors/colors/1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: false + optional: true + /@csstools/postcss-cascade-layers/1.1.1_postcss@8.4.21: resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==} engines: {node: ^12 || ^14 || >=16} @@ -8139,11 +8170,44 @@ packages: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} dev: false + /autocannon/7.10.0: + resolution: {integrity: sha512-PY1UrXL4NHE7J0hA6GGN2r8xjiAePS/bii3Hz7NOvp4JO3xDNBgRftDjfAxj1t6FDWXiXEOuKF/pdDiisIS8ZA==} + hasBin: true + dependencies: + chalk: 4.1.2 + char-spinner: 1.0.1 + cli-table3: 0.6.3 + color-support: 1.1.3 + cross-argv: 2.0.0 + form-data: 4.0.0 + has-async-hooks: 1.0.0 + hdr-histogram-js: 3.0.0 + hdr-histogram-percentiles-obj: 3.0.0 + http-parser-js: 0.5.8 + hyperid: 3.1.1 + lodash.chunk: 4.2.0 + lodash.clonedeep: 4.5.0 + lodash.flatten: 4.4.0 + manage-path: 2.0.0 + on-net-listen: 1.1.2 + pretty-bytes: 5.6.0 + progress: 2.0.3 + reinterval: 1.1.0 + retimer: 3.0.0 + semver: 7.3.8 + subarg: 1.0.0 + timestring: 6.0.0 + dev: false + /autoprefixer/10.4.13_postcss@8.4.21: resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} @@ -8524,6 +8588,10 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: false + /char-spinner/1.0.1: + resolution: {integrity: sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==} + dev: false + /character-entities-html4/2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -8622,6 +8690,15 @@ packages: engines: {node: '>=6'} dev: false + /cli-table3/0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + dev: false + /cliui/6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: @@ -8696,6 +8773,13 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: false + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens/2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false @@ -8774,6 +8858,10 @@ packages: resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==} dev: true + /cross-argv/2.0.0: + resolution: {integrity: sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==} + dev: false + /cross-spawn/5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -9046,6 +9134,11 @@ packages: slash: 4.0.0 dev: true + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /delegates/1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false @@ -10030,6 +10123,15 @@ packages: dependencies: is-callable: 1.2.7 + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /format/0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -10376,6 +10478,10 @@ packages: engines: {node: '>=6'} dev: true + /has-async-hooks/1.0.0: + resolution: {integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==} + dev: false + /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -10579,6 +10685,19 @@ packages: space-separated-tokens: 2.0.2 dev: false + /hdr-histogram-js/3.0.0: + resolution: {integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==} + engines: {node: '>=14'} + dependencies: + '@assemblyscript/loader': 0.19.23 + base64-js: 1.5.1 + pako: 1.0.11 + dev: false + + /hdr-histogram-percentiles-obj/3.0.0: + resolution: {integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==} + dev: false + /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -10629,6 +10748,10 @@ packages: statuses: 2.0.1 toidentifier: 1.0.1 + /http-parser-js/0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: false + /http-proxy-agent/4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} @@ -10662,6 +10785,13 @@ packages: resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} engines: {node: '>=12.20.0'} + /hyperid/3.1.1: + resolution: {integrity: sha512-RveV33kIksycSf7HLkq1sHB5wW0OwuX8ot8MYnY++gaaPXGFfKpBncHrAWxdpuEeRlazUMGWefwP1w6o6GaumA==} + dependencies: + uuid: 8.3.2 + uuid-parse: 1.1.0 + dev: false + /iconv-lite/0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -11265,10 +11395,22 @@ packages: dependencies: p-locate: 5.0.0 + /lodash.chunk/4.2.0: + resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} + dev: false + + /lodash.clonedeep/4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + /lodash.debounce/4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: false + /lodash.flatten/4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: false + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -11372,6 +11514,10 @@ packages: semver: 6.3.0 dev: false + /manage-path/2.0.0: + resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==} + dev: false + /map-obj/1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -11974,14 +12120,12 @@ packages: /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mime/1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -12082,6 +12226,10 @@ packages: /minimist/1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + /minipass/3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -12381,6 +12529,11 @@ packages: ee-first: 1.1.1 dev: false + /on-net-listen/1.1.2: + resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} + engines: {node: '>=9.4.0 || ^8.9.4'} + dev: false + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -12553,6 +12706,10 @@ packages: netmask: 2.0.2 dev: true + /pako/1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -12723,6 +12880,10 @@ packages: playwright-core: 1.30.0 dev: true + /port-authority/2.0.1: + resolution: {integrity: sha512-Hz/WvSNt5+7x+Rq1Cn6DetJOZxKtLDehJ1mLCYge6ju4QvSF/PHvRgy94e1SKJVI96AJTcqEdNwkkaAFad+TXQ==} + dev: false + /postcss-attribute-case-insensitive/5.0.2_postcss@8.4.21: resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} engines: {node: ^12 || ^14 || >=16} @@ -13227,6 +13388,11 @@ packages: engines: {node: '>=6'} dev: false + /progress/2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: false + /prompts/2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -13529,6 +13695,10 @@ packages: unified: 10.1.2 dev: false + /reinterval/1.1.0: + resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} + dev: false + /remark-code-titles/0.1.2: resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==} dependencies: @@ -13691,6 +13861,10 @@ packages: unified: 10.1.2 dev: false + /retimer/3.0.0: + resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==} + dev: false + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -14334,6 +14508,12 @@ packages: inline-style-parser: 0.1.1 dev: false + /subarg/1.0.0: + resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} + dependencies: + minimist: 1.2.8 + dev: false + /suf-log/2.5.3: resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} dependencies: @@ -14513,6 +14693,11 @@ packages: engines: {node: '>=6'} dev: false + /timestring/6.0.0: + resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} + engines: {node: '>=8'} + dev: false + /tiny-glob/0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} dependencies: @@ -15087,6 +15272,15 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /uuid-parse/1.1.0: + resolution: {integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==} + dev: false + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /uvu/0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 937aa21ca887..fb7deebf6742 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,4 @@ packages: - 'examples/**/*' - 'smoke/**/*' - 'scripts' + - 'benchmark'