From 614e447de85a2104051d314faa3b62587aaa7181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 6 Aug 2024 17:40:30 +0800 Subject: [PATCH 1/8] feat: add quick benchmark --- .../src/bench => benchmark/client}/App.vue | 4 +- .../src/bench => benchmark/client}/data.ts | 0 benchmark/client/index.html | 12 ++ benchmark/client/index.ts | 4 + .../bench => benchmark/client}/profiling.ts | 31 ++- benchmark/index.js | 196 ++++++++++++++++++ benchmark/package.json | 19 ++ benchmark/tsconfig.json | 26 +++ playground/setup/dev.js | 4 +- scripts/utils.js | 12 +- 10 files changed, 288 insertions(+), 20 deletions(-) rename {playground/src/bench => benchmark/client}/App.vue (96%) rename {playground/src/bench => benchmark/client}/data.ts (100%) create mode 100644 benchmark/client/index.html create mode 100644 benchmark/client/index.ts rename {playground/src/bench => benchmark/client}/profiling.ts (70%) create mode 100644 benchmark/index.js create mode 100644 benchmark/package.json create mode 100644 benchmark/tsconfig.json diff --git a/playground/src/bench/App.vue b/benchmark/client/App.vue similarity index 96% rename from playground/src/bench/App.vue rename to benchmark/client/App.vue index a67ef8754..97482030d 100644 --- a/playground/src/bench/App.vue +++ b/benchmark/client/App.vue @@ -1,5 +1,5 @@ - + + diff --git a/benchmark/client/index.ts b/benchmark/client/index.ts new file mode 100644 index 000000000..7fe6db3f4 --- /dev/null +++ b/benchmark/client/index.ts @@ -0,0 +1,4 @@ +import { createVaporApp } from '@vue/vapor' +import App from './App.vue' + +createVaporApp(App).mount('#app') diff --git a/playground/src/bench/profiling.ts b/benchmark/client/profiling.ts similarity index 70% rename from playground/src/bench/profiling.ts rename to benchmark/client/profiling.ts index 256b0bd1c..d1a8c2c12 100644 --- a/playground/src/bench/profiling.ts +++ b/benchmark/client/profiling.ts @@ -1,25 +1,32 @@ -// @ts-expect-error +/* eslint-disable no-console */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-restricted-globals */ + +declare module globalThis { + let doProfile: boolean + let times: Record +} + globalThis.doProfile = false -// const defer = nextTick -const ric = - typeof requestIdleCallback === 'undefined' ? setTimeout : requestIdleCallback -export const defer = () => new Promise(r => ric(r)) +export const defer = () => new Promise(r => requestIdleCallback(r)) -const times: Record = {} +const times: Record = (globalThis.times = {}) -export const wrap = ( +export function wrap( id: string, fn: (...args: any[]) => any, -): ((...args: any[]) => Promise) => { - if (import.meta.env.PROD) return fn +): (...args: any[]) => Promise { return async (...args) => { const btns = Array.from( document.querySelectorAll('#control button'), ) + const timeEl = document.getElementById('time')! + timeEl.classList.remove('done') for (const node of btns) { node.disabled = true } - const doProfile = (globalThis as any).doProfile + + const { doProfile } = globalThis await defer() doProfile && console.profile(id) @@ -33,6 +40,7 @@ export const wrap = ( Math.floor(prevTimes.length / 2) ] const mean = prevTimes.reduce((a, b) => a + b, 0) / prevTimes.length + const msg = `${id}: min: ${Math.min(...prevTimes).toFixed(2)} / ` + `max: ${Math.max(...prevTimes).toFixed(2)} / ` + @@ -43,7 +51,8 @@ export const wrap = ( `over ${prevTimes.length} runs` doProfile && console.profileEnd(id) console.log(msg) - document.getElementById('time')!.textContent = msg + timeEl.textContent = msg + timeEl.classList.add('done') for (const node of btns) { node.disabled = false diff --git a/benchmark/index.js b/benchmark/index.js new file mode 100644 index 000000000..6edf6c91f --- /dev/null +++ b/benchmark/index.js @@ -0,0 +1,196 @@ +/* eslint-disable no-restricted-syntax */ + +// @ts-check +import path from 'node:path' +import connect from 'connect' +import Vue from '@vitejs/plugin-vue' +import { build } from 'vite' +import { exec } from '../scripts/utils.js' +import sirv from 'sirv' +import { launch } from 'puppeteer' + +const PORT = 8193 + +await buildVapor() +await buildApp() +const server = startServer() +await bench() +server.close() + +process.on('SIGTERM', () => { + server.close() +}) + +async function buildVapor() { + console.info('Building Vapor...') + const options = { + cwd: path.resolve(import.meta.dirname, '..'), + stdio: 'inherit', + } + const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([ + exec( + 'pnpm', + 'run build shared compiler-core compiler-dom compiler-vapor -pf cjs'.split( + ' ', + ), + options, + ), + exec( + 'pnpm', + 'run build compiler-sfc compiler-ssr -f cjs'.split(' '), + options, + ), + exec('pnpm', 'run build vue-vapor -pf esm-browser'.split(' '), options), + ]) + + if (!ok || !ok2 || !ok3) { + console.error('Failed to build') + process.exit(1) + } +} + +async function buildApp() { + console.info('Building app...') + + process.env.NODE_ENV = 'production' + const CompilerSFC = await import( + '../packages/compiler-sfc/dist/compiler-sfc.cjs.js' + ) + const CompilerVapor = await import( + '../packages/compiler-vapor/dist/compiler-vapor.cjs.prod.js' + ) + + const vaporRuntime = path.resolve( + import.meta.dirname, + '../packages/vue-vapor/dist/vue-vapor.esm-browser.prod.js', + ) + await build({ + root: './client', + build: { + minify: 'terser', + }, + resolve: { + alias: { + 'vue/vapor': vaporRuntime, + '@vue/vapor': vaporRuntime, + }, + }, + plugins: [ + Vue({ + compiler: CompilerSFC, + template: { + compiler: /** @type {any} */ (CompilerVapor), + }, + }), + ], + }) +} + +function startServer() { + const server = connect().use(sirv('./client/dist')).listen(PORT) + console.info(`Server started at http://localhost:${PORT}`) + return server +} + +async function bench() { + const disableFeatures = [ + 'Translate', // avoid translation popups + 'PrivacySandboxSettings4', // avoid privacy popup + 'IPH_SidePanelGenericMenuFeature', // bookmark popup see https://github.com/krausest/js-framework-benchmark/issues/1688 + ] + + const args = [ + '--js-flags=--expose-gc', // needed for gc() function + '--no-default-browser-check', + '--disable-sync', + '--no-first-run', + '--ash-no-nudges', + '--disable-extensions', + `--disable-features=${disableFeatures.join(',')}`, + ] + + const browser = await launch({ + // headless: false, + args, + }) + const page = await browser.newPage() + await page.goto(`http://localhost:${PORT}/`, { + waitUntil: 'networkidle0', + }) + + await forceGC() + + const t = performance.now() + for (let i = 0; i < 50; i++) { + await doAction('run') + await doAction('add') + await doAction('update') + await doAction('swaprows') + await doAction('clear') + } + console.info('Total time:', performance.now() - t, 'ms') + + const times = await getTimes() + /** @type {Record} */ + const result = {} + for (const key in times) { + const mean = getMean(times[key]) + const std = getStandardDeviation(times[key]) + result[key] = { mean: mean.toFixed(2), std: std.toFixed(2) } + } + + // eslint-disable-next-line no-console + console.table(result) + + await page.close() + await browser.close() + + /** + * @param {string} id + */ + async function doAction(id) { + await page.click(`#${id}`) + await page.waitForSelector('.done') + } + + function getTimes() { + return page.evaluate(() => /** @type {any} */ (globalThis).times) + } + + async function forceGC() { + await page.evaluate( + "window.gc({type:'major',execution:'sync',flavor:'last-resort'})", + ) + } +} + +/** + * @param {number[]} nums + * @returns {number} + */ +function getMean(nums) { + return ( + nums.reduce( + /** + * @param {number} a + * @param {number} b + * @returns {number} + */ + (a, b) => a + b, + 0, + ) / nums.length + ) +} + +/** + * + * @param {number[]} array + * @returns + */ +function getStandardDeviation(array) { + const n = array.length + const mean = array.reduce((a, b) => a + b) / n + return Math.sqrt( + array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n, + ) +} diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 000000000..583ce1571 --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,19 @@ +{ + "name": "benchmark", + "version": "0.0.0", + "author": "三咲智子 Kevin Deng ", + "license": "MIT", + "type": "module", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@vitejs/plugin-vue": "npm:@vue-vapor/vite-plugin-vue@0.0.0-alpha.4", + "connect": "^3.7.0", + "sirv": "^2.0.4", + "vite": "^5.0.12" + }, + "devDependencies": { + "@types/connect": "^3.4.38" + } +} diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json new file mode 100644 index 000000000..94ab64844 --- /dev/null +++ b/benchmark/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["es2022", "dom"], + "allowJs": true, + "moduleDetection": "force", + "module": "preserve", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "types": ["node", "vite/client"], + "strict": true, + "noUnusedLocals": true, + "declaration": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "noEmit": true, + "paths": { + "vue": ["../packages/vue-vapor/src"], + "@vue/vapor": ["../packages/vue-vapor/src"], + "@vue/*": ["../packages/*/src"] + } + }, + "include": ["**/*"] +} diff --git a/playground/setup/dev.js b/playground/setup/dev.js index 8b2f7bdaa..a6ba15274 100644 --- a/playground/setup/dev.js +++ b/playground/setup/dev.js @@ -1,10 +1,8 @@ // @ts-check import path from 'node:path' -import { fileURLToPath } from 'node:url' -const dirname = path.dirname(fileURLToPath(new URL(import.meta.url))) const resolve = (/** @type {string} */ p) => - path.resolve(dirname, '../../packages', p) + path.resolve(import.meta.dirname, '../../packages', p) /** * @param {Object} [env] diff --git a/scripts/utils.js b/scripts/utils.js index d81680f2e..5123d1294 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -3,17 +3,20 @@ import fs from 'node:fs' import pico from 'picocolors' import { createRequire } from 'node:module' import { spawn } from 'node:child_process' +import path from 'node:path' const require = createRequire(import.meta.url) +const packagesPath = path.resolve(import.meta.dirname, '../packages') -export const targets = fs.readdirSync('packages').filter(f => { +export const targets = fs.readdirSync(packagesPath).filter(f => { + const folder = path.resolve(packagesPath, f) if ( - !fs.statSync(`packages/${f}`).isDirectory() || - !fs.existsSync(`packages/${f}/package.json`) + !fs.statSync(folder).isDirectory() || + !fs.existsSync(`${folder}/package.json`) ) { return false } - const pkg = require(`../packages/${f}/package.json`) + const pkg = require(`${folder}/package.json`) if (pkg.private && !pkg.buildOptions) { return false } @@ -61,6 +64,7 @@ export function fuzzyMatchTarget(partialTargets, includeAllMatching) { * @param {string} command * @param {ReadonlyArray} args * @param {object} [options] + * @returns {Promise<{ ok: boolean, code: number | null, stderr: string, stdout: string }>} */ export async function exec(command, args, options) { return new Promise((resolve, reject) => { From cd6eeebe3fd5b89417dd1ace1e8243822088c5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 6 Aug 2024 14:11:51 +0800 Subject: [PATCH 2/8] feat: cli --- benchmark/index.js | 60 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/benchmark/index.js b/benchmark/index.js index 6edf6c91f..6ab794866 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -2,17 +2,56 @@ // @ts-check import path from 'node:path' +import { parseArgs } from 'node:util' import connect from 'connect' import Vue from '@vitejs/plugin-vue' import { build } from 'vite' import { exec } from '../scripts/utils.js' import sirv from 'sirv' import { launch } from 'puppeteer' +import colors from 'picocolors' + +const { + values: { skipVapor, skipApp, port: portStr, count: countStr, headless }, +} = parseArgs({ + allowPositionals: true, + options: { + skipVapor: { + type: 'boolean', + short: 'v', + }, + skipApp: { + type: 'boolean', + short: 'a', + }, + port: { + type: 'string', + short: 'p', + default: '8193', + }, + count: { + type: 'string', + short: 'c', + default: '100', + }, + headless: { + type: 'boolean', + short: 'h', + default: true, + }, + }, +}) -const PORT = 8193 +const port = +(/** @type {string}*/ (portStr)) +const count = +(/** @type {string}*/ (countStr)) + +if (!skipVapor) { + await buildVapor() +} +if (!skipApp) { + await buildApp() +} -await buildVapor() -await buildApp() const server = startServer() await bench() server.close() @@ -22,7 +61,8 @@ process.on('SIGTERM', () => { }) async function buildVapor() { - console.info('Building Vapor...') + console.info(colors.blue('Building Vapor...')) + const options = { cwd: path.resolve(import.meta.dirname, '..'), stdio: 'inherit', @@ -50,7 +90,7 @@ async function buildVapor() { } async function buildApp() { - console.info('Building app...') + console.info(colors.blue('\nBuilding app...\n')) process.env.NODE_ENV = 'production' const CompilerSFC = await import( @@ -87,8 +127,8 @@ async function buildApp() { } function startServer() { - const server = connect().use(sirv('./client/dist')).listen(PORT) - console.info(`Server started at http://localhost:${PORT}`) + const server = connect().use(sirv('./client/dist')).listen(port) + console.info(`\n\nServer started at`, colors.blue(`http://localhost:${port}`)) return server } @@ -110,18 +150,18 @@ async function bench() { ] const browser = await launch({ - // headless: false, + headless, args, }) const page = await browser.newPage() - await page.goto(`http://localhost:${PORT}/`, { + await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle0', }) await forceGC() const t = performance.now() - for (let i = 0; i < 50; i++) { + for (let i = 0; i < count; i++) { await doAction('run') await doAction('add') await doAction('update') From aa22bccb5f53457f970eb031ce459d3098ced412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Tue, 6 Aug 2024 17:39:21 +0800 Subject: [PATCH 3/8] feat: add cli options --- benchmark/.gitignore | 1 + benchmark/client/App.vue | 2 +- benchmark/client/profiling.ts | 49 ++++++------ benchmark/index.js | 138 ++++++++++++++++++++-------------- eslint.config.js | 1 + 5 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 benchmark/.gitignore diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 000000000..a5787a816 --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1 @@ +benchmark-result.json diff --git a/benchmark/client/App.vue b/benchmark/client/App.vue index 97482030d..7d8acb4f7 100644 --- a/benchmark/client/App.vue +++ b/benchmark/client/App.vue @@ -82,7 +82,7 @@ async function bench() { > - + diff --git a/benchmark/client/profiling.ts b/benchmark/client/profiling.ts index d1a8c2c12..d85667164 100644 --- a/benchmark/client/profiling.ts +++ b/benchmark/client/profiling.ts @@ -17,14 +17,7 @@ export function wrap( fn: (...args: any[]) => any, ): (...args: any[]) => Promise { return async (...args) => { - const btns = Array.from( - document.querySelectorAll('#control button'), - ) - const timeEl = document.getElementById('time')! - timeEl.classList.remove('done') - for (const node of btns) { - node.disabled = true - } + document.body.classList.remove('done') const { doProfile } = globalThis await defer() @@ -32,38 +25,48 @@ export function wrap( doProfile && console.profile(id) const start = performance.now() fn(...args) + await defer() const time = performance.now() - start const prevTimes = times[id] || (times[id] = []) prevTimes.push(time) - const median = prevTimes.slice().sort((a, b) => a - b)[ - Math.floor(prevTimes.length / 2) - ] - const mean = prevTimes.reduce((a, b) => a + b, 0) / prevTimes.length + const { min, max, median, mean, std } = compute(prevTimes) const msg = - `${id}: min: ${Math.min(...prevTimes).toFixed(2)} / ` + - `max: ${Math.max(...prevTimes).toFixed(2)} / ` + - `median: ${median.toFixed(2)}ms / ` + - `mean: ${mean.toFixed(2)}ms / ` + + `${id}: min: ${min} / ` + + `max: ${max} / ` + + `median: ${median}ms / ` + + `mean: ${mean}ms / ` + `time: ${time.toFixed(2)}ms / ` + - `std: ${getStandardDeviation(prevTimes).toFixed(2)} ` + + `std: ${std} ` + `over ${prevTimes.length} runs` doProfile && console.profileEnd(id) console.log(msg) + const timeEl = document.getElementById('time')! timeEl.textContent = msg - timeEl.classList.add('done') - for (const node of btns) { - node.disabled = false - } + document.body.classList.add('done') } } -function getStandardDeviation(array: number[]) { +function compute(array: number[]) { const n = array.length + const max = Math.max(...array) + const min = Math.min(...array) const mean = array.reduce((a, b) => a + b) / n - return Math.sqrt( + const std = Math.sqrt( array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n, ) + const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)] + return { + max: round(max), + min: round(min), + mean: round(mean), + std: round(std), + median: round(median), + } +} + +function round(n: number) { + return +n.toFixed(2) } diff --git a/benchmark/index.js b/benchmark/index.js index 6ab794866..593e749ed 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,19 +1,28 @@ -/* eslint-disable no-restricted-syntax */ - // @ts-check import path from 'node:path' import { parseArgs } from 'node:util' -import connect from 'connect' +import { writeFile } from 'node:fs/promises' import Vue from '@vitejs/plugin-vue' import { build } from 'vite' -import { exec } from '../scripts/utils.js' +import connect from 'connect' import sirv from 'sirv' import { launch } from 'puppeteer' import colors from 'picocolors' +import { exec } from '../scripts/utils.js' + +// Thanks to https://github.com/krausest/js-framework-benchmark (Apache-2.0 license) const { - values: { skipVapor, skipApp, port: portStr, count: countStr, headless }, + values: { + skipVapor, + skipApp, + skipBench, + port: portStr, + count: countStr, + 'no-headless': noHeadless, + }, } = parseArgs({ + allowNegative: true, allowPositionals: true, options: { skipVapor: { @@ -24,6 +33,10 @@ const { type: 'boolean', short: 'a', }, + skipBench: { + type: 'boolean', + short: 'a', + }, port: { type: 'string', short: 'p', @@ -32,12 +45,10 @@ const { count: { type: 'string', short: 'c', - default: '100', + default: '50', }, - headless: { + 'no-headless': { type: 'boolean', - short: 'h', - default: true, }, }, }) @@ -51,14 +62,12 @@ if (!skipVapor) { if (!skipApp) { await buildApp() } - const server = startServer() -await bench() -server.close() -process.on('SIGTERM', () => { +if (!skipBench) { + await bench() server.close() -}) +} async function buildVapor() { console.info(colors.blue('Building Vapor...')) @@ -99,7 +108,6 @@ async function buildApp() { const CompilerVapor = await import( '../packages/compiler-vapor/dist/compiler-vapor.cjs.prod.js' ) - const vaporRuntime = path.resolve( import.meta.dirname, '../packages/vue-vapor/dist/vue-vapor.esm-browser.prod.js', @@ -108,6 +116,12 @@ async function buildApp() { root: './client', build: { minify: 'terser', + rollupOptions: { + onwarn(log, handler) { + if (log.code === 'INVALID_ANNOTATION') return + handler(log) + }, + }, }, resolve: { alias: { @@ -115,6 +129,7 @@ async function buildApp() { '@vue/vapor': vaporRuntime, }, }, + clearScreen: false, plugins: [ Vue({ compiler: CompilerSFC, @@ -129,10 +144,12 @@ async function buildApp() { function startServer() { const server = connect().use(sirv('./client/dist')).listen(port) console.info(`\n\nServer started at`, colors.blue(`http://localhost:${port}`)) + process.on('SIGTERM', () => server.close()) return server } async function bench() { + console.info(colors.blue('\nStarting benchmark...')) const disableFeatures = [ 'Translate', // avoid translation popups 'PrivacySandboxSettings4', // avoid privacy popup @@ -149,10 +166,13 @@ async function bench() { `--disable-features=${disableFeatures.join(',')}`, ] + const headless = !noHeadless + console.info('headless:', headless) const browser = await launch({ - headless, + headless: headless, args, }) + console.log('browser version:', colors.blue(await browser.version())) const page = await browser.newPage() await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle0', @@ -162,34 +182,45 @@ async function bench() { const t = performance.now() for (let i = 0; i < count; i++) { - await doAction('run') - await doAction('add') - await doAction('update') - await doAction('swaprows') - await doAction('clear') + await clickButton('run') + await clickButton('add') + await select() + await clickButton('update') + await clickButton('swaprows') + await clickButton('runLots') + await clickButton('clear') } - console.info('Total time:', performance.now() - t, 'ms') + console.info('Total time:', ((performance.now() - t) / 1000).toFixed(2), 's') const times = await getTimes() - /** @type {Record} */ - const result = {} - for (const key in times) { - const mean = getMean(times[key]) - const std = getStandardDeviation(times[key]) - result[key] = { mean: mean.toFixed(2), std: std.toFixed(2) } - } - // eslint-disable-next-line no-console + /** @type {Record} */ + const result = Object.fromEntries( + Object.entries(times).map(([k, v]) => [k, compute(v)]), + ) console.table(result) + writeFile('benchmark-result.json', JSON.stringify(result)) - await page.close() await browser.close() /** * @param {string} id */ - async function doAction(id) { + async function clickButton(id) { await page.click(`#${id}`) + await wait() + } + + async function select() { + for (let i = 1; i <= 10; i++) { + await page.click(`tbody > tr:nth-child(2) > td:nth-child(2) > a`) + await page.waitForSelector(`tbody > tr:nth-child(2).danger`) + await page.click(`tbody > tr:nth-child(2) > td:nth-child(3) > button`) + await wait() + } + } + + async function wait() { await page.waitForSelector('.done') } @@ -199,38 +230,35 @@ async function bench() { async function forceGC() { await page.evaluate( - "window.gc({type:'major',execution:'sync',flavor:'last-resort'})", + `window.gc({type:'major',execution:'sync',flavor:'last-resort'})`, ) } } /** - * @param {number[]} nums - * @returns {number} - */ -function getMean(nums) { - return ( - nums.reduce( - /** - * @param {number} a - * @param {number} b - * @returns {number} - */ - (a, b) => a + b, - 0, - ) / nums.length - ) -} - -/** - * * @param {number[]} array - * @returns */ -function getStandardDeviation(array) { +function compute(array) { const n = array.length + const max = Math.max(...array) + const min = Math.min(...array) const mean = array.reduce((a, b) => a + b) / n - return Math.sqrt( + const std = Math.sqrt( array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n, ) + const median = array.slice().sort((a, b) => a - b)[Math.floor(n / 2)] + return { + max: round(max), + min: round(min), + mean: round(mean), + std: round(std), + median: round(median), + } +} + +/** + * @param {number} n + */ +function round(n) { + return +n.toFixed(2) } diff --git a/eslint.config.js b/eslint.config.js index f744b00c9..2717af4bb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -146,6 +146,7 @@ export default tseslint.config( 'eslint.config.js', 'rollup*.config.js', 'scripts/**', + 'benchmark/*', './*.{js,ts}', 'packages/*/*.js', 'packages/vue/*/*.js', From cfa546da7c1b91e09cf251e722032d2817de657d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 7 Aug 2024 01:45:08 +0800 Subject: [PATCH 4/8] refactor: save with sha --- benchmark/.gitignore | 2 +- benchmark/index.js | 27 ++++++++++++++++++++------- scripts/release.js | 11 +---------- scripts/utils.js | 9 +++++++++ 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/benchmark/.gitignore b/benchmark/.gitignore index a5787a816..484ab7e5c 100644 --- a/benchmark/.gitignore +++ b/benchmark/.gitignore @@ -1 +1 @@ -benchmark-result.json +results/* diff --git a/benchmark/index.js b/benchmark/index.js index 593e749ed..09927d2df 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,14 +1,14 @@ // @ts-check import path from 'node:path' import { parseArgs } from 'node:util' -import { writeFile } from 'node:fs/promises' +import { mkdir, writeFile } from 'node:fs/promises' import Vue from '@vitejs/plugin-vue' import { build } from 'vite' import connect from 'connect' import sirv from 'sirv' import { launch } from 'puppeteer' import colors from 'picocolors' -import { exec } from '../scripts/utils.js' +import { exec, getSha } from '../scripts/utils.js' // Thanks to https://github.com/krausest/js-framework-benchmark (Apache-2.0 license) @@ -79,17 +79,21 @@ async function buildVapor() { const [{ ok }, { ok: ok2 }, { ok: ok3 }] = await Promise.all([ exec( 'pnpm', - 'run build shared compiler-core compiler-dom compiler-vapor -pf cjs'.split( + 'run --silent build shared compiler-core compiler-dom compiler-vapor -pf cjs'.split( ' ', ), options, ), exec( 'pnpm', - 'run build compiler-sfc compiler-ssr -f cjs'.split(' '), + 'run --silent build compiler-sfc compiler-ssr -f cjs'.split(' '), + options, + ), + exec( + 'pnpm', + 'run --silent build vue-vapor -pf esm-browser'.split(' '), options, ), - exec('pnpm', 'run build vue-vapor -pf esm-browser'.split(' '), options), ]) if (!ok || !ok2 || !ok3) { @@ -190,7 +194,11 @@ async function bench() { await clickButton('runLots') await clickButton('clear') } - console.info('Total time:', ((performance.now() - t) / 1000).toFixed(2), 's') + console.info( + 'Total time:', + colors.cyan(((performance.now() - t) / 1000).toFixed(2)), + 's', + ) const times = await getTimes() @@ -199,7 +207,12 @@ async function bench() { Object.entries(times).map(([k, v]) => [k, compute(v)]), ) console.table(result) - writeFile('benchmark-result.json', JSON.stringify(result)) + + await mkdir('results', { recursive: true }).catch(() => {}) + writeFile( + `results/benchmark-${await getSha(true)}.json`, + JSON.stringify(result), + ) await browser.close() diff --git a/scripts/release.js b/scripts/release.js index dc1a5173e..1a3802819 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -6,7 +6,7 @@ import semver from 'semver' import enquirer from 'enquirer' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' -import { exec } from './utils.js' +import { exec, getSha } from './utils.js' import { parseArgs } from 'node:util' /** @@ -445,15 +445,6 @@ async function isInSyncWithRemote() { } } -/** - * @param {boolean=} short - */ -async function getSha(short) { - return ( - await exec('git', ['rev-parse', ...(short ? ['--short'] : []), 'HEAD']) - ).stdout -} - async function getBranch() { return (await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout } diff --git a/scripts/utils.js b/scripts/utils.js index 5123d1294..a789d6d30 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -108,3 +108,12 @@ export async function exec(command, args, options) { }) }) } + +/** + * @param {boolean=} short + */ +export async function getSha(short) { + return ( + await exec('git', ['rev-parse', ...(short ? ['--short'] : []), 'HEAD']) + ).stdout +} From 657047d8d40cc46a33bebf440230377b6f7c3661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 7 Aug 2024 02:39:54 +0800 Subject: [PATCH 5/8] feat: vdom --- benchmark/client/App.vue | 4 +- benchmark/client/index.html | 5 + benchmark/client/index.ts | 9 +- benchmark/client/vapor.ts | 4 + benchmark/client/vdom.ts | 4 + benchmark/index.js | 201 +++++++++++++++++------------ benchmark/package.json | 2 +- benchmark/tsconfig.json | 2 +- pnpm-lock.yaml | 243 ++++++++++++++++++++++++++++++++---- 9 files changed, 369 insertions(+), 105 deletions(-) create mode 100644 benchmark/client/vapor.ts create mode 100644 benchmark/client/vdom.ts diff --git a/benchmark/client/App.vue b/benchmark/client/App.vue index 7d8acb4f7..5b16e9547 100644 --- a/benchmark/client/App.vue +++ b/benchmark/client/App.vue @@ -3,6 +3,8 @@ import { ref, shallowRef } from '@vue/vapor' import { buildData } from './data' import { defer, wrap } from './profiling' +const isVapor = !!import.meta.env.IS_VAPOR + const selected = ref() const rows = shallowRef< { @@ -75,7 +77,7 @@ async function bench() {