mirror of https://github.com/vuejs/core.git
361 lines
8.5 KiB
JavaScript
361 lines
8.5 KiB
JavaScript
// @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,
|
|
devBuild,
|
|
},
|
|
} = 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: '30',
|
|
},
|
|
noHeadless: {
|
|
type: 'boolean',
|
|
},
|
|
devBuild: {
|
|
type: 'boolean',
|
|
short: 'd',
|
|
},
|
|
},
|
|
})
|
|
|
|
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 buildOptions = devBuild ? '-df' : '-pf'
|
|
const [{ ok }, { ok: ok2 }, { ok: ok3 }, { ok: ok4 }] = await Promise.all([
|
|
exec(
|
|
'pnpm',
|
|
`run --silent build shared compiler-core compiler-dom compiler-vapor ${buildOptions} cjs`.split(
|
|
' ',
|
|
),
|
|
options,
|
|
),
|
|
exec(
|
|
'pnpm',
|
|
'run --silent build compiler-sfc compiler-ssr -f cjs'.split(' '),
|
|
options,
|
|
),
|
|
exec(
|
|
'pnpm',
|
|
`run --silent build vue-vapor ${buildOptions} esm-browser`.split(' '),
|
|
options,
|
|
),
|
|
exec(
|
|
'pnpm',
|
|
`run --silent build vue ${buildOptions} 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'
|
|
)
|
|
const prodSuffix = devBuild ? '.js' : '.prod.js'
|
|
|
|
/** @type {any} */
|
|
const TemplateCompiler = await import(
|
|
(isVapor
|
|
? '../packages/compiler-vapor/dist/compiler-vapor.cjs'
|
|
: '../packages/compiler-dom/dist/compiler-dom.cjs') + prodSuffix
|
|
)
|
|
const runtimePath = path.resolve(
|
|
import.meta.dirname,
|
|
(isVapor
|
|
? '../packages/vue-vapor/dist/vue-vapor.esm-browser'
|
|
: '../packages/vue/dist/vue.runtime.esm-browser') + prodSuffix,
|
|
)
|
|
|
|
const mode = isVapor ? 'vapor' : 'vdom'
|
|
await build({
|
|
root: './client',
|
|
base: `/${mode}`,
|
|
define: {
|
|
'import.meta.env.IS_VAPOR': String(isVapor),
|
|
},
|
|
build: {
|
|
minify: !devBuild && '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)
|
|
printPort(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()
|
|
page.emulateCPUThrottling(4)
|
|
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)
|
|
}
|
|
|
|
/** @param {number} port */
|
|
function printPort(port) {
|
|
const vaporLink = !noVapor
|
|
? `\nVapor: ${colors.blue(`http://localhost:${port}/vapor`)}`
|
|
: ''
|
|
const vdomLink = vdom
|
|
? `\nvDom: ${colors.blue(`http://localhost:${port}/vdom`)}`
|
|
: ''
|
|
console.info(`\n\nServer started at`, vaporLink, vdomLink)
|
|
}
|