Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Options
<path> Path to a JavaScript or TypeScript file that exports the function to be benchmarked.
--debug, -d Run a development build instead of a production build to aid debugging.
--devtools, -t Run Chrome in windowed mode with the devtools open.
--onReady Measure time not until the first render, but until `onReady` callback is not invoked from the component.
--cpuThrottle=X Run Chrome with CPU throttled X times.
--version Prints the version.
--help Prints this message.
Expand Down Expand Up @@ -77,7 +78,7 @@ Path to the benchmark file to run. See the [Usage](#usage) section for more deta
#### options

Type: `Object`
Default: `{ debug: false, devtools: false, cpuThrottle: 1 }`
Default: `{ debug: false, devtools: false, cpuThrottle: 1, onReady: false }`

Optional object containing additional options.

Expand All @@ -95,6 +96,24 @@ Default: `false`

Run Chrome in windowed mode with the devtools open.

##### onReady

Type: `boolean`<br>
Default: `false`

Measure time not until the first render, but until `onReady` callback is not invoked from the component. Useful when you have something happening inside your component after the initial render. If enabled, a special `onReady` function is passed to the component as a prop, which can be called after the component has done all the initial tasks, for example:

```tsx
// Your component

const FooComponent = ({ onReady }) => {
useEffect(() => {
doSomeHeavyCalculations().finally(() => onReady())
}, [])
return ()
}
```

##### cpuThrottle

Type: `number`<br>
Expand Down
8 changes: 7 additions & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Options
<path> Path to a JavaScript or TypeScript file that exports the function to be benchmarked.
--debug, -d Run a development build instead of a production build to aid debugging.
--devtools, -t Run Chrome in windowed mode with the devtools open.
--onReady Measure time not until the first render, but until onReady callback is not invoked from the component.
--cpuThrottle=X Run Chrome with CPU throttled X times.
--version Prints the version.
--help Prints this message.
Expand All @@ -33,6 +34,10 @@ Examples
default: false,
alias: 't',
},
onReady: {
type: 'boolean',
default: false,
},
cpuThrottle: {
type: 'number',
default: 1,
Expand All @@ -49,7 +54,7 @@ async function main() {
}

const [filepath] = cli.input
const { debug, devtools, cpuThrottle } = cli.flags
const { debug, devtools, cpuThrottle, onReady } = cli.flags

spinner = ora().start()

Expand Down Expand Up @@ -87,6 +92,7 @@ async function main() {
debug,
devtools,
cpuThrottle,
onReady,
})

spinner.stop()
Expand Down
29 changes: 22 additions & 7 deletions lib/client.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import benchmark from 'benchmark'
import lodash from 'lodash'
import React from 'react'
import ReactDOM from 'react-dom'
import testComponent from 'react-benchmark-test-component'

// Hack to make benchmark work via webpack
const Benchmark = benchmark.runInContext({ _: lodash })
window.Benchmark = Benchmark

// Render an instance in the DOM before running the benchmark to make debugging easier
const container = document.createElement('div')
ReactDOM.render(testComponent(), container)
document.body.append(container)
let container = document.createElement('div')

const bench = new Benchmark({
defer: true,
async: true,
fn(deferred) {
const container = document.createElement('div')
ReactDOM.render(testComponent(), container, () => {
container.remove()
container = document.createElement('div')
document.body.append(container)
const handleReady = () => {
if (process.env.IS_ON_READY_ENABLED !== 'true') {
return
}
deferred.resolve()
})
}
ReactDOM.render(
React.createElement(testComponent, {
onReady: process.env.IS_ON_READY_ENABLED ? handleReady : undefined,
}),
container,
() => {
if (process.env.IS_ON_READY_ENABLED === 'true') {
return
}
deferred.resolve()
}
)
},
onCycle(e) {
window.benchmarkProgress(JSON.stringify(e.target))
Expand Down
7 changes: 6 additions & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export interface RunOptions {
* Run Chrome with CPU throttled X times. Useful to receive more precise results between runs.
* @default 1
*/
cpuThrottle?: number
cpuThrottle?: number,
/**
* Measure time not until the first render, but until onReady callback is not invoked from the component.
* @default false
*/
onReady?: boolean,
}

export default class ReactBenchmark extends EventEmitter {
Expand Down
3 changes: 2 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ module.exports = class ReactBenchmark extends EventEmitter {

async run(
filepath,
{ debug = false, devtools = false, cpuThrottle = 1 } = {}
{ debug = false, devtools = false, cpuThrottle = 1, onReady = false } = {}
) {
process.env.IS_ON_READY_ENABLED = onReady
if (this.running) {
throw new Error('Benchmark is already running')
}
Expand Down
5 changes: 4 additions & 1 deletion lib/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ exports.compile = async (outputPath, benchmarkPath, debug) => {
output: {
path: outputPath,
},
plugins: [new HtmlWebpackPlugin()],
plugins: [
new HtmlWebpackPlugin(),
new webpack.EnvironmentPlugin(['IS_ON_READY_ENABLED']),
],
performance: {
hints: false,
},
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@
"react": {
"version": "detect"
}
},
"globals": {
"process": true
}
},
"prettier": {
Expand Down
9 changes: 9 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ test('throttles CPU', async (t) => {
'The difference between throttled and not throttled execution is less then normal'
)
})

test('uses onReady cb when --onReady flag is passed', async (t) => {
const binPath = path.resolve(__dirname, '../lib/cli.js')
const fixturePath = path.resolve(__dirname, 'fixtures/benchmark-onready.tsx')

const result = await execa(binPath, [fixturePath])

t.regex(result.stdout, /[0-9,]+ ops\/sec ±[0-9.]+% \(\d+ runs sampled\)/)
})
51 changes: 51 additions & 0 deletions test/fixtures/benchmark-onready.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState, useEffect } from 'react'

type BenchmarkProps = {
onReady: () => void
}

const BenchmarkOnReady: React.FC<BenchmarkProps> = ({ onReady }) => {
if (!onReady) {
throw new Error('onReady has not been passed from react-benchmark')
}
const NODES_COUNT = 2
const [nodes, setNodes] = useState<React.ReactNode[]>([])
const getNodes = (numberOfNodes: number, page: number) => {
const newNodes: React.ReactNode[] = []
for (let i = numberOfNodes * (page + 1); i > numberOfNodes * page; i--) {
newNodes.push(
<p key={i}>
{i} bottles of beer on the wall
<br />
{i} bottles of beer!
<br />
Take one down, pass it around
<br />
{i - 1} bottles of beer on the wall!
</p>
)
}
return newNodes
}
const addDefferedNodes = (
nodesCount: number,
page: number,
timeout: number
) => {
setTimeout(() => {
setNodes((n) => [...n, ...getNodes(nodesCount, page)])
}, timeout)
}

useEffect(() => {
addDefferedNodes(NODES_COUNT, 1, 1)
})
useEffect(() => {
if (nodes.length === NODES_COUNT) {
onReady()
}
}, [nodes, onReady])
return <div>{nodes}</div>
}

export default BenchmarkOnReady