The way it was

I had a need, or a friend had a need, to benchmark some algorithms in the browser. Some things you are used to being easy, and this was one. It wasn’t.

The main benchmarking library is benchmark. It dates back many years to the days when javascript speed had an impact on loading. The rivalry between lodash and underscore. The debate on whether it was faster to run your for loop from i=0 up or from i=array.lenght down. Those where heady days.

It turns out that the benchmark library has not been receiving much love lately. It’s hard to get it running as an es6 module using webpack, which is used by React project starters like CRA and Next.js. But, it turns out there is a way to avoid all that.

The way it is

Benchmark was only last updated for node imports with require but it also works as a global that is imported directly into the browser. This is fine because I’m only using it to test an algorithm, not as a part of a larger project.

The way it to import it using a script tag on the page. In CRA you can put this in your /public/index.html page

<script src="https://cdn.jsdelivr.net/npm/lodash">
<script src="https://cdn.jsdelivr.net/npm/platform">
<script src="https://cdn.jsdelivr.net/npm/benchmark">

In Next.js you can import them into your page with the following. (You need “beforeInteractive” to ensure they load in the right order, as benchmark depends on lodash.)

<Script src="https://cdn.jsdelivr.net/npm/lodash" strategy="beforeInteractive"></Script>
<Script src="https://cdn.jsdelivr.net/npm/platform" strategy="beforeInteractive"></Script>
<Script src="https://cdn.jsdelivr.net/npm/benchmark" strategy="beforeInteractive"></Script>

Now, you can access the Benchmark object from the global window object.

##TypeScript

Using TypeScript adds wrinkle to this becuase you need to to declare a window type that includes Benchmark. You can do it by declaring an interface. But the interface will depend on Benchmark. Luckly, you can import the benchmark types.

You will still need to install benchmark, even though you will not use the code (it doesn’t work). But it will still import the types for you.

$ npm install benchmark @types/benchmark

Now, you can use this in your type definition.

import type Benchmark from 'benchmark'

declare global {
  interface Window {
    Benchmark: typeof Benchmark;
  }
}

You will also need to deal with this. (Benchmark is from an age where using this was very common.) Using this in TypeScript can be done with a special argument value in the function.

// a TypeScript function that uses a special `this` arg
function fnTyped(this: any) {
  console.log("this is defined as", this);
}

// the same function in JavaScript 
function fnJs() {
  console.log("this is defined as", this);
}

Pulling it all together.

Setup your index.js

// index.js
<head>
  ...
  <script src="https://cdn.jsdelivr.net/npm/lodash">
  <script src="https://cdn.jsdelivr.net/npm/platform">
  <script src="https://cdn.jsdelivr.net/npm/benchmark">
  ...
</head>

Install types for TypeScript

$ npm install benchmark @types/benchmark

Write your tests in a react component.

import type Benchmark from "benchmark";
import {fn1, fn2} from "./lib/my-fns";

declare global {
  interface Window {
    Benchmark: typeof Benchmark;
  }
}

export default function BenchmarkApp() {
  const handleClick = () => {
    const suite = new window.Benchmark.Suite();
    suite
      .add("benchmark fn1", {
        defer: true, // if using async or promises
        fn: async (deferred:any) => {
          await fn1(1,2,3);
          deferred.resolve();
      }})
      .add("benchmark fn2", {
        defer: true, // if using async or promises
        fn: async (deferred:any) => {
          await fn2(3,4,5);
          deferred.resolve();
      }})
      .on("cycle", function (event: Event) {
        console.log(String(event.target));
      })
      .on("complete", function (this: any) {
        console.log("Fastest is " + (this).filter("fastest").map("name"));
        // Stats for fn1
        console.log("Stats for: " + this[0].name + "\n", this[0].stats);
        // Stats for fn2
        console.log("Stats for: " + this[1].name + "\n", this[1].stats);
        console.log("Benchmarks completed.");
      })
      .run();
  }
}

Now you can use benchmark even though it hasn’t been updated for use with webpack.