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.
@@ -35,4 +35,3 @@ To this:
35
35
 
36
36
  OBS: do not commit this change or the docs will broken the deployment and will not be updated.
37
37
 
38
-
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](CONTRIBUTION.md) doc has some details.
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.6",
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
@@ -1,7 +0,0 @@
1
- declare module '*.module.css' {
2
- const classes: Record<string, string>
3
-
4
- export default classes
5
- }
6
-
7
- declare module '*.css'
@@ -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
- }
@@ -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
@@ -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
- })
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "include": [
4
- "./global.d.ts",
5
- "./src/**/*.ts",
6
- "./src/**/*.tsx",
7
- "./src/**/*.js",
8
- "./src/**/*.jsx"
9
- ],
10
- "compilerOptions": {
11
- "rootDir": ".",
12
- "ignoreDeprecations": "6.0"
13
- }
14
- }