Skip to content

Update and rebrand WASM Benchmark 1.5.0. #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 29, 2025
Merged
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
2 changes: 1 addition & 1 deletion examples/wasm-benchmark/LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The PSPDFKit for Web component is under a commercial license.
The Nutrient Web SDK component is under a commercial license.
Ping [email protected] for details.

The remaining benchmark code is under MIT:
Expand Down
2 changes: 1 addition & 1 deletion examples/wasm-benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ You can build an optimized version using the following command:
PUBLIC_URL="/webassembly-benchmark/" npm run build
```

Where `PUBLIC_URL` must be set according to the final URL, where the application is hosted.
Where `PUBLIC_URL` must be set according to the final URL, where the application is hosted, so the benchmark will be available at `http://your-domain.com/webassembly-benchmark/`.

## Optimizations

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<title>WebAssembly Benchmark by Nutrient</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600" rel="stylesheet">
<script src="%PUBLIC_URL%/vendor/nutrient/nutrient-viewer.js"></script>
<script src="%BASE_URL%/vendor/nutrient/nutrient-viewer.js"></script>
</head>
<body>
<noscript>
Expand All @@ -18,8 +18,9 @@
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To begin the development, run `npm run dev` or `yarn dev`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
<script type="module" src="/src/index.ts"></script>
</body>
</html>
30,011 changes: 2,092 additions & 27,919 deletions examples/wasm-benchmark/package-lock.json

Large diffs are not rendered by default.

40 changes: 14 additions & 26 deletions examples/wasm-benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@
"name": "nutrient-webassembly-benchmark",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": {
"npm": ">=8.3.0"
},
"dependencies": {
"@nutrient-sdk/viewer": "^1.1.0",
"details-polyfill": "^1.1.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-scripts": "^5.0.1"
"@nutrient-sdk/viewer": "^1.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"browserslist": [
"Firefox ESR",
Expand All @@ -28,22 +26,12 @@
"safari >= 15.4",
"ios_saf >= 15.4"
],
"overrides": {
"colors": "^1.4.0",
"minimist": "^1.2.6",
"nth-check": "^2.0.1",
"url-parse": "^1.5.9",
"handlebars": "^4.7.7",
"async@>= 3.0.0 < 3.2.2": "^3.2.2",
"async@< 2.6.4": "^2.6.4",
"loader-utils": "^2.0.4",
"json5": "^2.2.2",
"postcss": "^8.4.31",
"rollup": "^3.29.5",
"cross-spawn": "^7.0.5",
"path-to-regexp": "^0.1.12",
"cookie": "^0.7.0",
"http-proxy-middleware": "^2.0.9",
"webpack-dev-server": "^5.2.2"
"devDependencies": {
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.7.0",
"typescript": "^5.0.0",
"vite": "^7.0.5",
"vite-tsconfig-paths": "^5.1.4"
}
}
Binary file modified examples/wasm-benchmark/public/assets/default.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import "details-polyfill";

import { createBenchmark } from "./lib/tests";
import { getConfigOptionsFromURL } from "./lib/utils";
import render from "./ui/render";

import type NutrientViewer from "@nutrient-sdk/viewer";

export const NutrientWindow = window as unknown as Window & {
NutrientViewer: typeof NutrientViewer,
ga: any
}

// PDF to benchmark against.
const PDF = "./assets/default.pdf";
const PDF = import.meta.env.BASE_URL + "assets/default.pdf";

const state = {
export type AppState = {
tests: Record<string, { state: string; progress: number }>;
error: Error | unknown | null;
state: string;
nutrientScore: number;
loadTimeInNutrientScore: number;
document: ArrayBuffer | null;
licenseKey: string | null;
};

const state: AppState = {
tests: {
// We set the first test to running so we avoid a state where all is idle.
"Test-Initialization": { state: "running", progress: 0 },
Expand All @@ -30,11 +45,13 @@ render(state);
await fetch("./license-key").then((response) => response.text()),
]);

console.log(licenseKey)

const { nutrientConfig } = getConfigOptionsFromURL();

const benchmark = createBenchmark(pdf, licenseKey, nutrientConfig);

state.pdf = pdf;
state.document = pdf;
state.licenseKey = licenseKey;
render(state);

Expand All @@ -48,7 +65,7 @@ render(state);

await Promise.all(preFetchAssets);

const score = await benchmark.run((updatedTests) => {
const score = await benchmark.run((updatedTests: Record<string, { state: string; progress: number }>) => {
state.tests = updatedTests;
render(state);
});
Expand All @@ -60,16 +77,16 @@ render(state);
);
render(state);

if (window.ga) {
window.ga(
if (NutrientWindow.ga) {
NutrientWindow.ga(
"send",
"event",
"wasmbench",
"score",
"wasm-score",
state.nutrientScore,
);
window.ga(
NutrientWindow.ga(
"send",
"event",
"wasmbench",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import {
median,
} from "./utils";

export function createRunner(licenseKey) {
const tests = {};
type TestData = {
benchmarkFn: () => Promise<any>;
opts: Record<string, any>;
state: string;
progress: number;
totalTime: number;
medians: Array<{ name: string; median: number }>;
};

export function createRunner(licenseKey: string) {
const tests = {} as Record<string, TestData>;

// Register a benchmark to test
function bench(id, benchmarkFn, opts) {
if (typeof opts === "undefined") {
opts = {};
}

function bench(id: string, benchmarkFn: () => Promise<any>, opts: Record<string, any> = {}) {
tests[id] = {
benchmarkFn,
opts,
Expand All @@ -26,7 +31,7 @@ export function createRunner(licenseKey) {

// Run the test suite. The `onChange` callback will fire whenever the progress
// of a single test is changed.
async function run(onChange) {
async function run(onChange: (tests: Record<string, TestData>) => void) {
function notify() {
onChange(tests);
}
Expand All @@ -47,7 +52,7 @@ export function createRunner(licenseKey) {

notify();

const measurements = {};
const measurements: Record<string, Array<{ name: string; duration: number }>> = {};

for (let i = 0; i < totalRuns; i++) {
clearAllTimings();
Expand All @@ -56,7 +61,7 @@ export function createRunner(licenseKey) {

// benchmarkFn returns an array of performance measurement objects. In
// a first step, we filter those results.
results.forEach((result) => {
results.forEach((result: any) => {
const { name } = result;

if (!measurements[name]) {
Expand All @@ -83,7 +88,7 @@ export function createRunner(licenseKey) {
// Collect all relevant timings.
const medians = Object.keys(measurements).map((name) => ({
name,
median: median(measurements[name].map((m) => m.duration)),
median: median(measurements[name].map((m: any) => m.duration)),
}));
const totalTime = Math.round(
medians.reduce((sum, { median }) => sum + median, 0),
Expand All @@ -92,7 +97,7 @@ export function createRunner(licenseKey) {
// Add the total time of the test to the final score.
if (score.hasOwnProperty(opts.bucket)) {
if (opts.bucket !== "load" || score.load === 0) {
score[opts.bucket] += totalTime;
(score as any)[opts.bucket] += totalTime;
}
}

Expand All @@ -109,7 +114,7 @@ export function createRunner(licenseKey) {
return score;
}

function load(pdf, conf = {}) {
function load(pdf: any, conf: any = {}) {
const defaultConf = getConfigOptionsFromURL().nutrientConfig;
const configuration = Object.assign(
{
Expand All @@ -121,10 +126,10 @@ export function createRunner(licenseKey) {
conf,
);

return window.NutrientViewer.load(configuration).then((instance) => ({
return (window as any).NutrientViewer.load(configuration).then((instance: any) => ({
instance,
unload: () => {
window.NutrientViewer.unload(instance);
(window as any).NutrientViewer.unload(instance);
instance = null;
pdf = null;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NutrientWindow } from "..";
import { createRunner } from "./runner";
import { clearAllTimings, isMobileOS, isWasmSupported } from "./utils";

export function createBenchmark(pdf, licenseKey, conf) {
export function createBenchmark(pdf: any, licenseKey: any, conf: any) {
// Factory to create our test suite. It will register all tests in the runner.
const isWasm = isWasmSupported()

Expand All @@ -11,13 +12,13 @@ export function createBenchmark(pdf, licenseKey, conf) {
"Test-Rendering",
async () => {
const instance = await prepareInstance();
const totalPageCount = instance.totalPageCount;
const totalPageCount = (instance as any).totalPageCount;

const promises = [];

for (let pageIndex = 0; pageIndex < totalPageCount; pageIndex++) {
promises.push(
instance.renderPageAsArrayBuffer({ height: 1024 }, pageIndex),
(instance as any).renderPageAsArrayBuffer({ height: 1024 }, pageIndex),
);
}

Expand Down Expand Up @@ -47,7 +48,7 @@ export function createBenchmark(pdf, licenseKey, conf) {
const promises = [];

for (let i = 0; i < 50; i++) {
promises.push(instance.search("the"));
promises.push((instance as any).search("the"));
}

performance.mark("searchStart");
Expand All @@ -71,12 +72,12 @@ export function createBenchmark(pdf, licenseKey, conf) {

performance.mark("exportStart");
await Promise.all([
instance.exportPDF(),
instance.exportPDF(),
instance.exportPDF({ flatten: true }),
instance.exportPDF({ flatten: true }),
instance.exportXFDF(),
instance.exportXFDF(),
(instance as any).exportPDF(),
(instance as any).exportPDF(),
(instance as any).exportPDF({ flatten: true }),
(instance as any).exportPDF({ flatten: true }),
(instance as any).exportXFDF(),
(instance as any).exportXFDF(),
]);
performance.mark("exportEnd");

Expand All @@ -95,10 +96,10 @@ export function createBenchmark(pdf, licenseKey, conf) {
async () => {
const instance = await prepareInstance();

const annotation = new window.NutrientViewer.Annotations.TextAnnotation({
const annotation = new NutrientWindow.NutrientViewer.Annotations.TextAnnotation({
pageIndex: 0,
text: { format: "plain", value: "test" },
boundingBox: new window.NutrientViewer.Geometry.Rect({
boundingBox: new NutrientWindow.NutrientViewer.Geometry.Rect({
width: 200,
height: 30,
}),
Expand All @@ -108,15 +109,15 @@ export function createBenchmark(pdf, licenseKey, conf) {
performance.mark("createAnnotationStart");

const annotations = (
await Promise.all(range.map(() => instance.create(annotation)))
).map((createdAnnotations) => createdAnnotations[0]);
await Promise.all(range.map(() => (instance as any).create(annotation)))
).map((createdAnnotations: any) => createdAnnotations[0]);

// Nutrient Web SDK will only write annotations back to the PDF when it
// has to. To make sure this is happening, we profile the exportPDF
// endpoint.
await instance.exportPDF();
await (instance as any).exportPDF();

await Promise.all(annotations.map((a) => instance.delete(a.id)));
await Promise.all(annotations.map((a: any) => (instance as any).delete(a.id)));

performance.mark("createAnnotationEnd");

Expand Down Expand Up @@ -158,8 +159,8 @@ export function createBenchmark(pdf, licenseKey, conf) {

// We want to reuse the instance in the following tests. To achieve this, we
// store it in the function closure.
let instance;
let unload;
let instance: any | null = null;
let unload: any | null = null;

async function prepareInstance(canReuseLastOne = true, clearTimings = true) {
if (!canReuseLastOne) {
Expand All @@ -184,13 +185,15 @@ export function createBenchmark(pdf, licenseKey, conf) {
// regular.
//
// This will always return at least one.
function scaleRuns(runs) {
const params = {};
function scaleRuns(runs: number) {
const params = {} as Record<string, string>;

window.location.search
NutrientWindow.location.search
.substring(1)
.replace(/([^=&]+)=([^&]*)/g, (m, key, value) => {
.replace(/([^=&]+)=([^&]*)/g, (m: string, key: string, value: string) => {
params[decodeURIComponent(key)] = decodeURIComponent(value);

return "";
});

let runsScaleFactor = 1;
Expand Down
Loading
Loading