workflow: add quick benchmark (#266)

This commit is contained in:
Kevin Deng 三咲智子 2024-08-07 03:51:40 +08:00 committed by GitHub
parent 669c295475
commit 25f8502546
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 747 additions and 103 deletions

1
benchmark/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
results/*

View File

@ -1,8 +1,10 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
<script setup lang="ts" vapor>
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<number>()
const rows = shallowRef<
{
@ -75,14 +77,14 @@ async function bench() {
</script>
<template>
<h1>Vue.js Vapor Benchmark</h1>
<h1>Vue.js ({{ isVapor ? 'Vapor' : 'Virtual DOM' }}) Benchmark</h1>
<div
id="control"
style="display: flex; flex-direction: column; width: fit-content; gap: 6px"
>
<button @click="bench">Benchmark mounting</button>
<button id="run" @click="run">Create 1,000 rows</button>
<button id="runlots" @click="runLots">Create 10,000 rows</button>
<button id="runLots" @click="runLots">Create 10,000 rows</button>
<button id="add" @click="add">Append 1,000 rows</button>
<button id="update" @click="update">Update every 10th row</button>
<button id="clear" @click="clear">Clear</button>

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Vapor Benchmark</title>
<style>
html {
color-scheme: light dark;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
if (import.meta.env.IS_VAPOR) {
import('./vapor')
} else {
import('./vdom')
}

View File

@ -0,0 +1,79 @@
/* eslint-disable no-console */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-restricted-globals */
declare module globalThis {
let doProfile: boolean
let recordTime: boolean
let times: Record<string, number[]>
}
globalThis.recordTime = true
globalThis.doProfile = false
export const defer = () => new Promise(r => requestIdleCallback(r))
const times: Record<string, number[]> = (globalThis.times = {})
export function wrap(
id: string,
fn: (...args: any[]) => any,
): (...args: any[]) => Promise<void> {
return async (...args) => {
if (!globalThis.recordTime) {
return fn(...args)
}
document.body.classList.remove('done')
const { doProfile } = globalThis
await defer()
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 { min, max, median, mean, std } = compute(prevTimes)
const msg =
`${id}: min: ${min} / ` +
`max: ${max} / ` +
`median: ${median}ms / ` +
`mean: ${mean}ms / ` +
`time: ${time.toFixed(2)}ms / ` +
`std: ${std} ` +
`over ${prevTimes.length} runs`
doProfile && console.profileEnd(id)
console.log(msg)
const timeEl = document.getElementById('time')!
timeEl.textContent = msg
document.body.classList.add('done')
}
}
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
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)
}

View File

@ -0,0 +1,4 @@
import { createVaporApp } from '@vue/vapor'
import App from './App.vue'
createVaporApp(App as any).mount('#app')

4
benchmark/client/vdom.ts Normal file
View File

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

340
benchmark/index.js Normal file
View File

@ -0,0 +1,340 @@
// @ts-check
import path from 'node:path'
import { parseArgs } from 'node:util'
import { mkdir, rm, 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, getSha } from '../scripts/utils.js'
// Thanks to https://github.com/krausest/js-framework-benchmark (Apache-2.0 license)
const {
values: {
skipLib,
skipApp,
skipBench,
vdom,
noVapor,
port: portStr,
count: countStr,
noHeadless,
},
} = parseArgs({
allowNegative: true,
allowPositionals: true,
options: {
skipLib: {
type: 'boolean',
short: 'v',
},
skipApp: {
type: 'boolean',
short: 'a',
},
skipBench: {
type: 'boolean',
short: 'b',
},
noVapor: {
type: 'boolean',
},
vdom: {
type: 'boolean',
short: 'v',
},
port: {
type: 'string',
short: 'p',
default: '8193',
},
count: {
type: 'string',
short: 'c',
default: '50',
},
noHeadless: {
type: 'boolean',
},
},
})
const port = +(/** @type {string}*/ (portStr))
const count = +(/** @type {string}*/ (countStr))
const sha = await getSha(true)
if (!skipLib) {
await buildLib()
}
if (!skipApp) {
await rm('client/dist', { recursive: true }).catch(() => {})
vdom && (await buildApp(false))
!noVapor && (await buildApp(true))
}
const server = startServer()
if (!skipBench) {
await benchmark()
server.close()
}
async function buildLib() {
console.info(colors.blue('Building lib...'))
const options = {
cwd: path.resolve(import.meta.dirname, '..'),
stdio: 'inherit',
}
const [{ ok }, { ok: ok2 }, { ok: ok3 }, { ok: ok4 }] = await Promise.all([
exec(
'pnpm',
'run --silent build shared compiler-core compiler-dom compiler-vapor -pf cjs'.split(
' ',
),
options,
),
exec(
'pnpm',
'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 --silent build vue -pf esm-browser-runtime'.split(' '),
options,
),
])
if (!ok || !ok2 || !ok3 || !ok4) {
console.error('Failed to build')
process.exit(1)
}
}
/** @param {boolean} isVapor */
async function buildApp(isVapor) {
console.info(
colors.blue(`\nBuilding ${isVapor ? 'Vapor' : 'Virtual DOM'} app...\n`),
)
process.env.NODE_ENV = 'production'
const CompilerSFC = await import(
'../packages/compiler-sfc/dist/compiler-sfc.cjs.js'
)
/** @type {any} */
const TemplateCompiler = await import(
isVapor
? '../packages/compiler-vapor/dist/compiler-vapor.cjs.prod.js'
: '../packages/compiler-dom/dist/compiler-dom.cjs.prod.js'
)
const runtimePath = path.resolve(
import.meta.dirname,
isVapor
? '../packages/vue-vapor/dist/vue-vapor.esm-browser.prod.js'
: '../packages/vue/dist/vue.runtime.esm-browser.prod.js',
)
const mode = isVapor ? 'vapor' : 'vdom'
await build({
root: './client',
base: `/${mode}`,
define: {
'import.meta.env.IS_VAPOR': String(isVapor),
},
build: {
minify: 'terser',
outDir: path.resolve('./client/dist', mode),
rollupOptions: {
onwarn(log, handler) {
if (log.code === 'INVALID_ANNOTATION') return
handler(log)
},
},
},
resolve: {
alias: {
'@vue/vapor': runtimePath,
'vue/vapor': runtimePath,
vue: runtimePath,
},
},
clearScreen: false,
plugins: [
Vue({
compiler: CompilerSFC,
template: { compiler: TemplateCompiler },
}),
],
})
}
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 benchmark() {
console.info(colors.blue(`\nStarting benchmark...`))
const browser = await initBrowser()
await mkdir('results', { recursive: true }).catch(() => {})
if (!noVapor) {
await doBench(browser, true)
}
if (vdom) {
await doBench(browser, false)
}
await browser.close()
}
/**
*
* @param {import('puppeteer').Browser} browser
* @param {boolean} isVapor
*/
async function doBench(browser, isVapor) {
const mode = isVapor ? 'vapor' : 'vdom'
console.info('\n\nmode:', mode)
const page = await browser.newPage()
await page.goto(`http://localhost:${port}/${mode}`, {
waitUntil: 'networkidle0',
})
await forceGC()
const t = performance.now()
for (let i = 0; i < count; i++) {
await clickButton('run') // test: create rows
await clickButton('update') // partial update
await clickButton('swaprows') // swap rows
await select() // test: select row, remove row
await clickButton('clear') // clear rows
await withoutRecord(() => clickButton('run'))
await clickButton('add') // append rows to large table
await withoutRecord(() => clickButton('clear'))
await clickButton('runLots') // create many rows
await withoutRecord(() => clickButton('clear'))
// TODO replace all rows
}
console.info(
'Total time:',
colors.cyan(((performance.now() - t) / 1000).toFixed(2)),
's',
)
const times = await getTimes()
const result =
/** @type {Record<string, typeof compute>} */
Object.fromEntries(Object.entries(times).map(([k, v]) => [k, compute(v)]))
console.table(result)
await writeFile(
`results/benchmark-${sha}-${mode}.json`,
JSON.stringify(result, undefined, 2),
)
await page.close()
return result
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 {() => any} fn */
async function withoutRecord(fn) {
await page.evaluate(() => (globalThis.recordTime = false))
await fn()
await page.evaluate(() => (globalThis.recordTime = true))
}
/** @param {string} 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')
}
}
async function initBrowser() {
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 headless = !noHeadless
console.info('headless:', headless)
const browser = await launch({
headless: headless,
args,
})
console.log('browser version:', colors.blue(await browser.version()))
return browser
}
/** @param {number[]} 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
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)
}

19
benchmark/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "benchmark",
"version": "0.0.0",
"author": "三咲智子 Kevin Deng <sxzz@sxzz.moe>",
"license": "MIT",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@vitejs/plugin-vue": "npm:@vue-vapor/vite-plugin-vue@0.0.0-alpha.6",
"connect": "^3.7.0",
"sirv": "^2.0.4",
"vite": "^5.0.12"
},
"devDependencies": {
"@types/connect": "^3.4.38"
}
}

26
benchmark/tsconfig.json Normal file
View File

@ -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/src"],
"@vue/vapor": ["../packages/vue-vapor/src"],
"@vue/*": ["../packages/*/src"]
}
},
"include": ["**/*"]
}

View File

@ -146,6 +146,7 @@ export default tseslint.config(
'eslint.config.js',
'rollup*.config.js',
'scripts/**',
'benchmark/*',
'./*.{js,ts}',
'packages/*/*.js',
'packages/vue/*/*.js',

View File

@ -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]

View File

@ -1,60 +0,0 @@
// @ts-expect-error
globalThis.doProfile = false
// const defer = nextTick
const ric =
typeof requestIdleCallback === 'undefined' ? setTimeout : requestIdleCallback
export const defer = () => new Promise(r => ric(r))
const times: Record<string, number[]> = {}
export const wrap = (
id: string,
fn: (...args: any[]) => any,
): ((...args: any[]) => Promise<void>) => {
if (import.meta.env.PROD) return fn
return async (...args) => {
const btns = Array.from(
document.querySelectorAll<HTMLButtonElement>('#control button'),
)
for (const node of btns) {
node.disabled = true
}
const doProfile = (globalThis as any).doProfile
await defer()
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 msg =
`${id}: min: ${Math.min(...prevTimes).toFixed(2)} / ` +
`max: ${Math.max(...prevTimes).toFixed(2)} / ` +
`median: ${median.toFixed(2)}ms / ` +
`mean: ${mean.toFixed(2)}ms / ` +
`time: ${time.toFixed(2)}ms / ` +
`std: ${getStandardDeviation(prevTimes).toFixed(2)} ` +
`over ${prevTimes.length} runs`
doProfile && console.profileEnd(id)
console.log(msg)
document.getElementById('time')!.textContent = msg
for (const node of btns) {
node.disabled = false
}
}
}
function getStandardDeviation(array: number[]) {
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,
)
}

View File

@ -4,27 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
catalogs:
default:
'@babel/parser':
specifier: ^7.24.7
version: 7.24.7
'@babel/types':
specifier: ^7.24.7
version: 7.24.7
estree-walker:
specifier: ^2.0.2
version: 2.0.2
magic-string:
specifier: ^0.30.10
version: 0.30.10
source-map-js:
specifier: ^1.2.0
version: 1.2.0
vite:
specifier: ^5.3.3
version: 5.3.3
importers:
.:
@ -177,6 +156,25 @@ importers:
specifier: ^1.6.0
version: 1.6.0(@types/node@20.14.13)(@vitest/ui@1.6.0)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.1)
benchmark:
dependencies:
'@vitejs/plugin-vue':
specifier: npm:@vue-vapor/vite-plugin-vue@0.0.0-alpha.6
version: '@vue-vapor/vite-plugin-vue@0.0.0-alpha.6(vite@5.3.3(@types/node@20.14.13)(sass@1.77.8)(terser@5.31.1))(vue@3.4.36(typescript@5.4.5))'
connect:
specifier: ^3.7.0
version: 3.7.0
sirv:
specifier: ^2.0.4
version: 2.0.4
vite:
specifier: ^5.0.12
version: 5.3.3(@types/node@20.14.13)(sass@1.77.8)(terser@5.31.1)
devDependencies:
'@types/connect':
specifier: ^3.4.38
version: 3.4.38
packages/compiler-core:
dependencies:
'@babel/parser':
@ -1418,6 +1416,9 @@ packages:
'@tootallnate/quickjs-emscripten@0.23.0':
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@ -1566,13 +1567,49 @@ packages:
vite: ^5.0.0
vue: '*'
'@vue-vapor/vite-plugin-vue@0.0.0-alpha.6':
resolution: {integrity: sha512-V2aTQ7bkDXsoPvYIkTA54m3ypUXDIVpTFspn+ycuYcMfIY37cZ0ny6jm/afNY6k1DiaQ9JfAMBXAKzTBpu2B9A==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vue: '*'
'@vue/compiler-core@3.4.36':
resolution: {integrity: sha512-qBkndgpwFKdupmOPoiS10i7oFdN7a+4UNDlezD0GlQ1kuA1pNrscg9g12HnB5E8hrWSuEftRsbJhL1HI2zpJhg==}
'@vue/compiler-dom@3.4.36':
resolution: {integrity: sha512-eEIjy4GwwZTFon/Y+WO8tRRNGqylaRlA79T1RLhUpkOzJ7EtZkkb8MurNfkqY6x6Qiu0R7ESspEF7GkPR/4yYg==}
'@vue/compiler-sfc@3.4.36':
resolution: {integrity: sha512-rhuHu7qztt/rNH90dXPTzhB7hLQT2OC4s4GrPVqmzVgPY4XBlfWmcWzn4bIPEWNImt0CjO7kfHAf/1UXOtx3vw==}
'@vue/compiler-ssr@3.4.36':
resolution: {integrity: sha512-Wt1zyheF0zVvRJyhY74uxQbnkXV2Le/JPOrAxooR4rFYKC7cFr+cRqW6RU3cM/bsTy7sdZ83IDuy/gLPSfPGng==}
'@vue/consolidate@1.0.0':
resolution: {integrity: sha512-oTyUE+QHIzLw2PpV14GD/c7EohDyP64xCniWTcqcEmTd699eFqTIwOmtDYjcO1j3QgdXoJEoWv1/cCdLrRoOfg==}
engines: {node: '>= 0.12.0'}
'@vue/reactivity@3.4.36':
resolution: {integrity: sha512-wN1aoCwSoqrt1yt8wO0gc13QaC+Vk1o6AoSt584YHNnz6TGDhh1NCMUYgAnvp4HEIkLdGsaC1bvu/P+wpoDEXw==}
'@vue/repl@4.3.1':
resolution: {integrity: sha512-yzUuLhR+MqOGBDES+xbnm27SfPIEv7XKwhFWWpQhL7HUbXj77GVu+x50Q56JhCWWKTUJzk9MOvAn7bSgdvB5og==}
'@vue/runtime-core@3.4.36':
resolution: {integrity: sha512-9+TR14LAVEerZWLOm/N/sG2DVYhrH2bKgFrbH/FVt/Q8Jdw4OtdcGMRC6Tx8VAo0DA1eqAqrZaX0fbOaOxxZ4A==}
'@vue/runtime-dom@3.4.36':
resolution: {integrity: sha512-2Qe2fKkLxgZBVvHrG0QMNLL4bsx7Ae88pyXebY2WnQYABpOnGYvA+axMbcF9QwM4yxnsv+aELbC0eiNVns7mGw==}
'@vue/server-renderer@3.4.36':
resolution: {integrity: sha512-2XW90Rq8+Y7S1EIsAuubZVLm0gCU8HYb5mRAruFdwfC3XSOU5/YKePz29csFzsch8hXaY5UHh7ZMddmi1XTJEA==}
peerDependencies:
vue: 3.4.36
'@vue/shared@3.4.36':
resolution: {integrity: sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==}
'@vueuse/core@10.9.0':
resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==}
@ -1901,6 +1938,10 @@ packages:
confbox@0.1.7:
resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
connect@3.7.0:
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
engines: {node: '>= 0.10.0'}
constantinople@4.0.1:
resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
@ -2106,6 +2147,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
electron-to-chromium@1.4.818:
resolution: {integrity: sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA==}
@ -2118,6 +2162,10 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@ -2129,6 +2177,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@5.0.0:
resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
engines: {node: '>=0.12'}
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@ -2174,6 +2226,9 @@ packages:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@ -2316,6 +2371,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
finalhandler@1.1.2:
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
engines: {node: '>= 0.8'}
find-up-simple@1.0.0:
resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==}
engines: {node: '>=18'}
@ -2974,6 +3033,10 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
on-headers@1.0.2:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
@ -3038,6 +3101,10 @@ packages:
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@ -3484,6 +3551,10 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
statuses@1.5.0:
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
engines: {node: '>= 0.6'}
std-env@3.7.0:
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
@ -3699,6 +3770,10 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
@ -3724,6 +3799,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -3853,6 +3932,14 @@ packages:
'@vue/composition-api':
optional: true
vue@3.4.36:
resolution: {integrity: sha512-mIFvbLgjODfx3Iy1SrxOsiPpDb8Bo3EU+87ioimOZzZTOp15IEdAels70IjBOLO3ZFlLW5AhdwY4dWbXVQKYow==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@ -4644,6 +4731,10 @@ snapshots:
'@tootallnate/quickjs-emscripten@0.23.0': {}
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.14.13
'@types/estree@1.0.5': {}
'@types/hash-sum@1.0.2': {}
@ -4849,10 +4940,69 @@ snapshots:
vite: 5.2.9(@types/node@20.14.13)(sass@1.77.8)(terser@5.31.1)
vue: link:packages/vue
'@vue-vapor/vite-plugin-vue@0.0.0-alpha.6(vite@5.3.3(@types/node@20.14.13)(sass@1.77.8)(terser@5.31.1))(vue@3.4.36(typescript@5.4.5))':
dependencies:
vite: 5.3.3(@types/node@20.14.13)(sass@1.77.8)(terser@5.31.1)
vue: 3.4.36(typescript@5.4.5)
'@vue/compiler-core@3.4.36':
dependencies:
'@babel/parser': 7.24.7
'@vue/shared': 3.4.36
entities: 5.0.0
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-dom@3.4.36':
dependencies:
'@vue/compiler-core': 3.4.36
'@vue/shared': 3.4.36
'@vue/compiler-sfc@3.4.36':
dependencies:
'@babel/parser': 7.24.7
'@vue/compiler-core': 3.4.36
'@vue/compiler-dom': 3.4.36
'@vue/compiler-ssr': 3.4.36
'@vue/shared': 3.4.36
estree-walker: 2.0.2
magic-string: 0.30.10
postcss: 8.4.40
source-map-js: 1.2.0
'@vue/compiler-ssr@3.4.36':
dependencies:
'@vue/compiler-dom': 3.4.36
'@vue/shared': 3.4.36
'@vue/consolidate@1.0.0': {}
'@vue/reactivity@3.4.36':
dependencies:
'@vue/shared': 3.4.36
'@vue/repl@4.3.1': {}
'@vue/runtime-core@3.4.36':
dependencies:
'@vue/reactivity': 3.4.36
'@vue/shared': 3.4.36
'@vue/runtime-dom@3.4.36':
dependencies:
'@vue/reactivity': 3.4.36
'@vue/runtime-core': 3.4.36
'@vue/shared': 3.4.36
csstype: 3.1.3
'@vue/server-renderer@3.4.36(vue@3.4.36(typescript@5.4.5))':
dependencies:
'@vue/compiler-ssr': 3.4.36
'@vue/shared': 3.4.36
vue: 3.4.36(typescript@5.4.5)
'@vue/shared@3.4.36': {}
'@vueuse/core@10.9.0(vue@packages+vue)':
dependencies:
'@types/web-bluetooth': 0.0.20
@ -5201,6 +5351,15 @@ snapshots:
confbox@0.1.7: {}
connect@3.7.0:
dependencies:
debug: 2.6.9
finalhandler: 1.1.2
parseurl: 1.3.3
utils-merge: 1.0.1
transitivePeerDependencies:
- supports-color
constantinople@4.0.1:
dependencies:
'@babel/parser': 7.24.7
@ -5394,6 +5553,8 @@ snapshots:
eastasianwidth@0.2.0: {}
ee-first@1.1.1: {}
electron-to-chromium@1.4.818: {}
emoji-regex@10.3.0: {}
@ -5402,6 +5563,8 @@ snapshots:
emoji-regex@9.2.2: {}
encodeurl@1.0.2: {}
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
@ -5413,6 +5576,8 @@ snapshots:
entities@4.5.0: {}
entities@5.0.0: {}
env-paths@2.2.1: {}
error-ex@1.3.2:
@ -5516,6 +5681,8 @@ snapshots:
escalade@3.1.2: {}
escape-html@1.0.3: {}
escape-string-regexp@1.0.5: {}
escape-string-regexp@4.0.0: {}
@ -5725,6 +5892,18 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
finalhandler@1.1.2:
dependencies:
debug: 2.6.9
encodeurl: 1.0.2
escape-html: 1.0.3
on-finished: 2.3.0
parseurl: 1.3.3
statuses: 1.5.0
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
find-up-simple@1.0.0: {}
find-up@5.0.0:
@ -6362,6 +6541,10 @@ snapshots:
object-assign@4.1.1: {}
on-finished@2.3.0:
dependencies:
ee-first: 1.1.1
on-headers@1.0.2: {}
once@1.4.0:
@ -6447,6 +6630,8 @@ snapshots:
dependencies:
entities: 4.5.0
parseurl@1.3.3: {}
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
@ -6976,6 +7161,8 @@ snapshots:
stackback@0.0.2: {}
statuses@1.5.0: {}
std-env@3.7.0: {}
streamx@2.18.0:
@ -7171,6 +7358,8 @@ snapshots:
universalify@2.0.1: {}
unpipe@1.0.0: {}
untildify@4.0.0: {}
update-browserslist-db@1.1.0(browserslist@4.23.1):
@ -7197,6 +7386,8 @@ snapshots:
util-deprecate@1.0.2: {}
utils-merge@1.0.1: {}
validate-npm-package-license@3.0.4:
dependencies:
spdx-correct: 3.2.0
@ -7332,6 +7523,16 @@ snapshots:
dependencies:
vue: link:packages/vue
vue@3.4.36(typescript@5.4.5):
dependencies:
'@vue/compiler-dom': 3.4.36
'@vue/compiler-sfc': 3.4.36
'@vue/runtime-dom': 3.4.36
'@vue/server-renderer': 3.4.36(vue@3.4.36(typescript@5.4.5))
'@vue/shared': 3.4.36
optionalDependencies:
typescript: 5.4.5
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0

View File

@ -1,6 +1,7 @@
packages:
- 'packages/*'
- playground
- benchmark
catalog:
'@babel/parser': ^7.24.7

View File

@ -198,7 +198,9 @@ export function scanEnums() {
}
// 3. save cache
if (!existsSync('temp')) mkdirSync('temp')
try {
mkdirSync('temp')
} catch {}
/** @type {EnumData} */
const enumData = {

View File

@ -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
}

View File

@ -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<string>} 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) => {
@ -104,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
}