From 3cb894c4e59b43cada40b884c596cd7cb88e0ace Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 14 May 2026 06:41:42 -0700 Subject: [PATCH] chore: Add benchmarks for i18n vs vanilla usage (#2372) --- bun.lock | 5 +- packages/i18n/bench/README.md | 11 +++ packages/i18n/bench/_locales/en/messages.json | 17 ++++ packages/i18n/bench/background.ts | 85 +++++++++++++++++++ packages/i18n/bench/manifest.json | 11 +++ packages/i18n/bench/run.ts | 48 +++++++++++ packages/i18n/package.json | 2 + packages/i18n/tsconfig.json | 2 +- 8 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 packages/i18n/bench/README.md create mode 100644 packages/i18n/bench/_locales/en/messages.json create mode 100644 packages/i18n/bench/background.ts create mode 100644 packages/i18n/bench/manifest.json create mode 100644 packages/i18n/bench/run.ts diff --git a/bun.lock b/bun.lock index 158684c2..51e0df6a 100644 --- a/bun.lock +++ b/bun.lock @@ -110,6 +110,7 @@ "@types/node": "catalog:", "oxlint": "catalog:", "publint": "catalog:", + "tinybench": "^6.0.1", "tsdown": "catalog:", "typescript": "catalog:", "vitest": "catalog:", @@ -2304,7 +2305,7 @@ "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + "tinybench": ["tinybench@6.0.1", "", {}, "sha512-cMdWsxmysdg8mNWf1pujiWl3TW0cU6m8QuNw55QlnP3I6N96Grb0wnu5N0syHIu3LbiVZCNqlfWzWDq84HZphA=="], "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], @@ -2782,6 +2783,8 @@ "vitest/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "vitest/tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + "vue/@vue/shared": ["@vue/shared@3.5.34", "", {}, "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA=="], "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], diff --git a/packages/i18n/bench/README.md b/packages/i18n/bench/README.md new file mode 100644 index 00000000..c0e88666 --- /dev/null +++ b/packages/i18n/bench/README.md @@ -0,0 +1,11 @@ +# I18n Benchmarks + +These benchmarks must be ran in an actual browser extension environment. + +To run them, run `bench/run.ts` and load the `bench/` directory into a browser as an extension. Or just: + +```sh +bun --cwd packages/i18n bench +``` + +Benchmarks are defined in `bench/background.ts`. Add or edit them there. diff --git a/packages/i18n/bench/_locales/en/messages.json b/packages/i18n/bench/_locales/en/messages.json new file mode 100644 index 00000000..6488bd0d --- /dev/null +++ b/packages/i18n/bench/_locales/en/messages.json @@ -0,0 +1,17 @@ +{ + "simple": { + "message": "Just plain text" + }, + "substitution": { + "message": "Includes one substitution: $1" + }, + "singular": { + "message": "There is $1 item" + }, + "plural": { + "message": "There are $1 items" + }, + "wxtPlural": { + "message": "There is $1 item | There are $1 items" + } +} diff --git a/packages/i18n/bench/background.ts b/packages/i18n/bench/background.ts new file mode 100644 index 00000000..1cf9f973 --- /dev/null +++ b/packages/i18n/bench/background.ts @@ -0,0 +1,85 @@ +import { Bench } from 'tinybench'; +import { createI18n } from '../src'; + +declare const chrome: any; + +export const i18n = createI18n(); + +// Keep service worker alive +setTimeout(() => { + chrome.runtime.getPlatformInfo(); +}, 20e3); + +(async () => { + const results: Record = {}; + const runBench = async (bench: Bench) => { + await reportProgress(`[start] ${bench.name}`); + await bench.run(); + results[bench.name!] = bench.table(); + await reportProgress(`[done] ${bench.name}`); + }; + + { + const key = 'simple'; + const simple = new Bench({ name: 'Simple' }) + .add('Vanilla', () => void chrome.i18n.getMessage(key)) + .add('WXT I18n', () => void i18n.t(key)); + await runBench(simple); + } + + { + const key = 'substitution'; + const value = 'test'; + const substitution = new Bench({ name: 'Substitution' }) + .add('Vanilla', () => void chrome.i18n.getMessage(key, [value])) + .add('WXT I18n', () => void i18n.t(key, [value])); + await runBench(substitution); + } + + { + const vanillaPluralKey = 'plural'; + const wxtKey = 'wxtPlural'; + const one = 1; + const pluralSingular = new Bench({ + name: 'Plural (singular form)', + }) + .add( + 'Vanilla', + () => void chrome.i18n.getMessage(vanillaPluralKey, [one]), + ) + .add('WXT I18n', () => void i18n.t(wxtKey, one)); + await runBench(pluralSingular); + + const two = 2; + const vanillaSingularKey = 'singular'; + const pluralPlural = new Bench({ + name: 'Plural (plural form)', + time: 1000, + }) + .add( + 'Vanilla', + () => void chrome.i18n.getMessage(vanillaSingularKey, [two]), + ) + .add('WXT I18n', () => void i18n.t(wxtKey, two)); + await runBench(pluralPlural); + } + + // Report results + await reportResults(results); +})(); + +async function reportProgress(message: string) { + const res = await fetch('http://localhost:3000/progress', { + method: 'POST', + body: message, + }); + if (!res.ok) throw Error('Progress report failed...'); +} + +async function reportResults(results: any) { + const res = await fetch('http://localhost:3000/results', { + method: 'POST', + body: JSON.stringify(results, null, 2), + }); + if (!res.ok) throw Error('Results report failed...'); +} diff --git a/packages/i18n/bench/manifest.json b/packages/i18n/bench/manifest.json new file mode 100644 index 00000000..09513473 --- /dev/null +++ b/packages/i18n/bench/manifest.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/chrome-manifest", + "name": "WXT I18n Benchmark", + "version": "1.0.0", + "manifest_version": 3, + "default_locale": "en", + "background": { + "type": "module", + "service_worker": "dist/background.js" + } +} diff --git a/packages/i18n/bench/run.ts b/packages/i18n/bench/run.ts new file mode 100644 index 00000000..17948011 --- /dev/null +++ b/packages/i18n/bench/run.ts @@ -0,0 +1,48 @@ +import { join, relative } from 'node:path'; + +const distDir = join(import.meta.dir, 'dist'); +const backgroundSrc = join(import.meta.dir, 'background.ts'); + +console.log('\nBuilding benchmark extension...'); +await Bun.build({ + entrypoints: [backgroundSrc], + outdir: distDir, + format: 'esm', +}); +console.log('Done!'); + +console.log( + `\nInstall the extension from ./${relative(process.cwd(), import.meta.dir)} (or reload it) to run benchmarks.`, +); +console.log('Waiting for benchmark results...'); + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', +}; + +Bun.serve({ + fetch: async (request) => { + // CORS support + if (request.method === 'OPTIONS') { + return new Response(undefined, { headers: CORS_HEADERS }); + } + + const url = new URL(request.url); + if (url.pathname === '/progress') { + console.log(`\x1b[2m← ${await request.text()}\x1b[0m`); + return new Response(undefined, { status: 202, headers: CORS_HEADERS }); + } + + if (url.pathname === '/results') { + const body = await request.json(); + for (const [name, table] of Object.entries(body)) { + console.log('\n' + name); + console.table(table); + } + setTimeout(() => void process.exit(0)); + return new Response(undefined, { status: 202, headers: CORS_HEADERS }); + } + + return new Response(undefined, { status: 404, headers: CORS_HEADERS }); + }, +}); diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 36fab794..0ef40119 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -24,6 +24,7 @@ "build": "buildc -- tsdown", "check": "buildc --deps-only -- check", "test": "buildc --deps-only -- vitest", + "bench": "buildc --deps-only -- bun run bench/run.ts", "test:coverage": "bun run test run --coverage", "prepack": "bun run build" }, @@ -46,6 +47,7 @@ "@types/node": "catalog:", "oxlint": "catalog:", "publint": "catalog:", + "tinybench": "^6.0.1", "tsdown": "catalog:", "typescript": "catalog:", "vitest": "catalog:", diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json index 46e1f61f..c5dcbd37 100644 --- a/packages/i18n/tsconfig.json +++ b/packages/i18n/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "types": ["node"] + "types": ["bun"] }, "exclude": ["node_modules/**", "dist/**"] }