react-tooltip 6.0.0-beta.1179.rc.6 → 6.0.0-beta.1179.rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{CONTRIBUTION.md → CONTRIBUTING.md} +0 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/benchmarks/aggregate-benchmarks.mjs +0 -258
- package/benchmarks/fixture/app.tsx +0 -278
- package/benchmarks/fixture/index.html +0 -54
- package/benchmarks/run-benchmark.mjs +0 -324
- package/benchmarks/run-scaling-series.mjs +0 -77
- package/global.d.ts +0 -7
- package/rollup.config.dev.mjs +0 -87
- package/rollup.config.prod.mjs +0 -136
- package/rollup.config.types.mjs +0 -17
- package/scripts/configure-react-version.mjs +0 -52
- package/tsconfig.jest.json +0 -14
package/README.md
CHANGED
|
@@ -181,7 +181,7 @@ We would gladly accept a new maintainer to help out!
|
|
|
181
181
|
|
|
182
182
|
## Contributing
|
|
183
183
|
|
|
184
|
-
We welcome your contribution! Fork the repo, make some changes, submit a pull-request! Our [contributing](
|
|
184
|
+
We welcome your contribution! Fork the repo, make some changes, submit a pull-request! Our [contributing](CONTRIBUTING.md) doc has some details.
|
|
185
185
|
|
|
186
186
|
## License
|
|
187
187
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-tooltip",
|
|
3
|
-
"version": "6.0.0-beta.1179.rc.
|
|
3
|
+
"version": "6.0.0-beta.1179.rc.7",
|
|
4
4
|
"description": "react tooltip component",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev-rollup": "node ./prebuild.js --env=development && node --max_old_space_size=2048 ./node_modules/rollup/dist/bin/rollup -c rollup.config.dev.mjs --watch",
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile, writeFile } from 'fs/promises'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { fileURLToPath } from 'url'
|
|
4
|
-
import minimist from 'minimist'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
const resultsDir = path.join(__dirname, 'results')
|
|
8
|
-
|
|
9
|
-
const args = minimist(process.argv.slice(2))
|
|
10
|
-
const latest = Number(args.latest ?? 3)
|
|
11
|
-
const useAll = Boolean(args.all)
|
|
12
|
-
const generationArg = args.generation
|
|
13
|
-
const useAllGenerations = Boolean(args['all-generations'])
|
|
14
|
-
const explicitFiles = args._.map((inputPath) => path.resolve(inputPath))
|
|
15
|
-
|
|
16
|
-
function aggregateNumbers(values) {
|
|
17
|
-
const sorted = values
|
|
18
|
-
.filter((value) => typeof value === 'number' && Number.isFinite(value))
|
|
19
|
-
.sort((left, right) => left - right)
|
|
20
|
-
|
|
21
|
-
if (sorted.length === 0) {
|
|
22
|
-
return {
|
|
23
|
-
median: null,
|
|
24
|
-
p95: null,
|
|
25
|
-
min: null,
|
|
26
|
-
max: null,
|
|
27
|
-
mean: null,
|
|
28
|
-
standardDeviation: null,
|
|
29
|
-
spreadPercent: null,
|
|
30
|
-
sampleCount: 0,
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const middle = Math.floor(sorted.length / 2)
|
|
35
|
-
const median =
|
|
36
|
-
sorted.length % 2 === 0
|
|
37
|
-
? (sorted[middle - 1] + sorted[middle]) / 2
|
|
38
|
-
: sorted[middle]
|
|
39
|
-
const mean = sorted.reduce((total, value) => total + value, 0) / sorted.length
|
|
40
|
-
const variance =
|
|
41
|
-
sorted.reduce((total, value) => total + (value - mean) ** 2, 0) / sorted.length
|
|
42
|
-
const standardDeviation = Math.sqrt(variance)
|
|
43
|
-
const p95 = sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.95) - 1)]
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
median,
|
|
47
|
-
p95,
|
|
48
|
-
min: sorted[0],
|
|
49
|
-
max: sorted[sorted.length - 1],
|
|
50
|
-
mean,
|
|
51
|
-
standardDeviation,
|
|
52
|
-
spreadPercent: median === 0 ? null : ((p95 - median) / Math.abs(median)) * 100,
|
|
53
|
-
sampleCount: sorted.length,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function formatMs(value) {
|
|
58
|
-
return typeof value === 'number' ? `${value.toFixed(2)} ms` : '—'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function formatBytes(value) {
|
|
62
|
-
return typeof value === 'number' ? `${(value / 1024).toFixed(1)} KiB` : '—'
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function formatPercent(value) {
|
|
66
|
-
return typeof value === 'number' ? `${value.toFixed(1)}%` : '—'
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function timestampId() {
|
|
70
|
-
return new Date().toISOString().replace(/[:.]/g, '-')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function getRunGeneration(run) {
|
|
74
|
-
return Number.isInteger(run?.benchmarkVersion) ? run.benchmarkVersion : 0
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function resolveInputFiles() {
|
|
78
|
-
if (explicitFiles.length > 0) {
|
|
79
|
-
return explicitFiles
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const entries = await readdir(resultsDir)
|
|
83
|
-
const scalingEntries = entries
|
|
84
|
-
.filter((entry) => entry.endsWith('.json'))
|
|
85
|
-
.filter((entry) => entry.startsWith('scaling-'))
|
|
86
|
-
.map((entry) => path.join(resultsDir, entry))
|
|
87
|
-
.sort()
|
|
88
|
-
|
|
89
|
-
if (useAll) {
|
|
90
|
-
return scalingEntries
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return scalingEntries.slice(-latest)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function resolveGenerationSelection(runs) {
|
|
97
|
-
if (useAllGenerations) {
|
|
98
|
-
return {
|
|
99
|
-
selectedRuns: runs,
|
|
100
|
-
generationLabel: 'all benchmark generations',
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (generationArg !== undefined) {
|
|
105
|
-
const requestedGeneration =
|
|
106
|
-
generationArg === 'latest' ? Math.max(...runs.map(getRunGeneration)) : Number(generationArg)
|
|
107
|
-
|
|
108
|
-
if (!Number.isInteger(requestedGeneration)) {
|
|
109
|
-
throw new Error(`Invalid --generation value: ${generationArg}`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
selectedRuns: runs.filter((run) => getRunGeneration(run) === requestedGeneration),
|
|
114
|
-
generationLabel:
|
|
115
|
-
generationArg === 'latest'
|
|
116
|
-
? `latest benchmark generation (${requestedGeneration})`
|
|
117
|
-
: `benchmark generation ${requestedGeneration}`,
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
selectedRuns: runs,
|
|
123
|
-
generationLabel: 'all benchmark generations',
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function buildMarkdownReport(result) {
|
|
128
|
-
const lines = [
|
|
129
|
-
'# Aggregated React Tooltip Scaling Benchmark',
|
|
130
|
-
'',
|
|
131
|
-
`- Timestamp: ${result.timestamp}`,
|
|
132
|
-
`- Input files: ${result.inputFiles.length}`,
|
|
133
|
-
`- Selection: ${result.selection}`,
|
|
134
|
-
`- Generation filter: ${result.generationFilter}`,
|
|
135
|
-
`- Counts: ${result.counts.join(', ')}`,
|
|
136
|
-
'',
|
|
137
|
-
'| Count | V5 mount | V6 mount | Mount delta | Mount spread | V5 unmount | V6 unmount | Unmount delta | Unmount spread | V5 mount mem | V6 mount mem | Mount mem delta | Mount mem spread | V5 unmount mem | V6 unmount mem | Unmount mem delta | Unmount mem spread | Samples | V5 timeouts | V6 timeouts |',
|
|
138
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
result.summary.forEach((row) => {
|
|
142
|
-
lines.push(
|
|
143
|
-
`| ${row.count} | ${formatMs(row.v5.mount.median)} | ${formatMs(row.v6.mount.median)} | ${formatMs(row.mountDeltaMs)} | ${formatPercent(row.mountDeltaSpreadPercent)} | ${formatMs(row.v5.unmount.median)} | ${formatMs(row.v6.unmount.median)} | ${formatMs(row.unmountDeltaMs)} | ${formatPercent(row.unmountDeltaSpreadPercent)} | ${formatBytes(row.v5.mountMemory.median)} | ${formatBytes(row.v6.mountMemory.median)} | ${formatBytes(row.mountMemoryDeltaBytes)} | ${formatPercent(row.mountMemoryDeltaSpreadPercent)} | ${formatBytes(row.v5.unmountMemory.median)} | ${formatBytes(row.v6.unmountMemory.median)} | ${formatBytes(row.unmountMemoryDeltaBytes)} | ${formatPercent(row.unmountMemoryDeltaSpreadPercent)} | ${row.sampleCount} | ${row.v5.timeoutCount} | ${row.v6.timeoutCount} |`,
|
|
144
|
-
)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
return `${lines.join('\n')}\n`
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function main() {
|
|
151
|
-
const inputFiles = await resolveInputFiles()
|
|
152
|
-
|
|
153
|
-
if (inputFiles.length === 0) {
|
|
154
|
-
throw new Error('No benchmark result files were found to aggregate.')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const allRuns = await Promise.all(
|
|
158
|
-
inputFiles.map(async (inputFile) => JSON.parse(await readFile(inputFile, 'utf8'))),
|
|
159
|
-
)
|
|
160
|
-
const { selectedRuns: runs, generationLabel } = resolveGenerationSelection(allRuns)
|
|
161
|
-
|
|
162
|
-
if (runs.length === 0) {
|
|
163
|
-
throw new Error('No benchmark result files matched the requested generation filter.')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const counts = Array.from(
|
|
167
|
-
new Set(runs.flatMap((run) => run.counts ?? [])),
|
|
168
|
-
).sort((left, right) => left - right)
|
|
169
|
-
|
|
170
|
-
const summary = counts.map((count) => {
|
|
171
|
-
const rows = runs
|
|
172
|
-
.map((run) => run.summary?.find((entry) => entry.count === count))
|
|
173
|
-
.filter(Boolean)
|
|
174
|
-
|
|
175
|
-
const aggregateVersion = (version) => ({
|
|
176
|
-
mount: aggregateNumbers(rows.map((row) => row[version]?.mount?.median)),
|
|
177
|
-
unmount: aggregateNumbers(rows.map((row) => row[version]?.unmount?.median)),
|
|
178
|
-
mountMemory: aggregateNumbers(rows.map((row) => row[version]?.mountMemory?.median)),
|
|
179
|
-
unmountMemory: aggregateNumbers(rows.map((row) => row[version]?.unmountMemory?.median)),
|
|
180
|
-
timeoutCount: rows.reduce(
|
|
181
|
-
(total, row) => total + (row[version]?.timeoutCount ?? 0),
|
|
182
|
-
0,
|
|
183
|
-
),
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const v5 = aggregateVersion('v5')
|
|
187
|
-
const v6 = aggregateVersion('v6')
|
|
188
|
-
const sampleCount = Math.max(v5.mount.sampleCount, v6.mount.sampleCount)
|
|
189
|
-
|
|
190
|
-
const mountDeltaMs =
|
|
191
|
-
typeof v5.mount.median === 'number' && typeof v6.mount.median === 'number'
|
|
192
|
-
? v6.mount.median - v5.mount.median
|
|
193
|
-
: null
|
|
194
|
-
const unmountDeltaMs =
|
|
195
|
-
typeof v5.unmount.median === 'number' && typeof v6.unmount.median === 'number'
|
|
196
|
-
? v6.unmount.median - v5.unmount.median
|
|
197
|
-
: null
|
|
198
|
-
const mountMemoryDeltaBytes =
|
|
199
|
-
typeof v5.mountMemory.median === 'number' && typeof v6.mountMemory.median === 'number'
|
|
200
|
-
? v6.mountMemory.median - v5.mountMemory.median
|
|
201
|
-
: null
|
|
202
|
-
const unmountMemoryDeltaBytes =
|
|
203
|
-
typeof v5.unmountMemory.median === 'number' && typeof v6.unmountMemory.median === 'number'
|
|
204
|
-
? v6.unmountMemory.median - v5.unmountMemory.median
|
|
205
|
-
: null
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
count,
|
|
209
|
-
sampleCount,
|
|
210
|
-
v5,
|
|
211
|
-
v6,
|
|
212
|
-
mountDeltaMs,
|
|
213
|
-
unmountDeltaMs,
|
|
214
|
-
mountMemoryDeltaBytes,
|
|
215
|
-
unmountMemoryDeltaBytes,
|
|
216
|
-
mountDeltaSpreadPercent:
|
|
217
|
-
typeof v5.mount.spreadPercent === 'number' && typeof v6.mount.spreadPercent === 'number'
|
|
218
|
-
? Math.max(v5.mount.spreadPercent, v6.mount.spreadPercent)
|
|
219
|
-
: null,
|
|
220
|
-
unmountDeltaSpreadPercent:
|
|
221
|
-
typeof v5.unmount.spreadPercent === 'number' && typeof v6.unmount.spreadPercent === 'number'
|
|
222
|
-
? Math.max(v5.unmount.spreadPercent, v6.unmount.spreadPercent)
|
|
223
|
-
: null,
|
|
224
|
-
mountMemoryDeltaSpreadPercent:
|
|
225
|
-
typeof v5.mountMemory.spreadPercent === 'number' && typeof v6.mountMemory.spreadPercent === 'number'
|
|
226
|
-
? Math.max(v5.mountMemory.spreadPercent, v6.mountMemory.spreadPercent)
|
|
227
|
-
: null,
|
|
228
|
-
unmountMemoryDeltaSpreadPercent:
|
|
229
|
-
typeof v5.unmountMemory.spreadPercent === 'number' && typeof v6.unmountMemory.spreadPercent === 'number'
|
|
230
|
-
? Math.max(v5.unmountMemory.spreadPercent, v6.unmountMemory.spreadPercent)
|
|
231
|
-
: null,
|
|
232
|
-
}
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
const result = {
|
|
236
|
-
id: `aggregate-scaling-${timestampId()}`,
|
|
237
|
-
timestamp: new Date().toISOString(),
|
|
238
|
-
selection: useAll ? 'all scaling runs' : `latest ${latest} scaling run(s)`,
|
|
239
|
-
generationFilter: generationLabel,
|
|
240
|
-
inputFiles: runs.map((run, index) => inputFiles[allRuns.indexOf(run)] ?? inputFiles[index]),
|
|
241
|
-
counts,
|
|
242
|
-
summary,
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const jsonPath = path.join(resultsDir, `${result.id}.json`)
|
|
246
|
-
const markdownPath = path.join(resultsDir, `${result.id}.md`)
|
|
247
|
-
|
|
248
|
-
await writeFile(jsonPath, `${JSON.stringify(result, null, 2)}\n`, 'utf8')
|
|
249
|
-
await writeFile(markdownPath, buildMarkdownReport(result), 'utf8')
|
|
250
|
-
|
|
251
|
-
console.log(`Saved aggregate benchmark results to ${jsonPath}`)
|
|
252
|
-
console.log(`Saved aggregate benchmark report to ${markdownPath}`)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
main().catch((error) => {
|
|
256
|
-
console.error(error)
|
|
257
|
-
process.exitCode = 1
|
|
258
|
-
})
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import { flushSync } from 'react-dom'
|
|
3
|
-
import { createRoot } from 'react-dom/client'
|
|
4
|
-
import { Tooltip as TooltipV5 } from '../../docs/node_modules/react-tooltip/dist/react-tooltip.mjs'
|
|
5
|
-
import { Tooltip as TooltipV6 } from '../../dist/react-tooltip.esm.js'
|
|
6
|
-
|
|
7
|
-
type BenchmarkVersion = 'v5' | 'v6'
|
|
8
|
-
|
|
9
|
-
type RenderMode = 'shared'
|
|
10
|
-
|
|
11
|
-
type FixtureState = {
|
|
12
|
-
version: BenchmarkVersion
|
|
13
|
-
count: number
|
|
14
|
-
renderMode: RenderMode
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type ScenarioSample = {
|
|
18
|
-
count: number
|
|
19
|
-
mountDurationMs: number | null
|
|
20
|
-
unmountDurationMs: number | null
|
|
21
|
-
mountMemoryDeltaBytes: number | null
|
|
22
|
-
unmountMemoryDeltaBytes: number | null
|
|
23
|
-
timedOut: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type ScenarioResult = {
|
|
27
|
-
version: BenchmarkVersion
|
|
28
|
-
renderMode: RenderMode
|
|
29
|
-
samplesByCount: ScenarioSample[]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type BenchmarkHarness = {
|
|
33
|
-
runScalingBenchmark: (args: {
|
|
34
|
-
version: BenchmarkVersion
|
|
35
|
-
counts: number[]
|
|
36
|
-
timeoutMs: number
|
|
37
|
-
repeats: number
|
|
38
|
-
warmups: number
|
|
39
|
-
renderMode: RenderMode
|
|
40
|
-
onProgress?: (message: string) => void
|
|
41
|
-
}) => Promise<ScenarioResult>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
declare global {
|
|
45
|
-
interface Window {
|
|
46
|
-
__reactTooltipBenchmark?: BenchmarkHarness
|
|
47
|
-
__REACT_DEVTOOLS_GLOBAL_HOOK__?: {
|
|
48
|
-
isDisabled?: boolean
|
|
49
|
-
}
|
|
50
|
-
gc?: () => void
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
55
|
-
isDisabled: true,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function readUsedHeapBytes() {
|
|
59
|
-
const performanceWithMemory = window.performance as Performance & {
|
|
60
|
-
memory?: {
|
|
61
|
-
usedJSHeapSize: number
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return performanceWithMemory.memory?.usedJSHeapSize ?? null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function nextFrame() {
|
|
69
|
-
return new Promise<void>((resolve) => {
|
|
70
|
-
window.requestAnimationFrame(() => resolve())
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function collectGarbage() {
|
|
75
|
-
if (typeof window.gc === 'function') {
|
|
76
|
-
window.gc()
|
|
77
|
-
await nextFrame()
|
|
78
|
-
window.gc()
|
|
79
|
-
await nextFrame()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function readStableHeapBytes() {
|
|
84
|
-
await collectGarbage()
|
|
85
|
-
return readUsedHeapBytes()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function waitUntil(predicate: () => boolean, timeoutMs: number) {
|
|
89
|
-
const startedAt = window.performance.now()
|
|
90
|
-
|
|
91
|
-
while (window.performance.now() - startedAt < timeoutMs) {
|
|
92
|
-
if (predicate()) {
|
|
93
|
-
return true
|
|
94
|
-
}
|
|
95
|
-
await nextFrame()
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function BenchmarkFixture({ version, count }: FixtureState) {
|
|
102
|
-
const TooltipComponent = version === 'v5' ? TooltipV5 : TooltipV6
|
|
103
|
-
const tooltipId = `benchmark-tooltip-${version}`
|
|
104
|
-
|
|
105
|
-
const anchorIds = useMemo(
|
|
106
|
-
() => Array.from({ length: count }, (_, index) => `anchor-${version}-${index}`),
|
|
107
|
-
[count, version],
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
document.body.setAttribute('data-benchmark-count', String(count))
|
|
112
|
-
document.body.setAttribute('data-benchmark-version', version)
|
|
113
|
-
}, [count, version])
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<div className="benchmark-surface" aria-hidden="true">
|
|
117
|
-
<div className="anchor-grid">
|
|
118
|
-
{anchorIds.map((id, index) => (
|
|
119
|
-
<button
|
|
120
|
-
key={id}
|
|
121
|
-
id={id}
|
|
122
|
-
className="anchor"
|
|
123
|
-
data-tooltip-id={tooltipId}
|
|
124
|
-
data-tooltip-content={`Anchor ${index}`}
|
|
125
|
-
type="button"
|
|
126
|
-
>
|
|
127
|
-
{index}
|
|
128
|
-
</button>
|
|
129
|
-
))}
|
|
130
|
-
</div>
|
|
131
|
-
<TooltipComponent id={tooltipId} />
|
|
132
|
-
</div>
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const rootNode = document.getElementById('root')
|
|
137
|
-
|
|
138
|
-
if (!rootNode) {
|
|
139
|
-
throw new Error('Benchmark root element was not found.')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const benchmarkRoot = createRoot(rootNode)
|
|
143
|
-
|
|
144
|
-
function renderFixture(nextState: FixtureState) {
|
|
145
|
-
return new Promise<void>((resolve) => {
|
|
146
|
-
flushSync(() => {
|
|
147
|
-
benchmarkRoot.render(<BenchmarkFixture {...nextState} />)
|
|
148
|
-
})
|
|
149
|
-
resolve()
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function unmountFixture() {
|
|
154
|
-
return new Promise<void>((resolve) => {
|
|
155
|
-
flushSync(() => {
|
|
156
|
-
benchmarkRoot.render(<></>)
|
|
157
|
-
})
|
|
158
|
-
resolve()
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function runScalingBenchmark({
|
|
163
|
-
version,
|
|
164
|
-
counts,
|
|
165
|
-
timeoutMs,
|
|
166
|
-
repeats,
|
|
167
|
-
warmups,
|
|
168
|
-
renderMode,
|
|
169
|
-
onProgress,
|
|
170
|
-
}: {
|
|
171
|
-
version: BenchmarkVersion
|
|
172
|
-
counts: number[]
|
|
173
|
-
timeoutMs: number
|
|
174
|
-
repeats: number
|
|
175
|
-
warmups: number
|
|
176
|
-
renderMode: RenderMode
|
|
177
|
-
onProgress?: (message: string) => void
|
|
178
|
-
}): Promise<ScenarioResult> {
|
|
179
|
-
const samplesByCount: ScenarioSample[] = []
|
|
180
|
-
|
|
181
|
-
for (const count of counts) {
|
|
182
|
-
for (let warmupIndex = 0; warmupIndex < warmups; warmupIndex += 1) {
|
|
183
|
-
onProgress?.(`count=${count} warmup ${warmupIndex + 1}/${warmups}`)
|
|
184
|
-
await renderFixture({
|
|
185
|
-
version,
|
|
186
|
-
count: 0,
|
|
187
|
-
renderMode,
|
|
188
|
-
})
|
|
189
|
-
await nextFrame()
|
|
190
|
-
await renderFixture({
|
|
191
|
-
version,
|
|
192
|
-
count,
|
|
193
|
-
renderMode,
|
|
194
|
-
})
|
|
195
|
-
await waitUntil(
|
|
196
|
-
() => document.querySelectorAll('[data-tooltip-id]').length === count,
|
|
197
|
-
timeoutMs,
|
|
198
|
-
)
|
|
199
|
-
await nextFrame()
|
|
200
|
-
await unmountFixture()
|
|
201
|
-
await nextFrame()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
for (let repeatIndex = 0; repeatIndex < repeats; repeatIndex += 1) {
|
|
205
|
-
onProgress?.(`count=${count} repeat ${repeatIndex + 1}/${repeats}`)
|
|
206
|
-
await renderFixture({
|
|
207
|
-
version,
|
|
208
|
-
count: 0,
|
|
209
|
-
renderMode,
|
|
210
|
-
})
|
|
211
|
-
await nextFrame()
|
|
212
|
-
|
|
213
|
-
const mountMemoryBefore = await readStableHeapBytes()
|
|
214
|
-
const mountStartedAt = window.performance.now()
|
|
215
|
-
|
|
216
|
-
await renderFixture({
|
|
217
|
-
version,
|
|
218
|
-
count,
|
|
219
|
-
renderMode,
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const mountReady = await waitUntil(() => {
|
|
223
|
-
return document.querySelectorAll('[data-tooltip-id]').length === count
|
|
224
|
-
}, timeoutMs)
|
|
225
|
-
|
|
226
|
-
await nextFrame()
|
|
227
|
-
|
|
228
|
-
const mountEndedAt = window.performance.now()
|
|
229
|
-
|
|
230
|
-
const mountMemoryAfter = await readStableHeapBytes()
|
|
231
|
-
const unmountMemoryBefore = mountMemoryAfter
|
|
232
|
-
const unmountStartedAt = window.performance.now()
|
|
233
|
-
|
|
234
|
-
await unmountFixture()
|
|
235
|
-
|
|
236
|
-
const unmountReady = await waitUntil(() => {
|
|
237
|
-
return document.querySelectorAll('[data-tooltip-id]').length === 0
|
|
238
|
-
}, timeoutMs)
|
|
239
|
-
|
|
240
|
-
await nextFrame()
|
|
241
|
-
|
|
242
|
-
const unmountEndedAt = window.performance.now()
|
|
243
|
-
|
|
244
|
-
const unmountMemoryAfter = await readStableHeapBytes()
|
|
245
|
-
|
|
246
|
-
samplesByCount.push({
|
|
247
|
-
count,
|
|
248
|
-
mountDurationMs: mountReady ? mountEndedAt - mountStartedAt : null,
|
|
249
|
-
unmountDurationMs: unmountReady ? unmountEndedAt - unmountStartedAt : null,
|
|
250
|
-
mountMemoryDeltaBytes:
|
|
251
|
-
mountReady && mountMemoryBefore !== null && mountMemoryAfter !== null
|
|
252
|
-
? mountMemoryAfter - mountMemoryBefore
|
|
253
|
-
: null,
|
|
254
|
-
unmountMemoryDeltaBytes:
|
|
255
|
-
unmountReady && unmountMemoryBefore !== null && unmountMemoryAfter !== null
|
|
256
|
-
? unmountMemoryAfter - unmountMemoryBefore
|
|
257
|
-
: null,
|
|
258
|
-
timedOut: !mountReady || !unmountReady,
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
version,
|
|
265
|
-
renderMode,
|
|
266
|
-
samplesByCount,
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
window.__reactTooltipBenchmark = {
|
|
271
|
-
runScalingBenchmark,
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
renderFixture({
|
|
275
|
-
version: 'v6',
|
|
276
|
-
count: 0,
|
|
277
|
-
renderMode: 'shared',
|
|
278
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>React Tooltip Benchmark</title>
|
|
7
|
-
<style>
|
|
8
|
-
html,
|
|
9
|
-
body {
|
|
10
|
-
margin: 0;
|
|
11
|
-
padding: 0;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
body {
|
|
15
|
-
font-family: sans-serif;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
#root {
|
|
19
|
-
min-height: 100vh;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.benchmark-surface {
|
|
23
|
-
left: 24px;
|
|
24
|
-
opacity: 0.02;
|
|
25
|
-
pointer-events: none;
|
|
26
|
-
position: fixed;
|
|
27
|
-
top: 24px;
|
|
28
|
-
width: 1200px;
|
|
29
|
-
z-index: -1;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.anchor-grid {
|
|
33
|
-
display: grid;
|
|
34
|
-
gap: 8px;
|
|
35
|
-
grid-template-columns: repeat(10, minmax(0, 1fr));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.anchor {
|
|
39
|
-
align-items: center;
|
|
40
|
-
background: #111827;
|
|
41
|
-
border: 0;
|
|
42
|
-
border-radius: 6px;
|
|
43
|
-
color: #fff;
|
|
44
|
-
display: inline-flex;
|
|
45
|
-
height: 28px;
|
|
46
|
-
justify-content: center;
|
|
47
|
-
}
|
|
48
|
-
</style>
|
|
49
|
-
</head>
|
|
50
|
-
<body>
|
|
51
|
-
<div id="root"></div>
|
|
52
|
-
<script type="module" src="/app.js"></script>
|
|
53
|
-
</body>
|
|
54
|
-
</html>
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'url'
|
|
4
|
-
import * as esbuild from 'esbuild'
|
|
5
|
-
import minimist from 'minimist'
|
|
6
|
-
import { chromium } from 'playwright'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const rootDir = path.resolve(__dirname, '..')
|
|
10
|
-
const cacheDir = path.join(__dirname, '.cache')
|
|
11
|
-
const resultsDir = path.join(__dirname, 'results')
|
|
12
|
-
const fixtureHtmlPath = path.join(__dirname, 'fixture', 'index.html')
|
|
13
|
-
const fixtureEntryPath = path.join(__dirname, 'fixture', 'app.tsx')
|
|
14
|
-
const cacheHtmlPath = path.join(cacheDir, 'index.html')
|
|
15
|
-
const fixtureBundlePath = path.join(cacheDir, 'app.js')
|
|
16
|
-
const rootReactPath = path.join(rootDir, 'node_modules', 'react', 'index.js')
|
|
17
|
-
const rootReactDomPath = path.join(rootDir, 'node_modules', 'react-dom', 'index.js')
|
|
18
|
-
const benchmarkVersion = 3
|
|
19
|
-
const benchmarkLabel = 'precise-memory-gc-separated'
|
|
20
|
-
|
|
21
|
-
const args = minimist(process.argv.slice(2))
|
|
22
|
-
const counts = `${args.counts ?? '50,100,500,2000,5000,10000,25000'}`
|
|
23
|
-
.split(',')
|
|
24
|
-
.map((value) => Number(value.trim()))
|
|
25
|
-
.filter((value) => Number.isFinite(value) && value > 0)
|
|
26
|
-
const timeoutMs = Number(args.timeoutMs ?? 1200)
|
|
27
|
-
const repeats = Number(args.repeats ?? 5)
|
|
28
|
-
const warmups = Number(args.warmups ?? 1)
|
|
29
|
-
const executablePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
|
30
|
-
|
|
31
|
-
function aggregateNumbers(values) {
|
|
32
|
-
const sorted = [...values].sort((left, right) => left - right)
|
|
33
|
-
if (sorted.length === 0) {
|
|
34
|
-
return {
|
|
35
|
-
median: null,
|
|
36
|
-
p95: null,
|
|
37
|
-
min: null,
|
|
38
|
-
max: null,
|
|
39
|
-
mean: null,
|
|
40
|
-
standardDeviation: null,
|
|
41
|
-
spreadPercent: null,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const middle = Math.floor(sorted.length / 2)
|
|
46
|
-
const median =
|
|
47
|
-
sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]
|
|
48
|
-
const mean = sorted.reduce((total, value) => total + value, 0) / sorted.length
|
|
49
|
-
const variance = sorted.reduce((total, value) => total + (value - mean) ** 2, 0) / sorted.length
|
|
50
|
-
const standardDeviation = Math.sqrt(variance)
|
|
51
|
-
const p95 = sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.95) - 1)]
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
median,
|
|
55
|
-
p95,
|
|
56
|
-
min: sorted[0],
|
|
57
|
-
max: sorted[sorted.length - 1],
|
|
58
|
-
mean,
|
|
59
|
-
standardDeviation,
|
|
60
|
-
spreadPercent: median === 0 ? null : ((p95 - median) / Math.abs(median)) * 100,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function formatMs(value) {
|
|
65
|
-
return typeof value === 'number' ? `${value.toFixed(2)} ms` : 'timeout'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function formatBytes(value) {
|
|
69
|
-
return typeof value === 'number' ? `${(value / 1024).toFixed(1)} KiB` : '—'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function buildMarkdownReport(result) {
|
|
73
|
-
const lines = [
|
|
74
|
-
'# React Tooltip Scaling Benchmark',
|
|
75
|
-
'',
|
|
76
|
-
`- Timestamp: ${result.timestamp}`,
|
|
77
|
-
`- Browser: ${result.browser}`,
|
|
78
|
-
`- Counts: ${result.counts.join(', ')}`,
|
|
79
|
-
`- Warmups: ${result.warmups}`,
|
|
80
|
-
`- Repeats: ${result.repeats}`,
|
|
81
|
-
`- Timeout: ${result.timeoutMs} ms`,
|
|
82
|
-
'',
|
|
83
|
-
'| Count | V5 mount | V6 mount | Mount delta | V5 unmount | V6 unmount | Unmount delta | V5 mount mem | V6 mount mem | V5 issues | V6 issues |',
|
|
84
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
result.summary.forEach((row) => {
|
|
88
|
-
lines.push(
|
|
89
|
-
`| ${row.count} | ${formatMs(row.v5.mount.median)} | ${formatMs(row.v6.mount.median)} | ${formatMs(row.mountDeltaMs)} | ${formatMs(row.v5.unmount.median)} | ${formatMs(row.v6.unmount.median)} | ${formatMs(row.unmountDeltaMs)} | ${formatBytes(row.v5.mountMemory.median)} | ${formatBytes(row.v6.mountMemory.median)} | ${row.v5.timeoutCount} | ${row.v6.timeoutCount} |`,
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
return `${lines.join('\n')}\n`
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function logProgress(message) {
|
|
97
|
-
const timestamp = new Date().toLocaleTimeString()
|
|
98
|
-
console.log(`[benchmark ${timestamp}] ${message}`)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function timestampId() {
|
|
102
|
-
return new Date().toISOString().replace(/[:.]/g, '-')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function ensureFixtureBundle() {
|
|
106
|
-
await mkdir(cacheDir, { recursive: true })
|
|
107
|
-
|
|
108
|
-
await esbuild.build({
|
|
109
|
-
entryPoints: [fixtureEntryPath],
|
|
110
|
-
outfile: fixtureBundlePath,
|
|
111
|
-
bundle: true,
|
|
112
|
-
format: 'iife',
|
|
113
|
-
sourcemap: false,
|
|
114
|
-
platform: 'browser',
|
|
115
|
-
alias: {
|
|
116
|
-
react: rootReactPath,
|
|
117
|
-
'react-dom': rootReactDomPath,
|
|
118
|
-
},
|
|
119
|
-
jsx: 'transform',
|
|
120
|
-
jsxFactory: 'React.createElement',
|
|
121
|
-
jsxFragment: 'React.Fragment',
|
|
122
|
-
loader: {
|
|
123
|
-
'.js': 'jsx',
|
|
124
|
-
'.ts': 'ts',
|
|
125
|
-
'.tsx': 'tsx',
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const html = await readFile(fixtureHtmlPath, 'utf8')
|
|
130
|
-
await writeFile(
|
|
131
|
-
cacheHtmlPath,
|
|
132
|
-
html.replace('<script type="module" src="/app.js"></script>', ''),
|
|
133
|
-
'utf8',
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function main() {
|
|
138
|
-
logProgress('Preparing benchmark bundle')
|
|
139
|
-
await rm(cacheDir, { recursive: true, force: true })
|
|
140
|
-
await mkdir(resultsDir, { recursive: true })
|
|
141
|
-
await ensureFixtureBundle()
|
|
142
|
-
logProgress('Launching headless Chrome')
|
|
143
|
-
|
|
144
|
-
const browser = await chromium.launch({
|
|
145
|
-
executablePath,
|
|
146
|
-
headless: true,
|
|
147
|
-
args: ['--enable-precise-memory-info', '--js-flags=--expose-gc'],
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const page = await browser.newPage()
|
|
152
|
-
page.on('console', (message) => {
|
|
153
|
-
const text = message.text()
|
|
154
|
-
if (text.includes('Download the React DevTools')) {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
console.log(`[page:${message.type()}] ${text}`)
|
|
158
|
-
})
|
|
159
|
-
page.on('pageerror', (error) => {
|
|
160
|
-
console.error(`[pageerror] ${error.message}`)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
logProgress('Loading benchmark fixture')
|
|
164
|
-
await page.goto(pathToFileURL(cacheHtmlPath).toString(), {
|
|
165
|
-
waitUntil: 'networkidle',
|
|
166
|
-
})
|
|
167
|
-
await page.addScriptTag({
|
|
168
|
-
content: await readFile(fixtureBundlePath, 'utf8'),
|
|
169
|
-
})
|
|
170
|
-
await page.waitForFunction(() => Boolean(window.__reactTooltipBenchmark), null, {
|
|
171
|
-
timeout: timeoutMs,
|
|
172
|
-
})
|
|
173
|
-
logProgress(`Running scaling benchmark for counts: ${counts.join(', ')}`)
|
|
174
|
-
|
|
175
|
-
const runVersion = async (version) => {
|
|
176
|
-
logProgress(`Starting ${version.toUpperCase()} run`)
|
|
177
|
-
return page.evaluate(
|
|
178
|
-
async ({ nextVersion, nextCounts, nextTimeoutMs, nextRepeats, nextWarmups }) => {
|
|
179
|
-
const harness = window.__reactTooltipBenchmark
|
|
180
|
-
if (!harness) {
|
|
181
|
-
throw new Error('Benchmark harness was not initialized.')
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const progressPrefix = `[${nextVersion.toUpperCase()}]`
|
|
185
|
-
|
|
186
|
-
return harness.runScalingBenchmark({
|
|
187
|
-
version: nextVersion,
|
|
188
|
-
counts: nextCounts,
|
|
189
|
-
timeoutMs: nextTimeoutMs,
|
|
190
|
-
repeats: nextRepeats,
|
|
191
|
-
warmups: nextWarmups,
|
|
192
|
-
renderMode: 'shared',
|
|
193
|
-
onProgress: (message) => {
|
|
194
|
-
console.log(`${progressPrefix} ${message}`)
|
|
195
|
-
},
|
|
196
|
-
})
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
nextVersion: version,
|
|
200
|
-
nextCounts: counts,
|
|
201
|
-
nextTimeoutMs: timeoutMs,
|
|
202
|
-
nextRepeats: repeats,
|
|
203
|
-
nextWarmups: warmups,
|
|
204
|
-
},
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const v5 = await runVersion('v5')
|
|
209
|
-
logProgress('Completed V5 run')
|
|
210
|
-
const v6 = await runVersion('v6')
|
|
211
|
-
logProgress('Completed V6 run')
|
|
212
|
-
|
|
213
|
-
const summary = counts.map((count) => {
|
|
214
|
-
const v5Samples = v5.samplesByCount.filter((sample) => sample.count === count)
|
|
215
|
-
const v6Samples = v6.samplesByCount.filter((sample) => sample.count === count)
|
|
216
|
-
|
|
217
|
-
const aggregateVersion = (samples) => {
|
|
218
|
-
const successful = samples.filter((sample) => !sample.timedOut)
|
|
219
|
-
return {
|
|
220
|
-
mount: aggregateNumbers(
|
|
221
|
-
successful
|
|
222
|
-
.map((sample) => sample.mountDurationMs)
|
|
223
|
-
.filter((value) => typeof value === 'number'),
|
|
224
|
-
),
|
|
225
|
-
unmount: aggregateNumbers(
|
|
226
|
-
successful
|
|
227
|
-
.map((sample) => sample.unmountDurationMs)
|
|
228
|
-
.filter((value) => typeof value === 'number'),
|
|
229
|
-
),
|
|
230
|
-
mountMemory: aggregateNumbers(
|
|
231
|
-
successful
|
|
232
|
-
.map((sample) => sample.mountMemoryDeltaBytes)
|
|
233
|
-
.filter((value) => typeof value === 'number'),
|
|
234
|
-
),
|
|
235
|
-
unmountMemory: aggregateNumbers(
|
|
236
|
-
successful
|
|
237
|
-
.map((sample) => sample.unmountMemoryDeltaBytes)
|
|
238
|
-
.filter((value) => typeof value === 'number'),
|
|
239
|
-
),
|
|
240
|
-
timeoutCount: samples.filter((sample) => sample.timedOut).length,
|
|
241
|
-
sampleCount: samples.length,
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const v5Aggregate = aggregateVersion(v5Samples)
|
|
246
|
-
const v6Aggregate = aggregateVersion(v6Samples)
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
count,
|
|
250
|
-
v5: v5Aggregate,
|
|
251
|
-
v6: v6Aggregate,
|
|
252
|
-
mountDeltaMs:
|
|
253
|
-
typeof v5Aggregate.mount.median === 'number' &&
|
|
254
|
-
typeof v6Aggregate.mount.median === 'number'
|
|
255
|
-
? v6Aggregate.mount.median - v5Aggregate.mount.median
|
|
256
|
-
: null,
|
|
257
|
-
unmountDeltaMs:
|
|
258
|
-
typeof v5Aggregate.unmount.median === 'number' &&
|
|
259
|
-
typeof v6Aggregate.unmount.median === 'number'
|
|
260
|
-
? v6Aggregate.unmount.median - v5Aggregate.unmount.median
|
|
261
|
-
: null,
|
|
262
|
-
mountMemoryDeltaBytes:
|
|
263
|
-
typeof v5Aggregate.mountMemory.median === 'number' &&
|
|
264
|
-
typeof v6Aggregate.mountMemory.median === 'number'
|
|
265
|
-
? v6Aggregate.mountMemory.median - v5Aggregate.mountMemory.median
|
|
266
|
-
: null,
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
const result = {
|
|
271
|
-
id: `scaling-${timestampId()}`,
|
|
272
|
-
timestamp: new Date().toISOString(),
|
|
273
|
-
benchmarkVersion,
|
|
274
|
-
benchmarkLabel,
|
|
275
|
-
benchmarkFeatures: {
|
|
276
|
-
preciseMemory: true,
|
|
277
|
-
exposedGc: true,
|
|
278
|
-
gcSeparatedFromTiming: true,
|
|
279
|
-
},
|
|
280
|
-
browser: await page.evaluate(() => window.navigator.userAgent),
|
|
281
|
-
counts,
|
|
282
|
-
warmups,
|
|
283
|
-
repeats,
|
|
284
|
-
timeoutMs,
|
|
285
|
-
summary,
|
|
286
|
-
aggregates: {
|
|
287
|
-
v5MountDurationsMs: aggregateNumbers(
|
|
288
|
-
summary.map((item) => item.v5.mount.median).filter((value) => typeof value === 'number'),
|
|
289
|
-
),
|
|
290
|
-
v6MountDurationsMs: aggregateNumbers(
|
|
291
|
-
summary.map((item) => item.v6.mount.median).filter((value) => typeof value === 'number'),
|
|
292
|
-
),
|
|
293
|
-
v5UnmountDurationsMs: aggregateNumbers(
|
|
294
|
-
summary
|
|
295
|
-
.map((item) => item.v5.unmount.median)
|
|
296
|
-
.filter((value) => typeof value === 'number'),
|
|
297
|
-
),
|
|
298
|
-
v6UnmountDurationsMs: aggregateNumbers(
|
|
299
|
-
summary
|
|
300
|
-
.map((item) => item.v6.unmount.median)
|
|
301
|
-
.filter((value) => typeof value === 'number'),
|
|
302
|
-
),
|
|
303
|
-
},
|
|
304
|
-
raw: {
|
|
305
|
-
v5,
|
|
306
|
-
v6,
|
|
307
|
-
},
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const outputPath = path.join(resultsDir, `${result.id}.json`)
|
|
311
|
-
const reportPath = path.join(resultsDir, `${result.id}.md`)
|
|
312
|
-
await writeFile(outputPath, `${JSON.stringify(result, null, 2)}\n`, 'utf8')
|
|
313
|
-
await writeFile(reportPath, buildMarkdownReport(result), 'utf8')
|
|
314
|
-
logProgress(`Saved benchmark results to ${outputPath}`)
|
|
315
|
-
logProgress(`Saved benchmark report to ${reportPath}`)
|
|
316
|
-
} finally {
|
|
317
|
-
await browser.close()
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
main().catch((error) => {
|
|
322
|
-
console.error(error)
|
|
323
|
-
process.exitCode = 1
|
|
324
|
-
})
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process'
|
|
2
|
-
import minimist from 'minimist'
|
|
3
|
-
|
|
4
|
-
const args = minimist(process.argv.slice(2), {
|
|
5
|
-
alias: {
|
|
6
|
-
r: 'runs',
|
|
7
|
-
},
|
|
8
|
-
default: {
|
|
9
|
-
runs: 3,
|
|
10
|
-
},
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const runCount = Number(args.runs)
|
|
14
|
-
|
|
15
|
-
if (!Number.isInteger(runCount) || runCount <= 0) {
|
|
16
|
-
throw new Error(`Expected --runs/-r to be a positive integer, received: ${args.runs}`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const passthroughArgs = process.argv.slice(2).filter((arg, index, allArgs) => {
|
|
20
|
-
if (arg === '-r' || arg === '--runs') {
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const previousArg = allArgs[index - 1]
|
|
25
|
-
if (previousArg === '-r' || previousArg === '--runs') {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return true
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
function formatStep(step, total, label) {
|
|
33
|
-
return `[series ${step}/${total}] ${label}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function runCommand(command, commandArgs) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const child = spawn(command, commandArgs, {
|
|
39
|
-
stdio: 'inherit',
|
|
40
|
-
shell: false,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
child.on('error', reject)
|
|
44
|
-
child.on('exit', (code, signal) => {
|
|
45
|
-
if (code === 0) {
|
|
46
|
-
resolve()
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
reject(
|
|
51
|
-
new Error(
|
|
52
|
-
signal
|
|
53
|
-
? `${command} ${commandArgs.join(' ')} exited with signal ${signal}`
|
|
54
|
-
: `${command} ${commandArgs.join(' ')} exited with code ${code}`,
|
|
55
|
-
),
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function main() {
|
|
62
|
-
console.log(formatStep(1, runCount + 2, 'Building benchmark bundle'))
|
|
63
|
-
await runCommand('yarn', ['build-esbuild'])
|
|
64
|
-
|
|
65
|
-
for (let index = 0; index < runCount; index += 1) {
|
|
66
|
-
console.log(formatStep(index + 2, runCount + 2, `Running benchmark pass ${index + 1}/${runCount}`))
|
|
67
|
-
await runCommand('node', ['./benchmarks/run-benchmark.mjs', ...passthroughArgs])
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(formatStep(runCount + 2, runCount + 2, `Aggregating latest ${runCount} run(s)`))
|
|
71
|
-
await runCommand('node', ['./benchmarks/aggregate-benchmarks.mjs', '--latest', String(runCount)])
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
main().catch((error) => {
|
|
75
|
-
console.error(error)
|
|
76
|
-
process.exitCode = 1
|
|
77
|
-
})
|
package/global.d.ts
DELETED
package/rollup.config.dev.mjs
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// import analyze from 'rollup-plugin-analyzer'
|
|
2
|
-
import commonjs from '@rollup/plugin-commonjs'
|
|
3
|
-
import filesize from 'rollup-plugin-filesize'
|
|
4
|
-
import postcss from 'rollup-plugin-postcss'
|
|
5
|
-
import progress from 'rollup-plugin-progress'
|
|
6
|
-
import browsersync from 'rollup-plugin-browsersync'
|
|
7
|
-
import html from 'rollup-plugin-html-scaffold'
|
|
8
|
-
import replace from '@rollup/plugin-replace'
|
|
9
|
-
import copy from 'rollup-plugin-copy'
|
|
10
|
-
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
11
|
-
import ts from '@rollup/plugin-typescript'
|
|
12
|
-
import typescript from 'typescript'
|
|
13
|
-
|
|
14
|
-
const input = ['src/index-dev.tsx']
|
|
15
|
-
|
|
16
|
-
const name = 'ReactTooltip'
|
|
17
|
-
|
|
18
|
-
const globals = {
|
|
19
|
-
react: 'React',
|
|
20
|
-
'react-dom': 'ReactDOM',
|
|
21
|
-
clsx: 'clsx',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const plugins = [
|
|
25
|
-
progress(),
|
|
26
|
-
html({
|
|
27
|
-
input: './public/index-rollup.html',
|
|
28
|
-
output: './build/index.html',
|
|
29
|
-
template: { appBundle: 'index.js' },
|
|
30
|
-
}),
|
|
31
|
-
replace({
|
|
32
|
-
preventAssignment: true,
|
|
33
|
-
values: {
|
|
34
|
-
'process.env.NODE_ENV': JSON.stringify('development'),
|
|
35
|
-
},
|
|
36
|
-
}),
|
|
37
|
-
postcss({
|
|
38
|
-
extract: true,
|
|
39
|
-
autoModules: true,
|
|
40
|
-
include: '**/*.css',
|
|
41
|
-
extensions: ['.css'],
|
|
42
|
-
plugins: [],
|
|
43
|
-
}),
|
|
44
|
-
nodeResolve(),
|
|
45
|
-
ts({
|
|
46
|
-
typescript,
|
|
47
|
-
tsconfig: './tsconfig.json',
|
|
48
|
-
noEmitOnError: false,
|
|
49
|
-
// declaration: true,
|
|
50
|
-
// declarationDir: './build',
|
|
51
|
-
}),
|
|
52
|
-
commonjs({
|
|
53
|
-
include: 'node_modules/**',
|
|
54
|
-
}),
|
|
55
|
-
// analyze(), // to check diff of file size when bundle
|
|
56
|
-
filesize(),
|
|
57
|
-
copy({
|
|
58
|
-
// targets: [
|
|
59
|
-
// { src: 'src/assets', dest: 'build/' },
|
|
60
|
-
// { src: 'public/manifest.json', dest: 'build/' },
|
|
61
|
-
// { src: 'public/offline.html', dest: 'build/' },
|
|
62
|
-
// ],
|
|
63
|
-
targets: [{ src: 'dist/', dest: 'build/' }],
|
|
64
|
-
verbose: true,
|
|
65
|
-
}),
|
|
66
|
-
browsersync({
|
|
67
|
-
server: 'build',
|
|
68
|
-
watch: true,
|
|
69
|
-
ui: false,
|
|
70
|
-
open: false,
|
|
71
|
-
// port: 3000,
|
|
72
|
-
// ui: {
|
|
73
|
-
// port: 3001,
|
|
74
|
-
// },
|
|
75
|
-
}),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
export default {
|
|
79
|
-
input,
|
|
80
|
-
output: {
|
|
81
|
-
file: 'build/index.js',
|
|
82
|
-
format: 'umd',
|
|
83
|
-
name,
|
|
84
|
-
globals,
|
|
85
|
-
},
|
|
86
|
-
plugins,
|
|
87
|
-
}
|
package/rollup.config.prod.mjs
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import commonjs from '@rollup/plugin-commonjs'
|
|
2
|
-
import postcss from 'rollup-plugin-postcss'
|
|
3
|
-
import progress from 'rollup-plugin-progress'
|
|
4
|
-
import replace from '@rollup/plugin-replace'
|
|
5
|
-
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
6
|
-
import ts from '@rollup/plugin-typescript'
|
|
7
|
-
import { terser } from 'rollup-plugin-terser'
|
|
8
|
-
import typescript from 'typescript'
|
|
9
|
-
import { readFileSync } from 'fs'
|
|
10
|
-
import { fileURLToPath } from 'url'
|
|
11
|
-
import { dirname, resolve } from 'path'
|
|
12
|
-
import replaceBeforeSaveFile from './rollup-plugins/replace-before-save-file.js'
|
|
13
|
-
|
|
14
|
-
const filename = fileURLToPath(import.meta.url)
|
|
15
|
-
const dirName = dirname(filename)
|
|
16
|
-
|
|
17
|
-
// Read package.json
|
|
18
|
-
const pkg = JSON.parse(readFileSync(resolve(dirName, './package.json'), 'utf8'))
|
|
19
|
-
|
|
20
|
-
const input = ['src/index.tsx']
|
|
21
|
-
|
|
22
|
-
const name = 'ReactTooltip'
|
|
23
|
-
|
|
24
|
-
const banner = `
|
|
25
|
-
/*
|
|
26
|
-
* React Tooltip
|
|
27
|
-
* {@link https://github.com/ReactTooltip/react-tooltip}
|
|
28
|
-
* @copyright ReactTooltip Team
|
|
29
|
-
* @license MIT
|
|
30
|
-
*/`
|
|
31
|
-
|
|
32
|
-
const external = [
|
|
33
|
-
...Object.keys(pkg.peerDependencies ?? {}),
|
|
34
|
-
...Object.keys(pkg.dependencies ?? {}),
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
const buildFormats = [
|
|
38
|
-
{
|
|
39
|
-
file: 'dist/react-tooltip.mjs',
|
|
40
|
-
format: 'es',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
file: 'dist/react-tooltip.umd.js',
|
|
44
|
-
format: 'umd',
|
|
45
|
-
globals: {
|
|
46
|
-
'@floating-ui/dom': 'FloatingUIDOM',
|
|
47
|
-
react: 'React',
|
|
48
|
-
'react-dom': 'ReactDOM',
|
|
49
|
-
clsx: 'clsx',
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
file: 'dist/react-tooltip.cjs',
|
|
54
|
-
format: 'cjs',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
file: 'dist/react-tooltip.mjs',
|
|
58
|
-
format: 'es',
|
|
59
|
-
},
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
const sharedPlugins = [
|
|
63
|
-
progress(),
|
|
64
|
-
replace({
|
|
65
|
-
preventAssignment: true,
|
|
66
|
-
values: {
|
|
67
|
-
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
68
|
-
},
|
|
69
|
-
}),
|
|
70
|
-
nodeResolve(),
|
|
71
|
-
ts({
|
|
72
|
-
typescript,
|
|
73
|
-
tsconfig: './tsconfig.json',
|
|
74
|
-
noEmitOnError: false,
|
|
75
|
-
}),
|
|
76
|
-
commonjs({
|
|
77
|
-
include: 'node_modules/**',
|
|
78
|
-
}),
|
|
79
|
-
]
|
|
80
|
-
// this step is just to build the minified javascript files
|
|
81
|
-
const minifiedBuildFormats = buildFormats.map(({ file, ...rest }) => ({
|
|
82
|
-
file: file.replace(/(\.[cm]?js)$/, '.min$1'),
|
|
83
|
-
...rest,
|
|
84
|
-
minify: true,
|
|
85
|
-
plugins: [terser({ compress: { directives: false } })],
|
|
86
|
-
}))
|
|
87
|
-
|
|
88
|
-
const allBuildFormats = [...buildFormats, ...minifiedBuildFormats]
|
|
89
|
-
|
|
90
|
-
const config = allBuildFormats.map(
|
|
91
|
-
({ file, format, globals, plugins: specificPlugins, minify }) => {
|
|
92
|
-
const plugins = [
|
|
93
|
-
...sharedPlugins,
|
|
94
|
-
postcss({
|
|
95
|
-
extract: minify ? 'react-tooltip.min.css' : 'react-tooltip.css', // this will generate a specific file and override on multiples build, but the css will be the same
|
|
96
|
-
autoModules: true,
|
|
97
|
-
include: '**/*.css',
|
|
98
|
-
extensions: ['.css'],
|
|
99
|
-
plugins: [],
|
|
100
|
-
minimize: Boolean(minify),
|
|
101
|
-
}),
|
|
102
|
-
replaceBeforeSaveFile({
|
|
103
|
-
// this only works for the react-tooltip.css because it's the first file
|
|
104
|
-
// writen in our build process before the javascript files.
|
|
105
|
-
"'react-tooltip-css-placeholder'": 'file:react-tooltip.css',
|
|
106
|
-
'"react-tooltip-css-placeholder"': 'file:react-tooltip.css',
|
|
107
|
-
"'react-tooltip-core-css-placeholder'": 'file:react-tooltip.css',
|
|
108
|
-
'"react-tooltip-core-css-placeholder"': 'file:react-tooltip.css',
|
|
109
|
-
}),
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
if (specificPlugins && specificPlugins.length) {
|
|
113
|
-
plugins.push(...specificPlugins)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
input,
|
|
118
|
-
output: {
|
|
119
|
-
file,
|
|
120
|
-
format,
|
|
121
|
-
name,
|
|
122
|
-
globals,
|
|
123
|
-
sourcemap: true,
|
|
124
|
-
// Exclude the actual source content from the source map.
|
|
125
|
-
// This means that the source maps will contain references
|
|
126
|
-
// to positions in the original code, but not the source code itself.
|
|
127
|
-
sourcemapExcludeSources: true,
|
|
128
|
-
banner,
|
|
129
|
-
},
|
|
130
|
-
external,
|
|
131
|
-
plugins,
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
export default config
|
package/rollup.config.types.mjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import dts from 'rollup-plugin-dts'
|
|
2
|
-
import postcss from 'rollup-plugin-postcss'
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
input: './src/index.tsx',
|
|
6
|
-
output: [{ file: 'dist/react-tooltip.d.ts', format: 'es' }],
|
|
7
|
-
plugins: [
|
|
8
|
-
postcss({
|
|
9
|
-
extract: 'react-tooltip-tokens.css', // this will generate a specific file not being used, but we need this part of code
|
|
10
|
-
autoModules: true,
|
|
11
|
-
include: '**/*.css',
|
|
12
|
-
extensions: ['.css'],
|
|
13
|
-
plugins: [],
|
|
14
|
-
}),
|
|
15
|
-
dts(),
|
|
16
|
-
],
|
|
17
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
const reactVersion = process.argv[2]
|
|
4
|
-
|
|
5
|
-
const configs = {
|
|
6
|
-
'16.14': {
|
|
7
|
-
react: '16.14.0',
|
|
8
|
-
reactDom: '16.14.0',
|
|
9
|
-
typesReact: '^16.14.0',
|
|
10
|
-
typesReactDom: '^16.9.0',
|
|
11
|
-
testingLibraryReact: '12.1.5',
|
|
12
|
-
},
|
|
13
|
-
'17': {
|
|
14
|
-
react: '17.0.2',
|
|
15
|
-
reactDom: '17.0.2',
|
|
16
|
-
typesReact: '^17.0.0',
|
|
17
|
-
typesReactDom: '^17.0.0',
|
|
18
|
-
testingLibraryReact: '12.1.5',
|
|
19
|
-
},
|
|
20
|
-
'18': {
|
|
21
|
-
react: '18.2.0',
|
|
22
|
-
reactDom: '18.2.0',
|
|
23
|
-
typesReact: '^18.0.0',
|
|
24
|
-
typesReactDom: '^18.0.0',
|
|
25
|
-
testingLibraryReact: '14.3.1',
|
|
26
|
-
},
|
|
27
|
-
'19': {
|
|
28
|
-
react: '19.0.0',
|
|
29
|
-
reactDom: '19.0.0',
|
|
30
|
-
typesReact: '^19.0.0',
|
|
31
|
-
typesReactDom: '^19.0.0',
|
|
32
|
-
testingLibraryReact: '16.3.0',
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const selected = configs[reactVersion]
|
|
37
|
-
|
|
38
|
-
if (!selected) {
|
|
39
|
-
throw new Error(`Unsupported React version "${reactVersion}".`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const packages = [
|
|
43
|
-
`react@${selected.react}`,
|
|
44
|
-
`react-dom@${selected.reactDom}`,
|
|
45
|
-
`@types/react@${selected.typesReact}`,
|
|
46
|
-
`@types/react-dom@${selected.typesReactDom}`,
|
|
47
|
-
`@testing-library/react@${selected.testingLibraryReact}`,
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
execSync(`yarn add --dev --ignore-scripts --no-lockfile ${packages.join(' ')}`, {
|
|
51
|
-
stdio: 'inherit',
|
|
52
|
-
})
|
package/tsconfig.jest.json
DELETED