diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5cbae383..846e7246 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,12 @@ updates: interval: daily open-pull-requests-limit: 10 versioning-strategy: increase + - package-ecosystem: npm + directory: "/scripts/benchmark" + schedule: + interval: daily + open-pull-requests-limit: 10 + versioning-strategy: increase - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/nodejs-test.yml b/.github/workflows/nodejs-test.yml index 11a7a91a..b07c2cd2 100644 --- a/.github/workflows/nodejs-test.yml +++ b/.github/workflows/nodejs-test.yml @@ -23,7 +23,9 @@ jobs: node-version: lts/* cache: npm - run: npm ci + - run: npm ci --prefix scripts/benchmark - run: npm run lint + - run: npm --prefix scripts/benchmark run lint test: name: Node ${{ matrix.node }} @@ -33,7 +35,7 @@ jobs: fail-fast: false matrix: node: - - '20.19.0' + - '20' - '22' - '24' - 'lts/*' diff --git a/eslint.config.mjs b/eslint.config.mjs index ffa36b51..ae1afd2e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,7 @@ export default defineConfig([ }, }, { - ignores: ["eslint.config.{js,cjs,mjs}"], + ignores: ["eslint.config.{js,cjs,mjs}", "scripts/benchmark/**"], }, ...feedicFlatConfig, { diff --git a/package-lock.json b/package-lock.json index abe8aa6f..2763135f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,9 @@ "@biomejs/biome": "^2.5.15", "@eslint/compat": "^2.1.1", "@feedic/eslint-config": "^0.5.0", - "@types/he": "^1.2.3", "@types/node": "^26.6.4", "eslint": "^10.12.0", "eslint-config-biome": "^2.1.3", - "he": "^1.2.0", - "html-entities": "^2.6.0", - "parse-entities": "^4.0.2", - "tinybench": "^6.2.1", "tsx": "^4.23.15", "typedoc": "^0.28.20", "typescript": "^6.0.3", @@ -1431,13 +1426,6 @@ "@types/unist": "*" } }, - "node_modules/@types/he": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", - "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1932,39 +1920,6 @@ "dev": true, "license": "MIT" }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/ci-info": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", @@ -2038,20 +1993,6 @@ } } }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2796,16 +2737,6 @@ "node": ">=20" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hookified": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", @@ -2876,32 +2807,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-builtin-module": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz", @@ -2918,17 +2823,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2952,17 +2846,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -3608,26 +3491,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-imports-exports": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", @@ -4014,9 +3877,9 @@ } }, "node_modules/tinybench": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", - "integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.0.tgz", + "integrity": "sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 4ce8d7e7..c38d36c7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "!**/*.spec.ts" ], "scripts": { - "benchmark": "node --import=tsx scripts/benchmark.ts", + "benchmark": "npm --prefix scripts/benchmark run benchmark", "build": "tsc", "build:docs": "typedoc --hideGenerator src/index.ts", "build:encode-trie": "node --import=tsx scripts/write-encode-map.ts", @@ -62,14 +62,9 @@ "@biomejs/biome": "^2.5.15", "@eslint/compat": "^2.1.1", "@feedic/eslint-config": "^0.5.0", - "@types/he": "^1.2.3", "@types/node": "^26.6.4", "eslint": "^10.12.0", "eslint-config-biome": "^2.1.3", - "he": "^1.2.0", - "html-entities": "^2.6.0", - "parse-entities": "^4.0.2", - "tinybench": "^6.2.1", "tsx": "^4.23.15", "typedoc": "^0.28.20", "typescript": "^6.0.3", diff --git a/readme.md b/readme.md index d571412d..baae922e 100644 --- a/readme.md +++ b/readme.md @@ -40,7 +40,21 @@ entities.decodeHTML("asdf & ÿ ü '"); // "asdf & ÿ ü '" Benchmarked in September 2025 with Node v24.6.0 on Apple M2 using `tinybench`. Higher ops/s is better; `avg (μs)` is the mean time per operation. -See `scripts/benchmark.ts` to reproduce. +The tables below are historical measurements of the listed library versions, +including `he` 1.2.0. The current benchmark uses the versions in +`scripts/benchmark/package-lock.json`, including `he` 2.0.0, and does not reproduce +these historical results. + +To run the current comparison, use `scripts/benchmark/benchmark.ts`. It requires +Node v22+ and installs benchmark dependencies separately from the main development +dependencies: + +```sh +npm ci +npm ci --prefix scripts/benchmark +npm --prefix scripts/benchmark run lint +npm run benchmark +``` ### Decoding diff --git a/scripts/benchmark.ts b/scripts/benchmark/benchmark.ts similarity index 97% rename from scripts/benchmark.ts rename to scripts/benchmark/benchmark.ts index 0d4c36d5..5411d650 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark/benchmark.ts @@ -1,8 +1,8 @@ -import he from "he"; +import * as he from "he"; import * as htmlEntities from "html-entities"; import { parseEntities } from "parse-entities"; import { Bench } from "tinybench"; -import * as entities from "../src/index.js"; +import * as entities from "../../src/index.js"; const htmlEntitiesHtml5EncodeOptions: htmlEntities.EncodeOptions = { level: "html5", diff --git a/scripts/benchmark/package-lock.json b/scripts/benchmark/package-lock.json new file mode 100644 index 00000000..150e6d44 --- /dev/null +++ b/scripts/benchmark/package-lock.json @@ -0,0 +1,178 @@ +{ + "name": "entities-benchmark", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "entities-benchmark", + "dependencies": { + "he": "^2.0.0", + "html-entities": "^2.6.0", + "parse-entities": "^4.0.2", + "tinybench": "^6.2.1" + }, + "devDependencies": { + "@types/he": "^1.2.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@types/he": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", + "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/he": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/he/-/he-2.0.0.tgz", + "integrity": "sha512-0jE+JPv08nhXWmI1dULybzqNt34RbkH6Ul6V4cmDONpjYx1SfyObKF38FJLaJ1K7WWD93LWL9lewzk4E1Ria4A==", + "license": "MIT", + "bin": { + "he": "bin/he.mjs" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tinybench": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", + "integrity": "sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + } + } +} diff --git a/scripts/benchmark/package.json b/scripts/benchmark/package.json new file mode 100644 index 00000000..d1f4a8ac --- /dev/null +++ b/scripts/benchmark/package.json @@ -0,0 +1,21 @@ +{ + "name": "entities-benchmark", + "private": true, + "type": "module", + "scripts": { + "benchmark": "node --import=tsx benchmark.ts", + "lint": "tsc --noEmit" + }, + "dependencies": { + "he": "^2.0.0", + "html-entities": "^2.6.0", + "parse-entities": "^4.0.2", + "tinybench": "^6.2.1" + }, + "devDependencies": { + "@types/he": "^1.2.3" + }, + "engines": { + "node": ">=22" + } +} diff --git a/scripts/benchmark/tsconfig.json b/scripts/benchmark/tsconfig.json new file mode 100644 index 00000000..ade57d85 --- /dev/null +++ b/scripts/benchmark/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.eslint.json", + "include": ["benchmark.ts"], + "exclude": [] +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 3fdb051d..6699d7d9 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -6,5 +6,5 @@ "types": ["node"] }, "include": ["src", "scripts"], - "exclude": [] + "exclude": ["scripts/benchmark"] }