diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml
index ee8fa21..a023f64 100644
--- a/.github/workflows/autofix.yml
+++ b/.github/workflows/autofix.yml
@@ -31,6 +31,6 @@ jobs:
run: yarn format
- name: Apply autofix.ci
- uses: autofix-ci/action@2891949f3779a1cafafae1523058501de3d4e944 # v1
+ uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2
with:
fail-fast: false
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 205cbda..d53d83c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,6 @@ jobs:
strategy:
matrix:
node:
- - 18
- 20
- 22
- 24
diff --git a/.prettierignore b/.prettierignore
index 61c3bc7..2b81ee3 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,2 @@
.yarn
+/public/site.webmanifest
diff --git a/.size-limit.json b/.size-limit.json
index 955bd9f..ce60f2a 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -1,6 +1,6 @@
[
{
"path": "./lib/index.js",
- "limit": "460B"
+ "limit": "600B"
}
]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a885f6..eb4792d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Change Log
+## 0.2.0
+
+### Minor Changes
+
+- [#11](https://github.com/un-ts/stable-hash-x/pull/11) [`304fe40`](https://github.com/un-ts/stable-hash-x/commit/304fe40bf59dbd714b0e8f55b76ffdc5eef1232b) Thanks [@JounQin](https://github.com/JounQin)! - refactor: add a new `crossRealm` option for performance
+
+### Patch Changes
+
+- [#9](https://github.com/un-ts/stable-hash-x/pull/9) [`8dcd38d`](https://github.com/un-ts/stable-hash-x/commit/8dcd38d034f885c9a3501ecd85b5f579ebcef97a) Thanks [@JounQin](https://github.com/JounQin)! - fix: use `toString` and `prototype` instead of `constructor` for type checking
+
## 0.1.1
### Patch Changes
diff --git a/README.md b/README.md
index 95e7af3..790293a 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
[](https://github.com/prettier/prettier)
[](https://github.com/changesets/changesets)
-A tiny and fast (460b [unpkg](https://unpkg.com/stable-hash-x@latest/lib/index.js)) lib for "stably hashing" a JavaScript value. Originally created for [SWR](https://github.com/vercel/swr) by [Shu Ding][] at [`stable-hash`](https://github.com/shuding/stable-hash), we forked it because the original one is a bit out of maintenance for a long time.
+A tiny and fast (600B [unpkg](https://unpkg.com/stable-hash-x@latest/lib/index.js)) lib for "stably hashing" a JavaScript value, works with cross-realm objects. Originally created for [SWR](https://github.com/vercel/swr) by [Shu Ding][] at [`stable-hash`](https://github.com/shuding/stable-hash), we forked it because the original one is a bit out of maintenance for a long time.
It's similar to `JSON.stringify(value)`, but:
@@ -31,6 +31,7 @@ It's similar to `JSON.stringify(value)`, but:
- [Array](#array)
- [Object](#object)
- [`Function`, `Class`, `Set`, `Map`, `Buffer`...](#function-class-set-map-buffer)
+ - [Cross-realm](#cross-realm)
- [Benchmark](#benchmark)
- [Notes](#notes)
- [Sponsors and Backers](#sponsors-and-backers)
@@ -49,6 +50,8 @@ yarn add stable-hash-x
import { hash } from 'stable-hash-x'
hash(anyJavaScriptValueHere) // returns a string
+
+hash(anyJavaScriptValueHere, true) // if you're running in cross-realm environment, it's disabled by default for performance
```
## Examples
@@ -153,37 +156,39 @@ hash(foo) === hash(foo)
hash(foo) !== hash(new Set([1]))
```
+### Cross-realm
+
+```js
+import { runInNewContext } from 'node:vm'
+
+const obj1 = {
+ a: 1,
+ b: new Date('2022-06-25T01:55:27.743Z'),
+ c: /test/,
+ f: Symbol('test'),
+}
+const obj2 = runInNewContext(`({
+ a: 1,
+ b: new Date('2022-06-25T01:55:27.743Z'),
+ c: /test/,
+ f: Symbol('test'),
+})`)
+
+obj1 === obj2 // false
+hash(obj1) === hash(obj2, true) // true
+```
+
## Benchmark
```log
-clk: ~2.91 GHz
-cpu: Apple M1 Max
-runtime: node 22.16.0 (arm64-darwin)
-
-benchmark avg (min … max) p75 / p99 (min … top 1%)
-------------------------------------------- -------------------------------
-stable-hash-x 7.87 µs/iter 7.38 µs █
- (6.67 µs … 749.13 µs) 11.42 µs ▇█▃
- (104.00 b … 859.30 kb) 10.89 kb ▁███▅▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁
- 4.41 ipc ( 1.81% stalls) 98.08% L1 data cache
- 28.04k cycles 123.52k instructions 29.75% retired LD/ST ( 36.75k)
-
-hash-object 15.07 µs/iter 14.95 µs █ █
- (14.77 µs … 16.93 µs) 15.00 µs ▅ ▅ ▅▅█ ▅█▅ ▅
- (659.78 b … 3.26 kb) 1.95 kb █▁▁█▁▁▁▁▁▁███▁▁███▁▁█
- 4.97 ipc ( 1.22% stalls) 99.33% L1 data cache
- 46.36k cycles 230.44k instructions 35.12% retired LD/ST ( 80.94k)
-
-json-stringify-deterministic 8.37 µs/iter 8.41 µs █
- (8.29 µs … 8.50 µs) 8.44 µs █ █
- ( 1.65 kb … 1.65 kb) 1.65 kb █▁████▁██▁█▁▁▁█▁█▁███
- 5.17 ipc ( 1.28% stalls) 99.40% L1 data cache
- 25.99k cycles 134.30k instructions 35.51% retired LD/ST ( 47.69k)
-
-summary
- stable-hash-x
- 1.06x faster than json-stringify-deterministic
- 1.91x faster than hash-object
+┌─────────┬────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
+│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
+├─────────┼────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
+│ 0 │ 'stable-hash-x' │ '7877.4 ± 1.57%' │ '7042.0 ± 167.00' │ '138708 ± 0.05%' │ '142005 ± 3449' │ 126975 │
+│ 1 │ 'hash-object' │ '17632 ± 0.73%' │ '16708 ± 458.00' │ '58820 ± 0.07%' │ '59852 ± 1600' │ 56716 │
+│ 2 │ 'json-stringify-deterministic' │ '10901 ± 0.83%' │ '10250 ± 250.00' │ '95860 ± 0.05%' │ '97561 ± 2439' │ 91739 │
+│ 3 │ 'stable-hash' │ '8318.5 ± 3.27%' │ '7042.0 ± 208.00' │ '138347 ± 0.06%' │ '142005 ± 4074' │ 120214 │
+└─────────┴────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
```
## Notes
diff --git a/benchmark.js b/benchmark.js
index 8720175..a4d53d6 100644
--- a/benchmark.js
+++ b/benchmark.js
@@ -6,9 +6,10 @@ import { encodeUrl } from 'ab64'
import { flattie } from 'flattie'
import hashObject from 'hash-object'
import stringify from 'json-stringify-deterministic'
-import { bench, run, summary } from 'mitata'
+import { stableHash } from 'stable-hash'
+import { Bench } from 'tinybench'
-import { hash } from 'stable-hash-x'
+import { stableHash as hash } from './lib/index.js'
// this is an example of payload
const payload = {
@@ -42,7 +43,7 @@ const payload = {
/**
* Benchmarking `stable-hash-x` vs. `hash-object` vs.
- * `json-stringify-deterministic`
+ * `json-stringify-deterministic` vs. `stable-hash`
*
* The goal is to represent a real use-case. Because that:
*
@@ -87,12 +88,26 @@ const getHashThree = obj =>
.digest('base64'),
)
-summary(() => {
- bench('stable-hash-x', () => getHashOne(payload)).baseline()
+/**
+ * @param {unknown} obj
+ * @returns {string} The hash
+ */
+const getHashFour = obj =>
+ encodeUrl(
+ crypto
+ .createHash('sha512')
+ .update(stableHash(flattie(obj)))
+ .digest('base64'),
+ )
+
+const bench = new Bench()
- bench('hash-object', () => getHashTwo(payload))
+bench
+ .add('stable-hash-x', () => getHashOne(payload))
+ .add('hash-object', () => getHashTwo(payload))
+ .add('json-stringify-deterministic', () => getHashThree(payload))
+ .add('stable-hash', () => getHashFour(payload))
- bench('json-stringify-deterministic', () => getHashThree(payload))
-})
+await bench.run()
-await run()
+console.table(bench.table())
diff --git a/benchmark.txt b/benchmark.txt
index 64f743f..18aebd4 100644
--- a/benchmark.txt
+++ b/benchmark.txt
@@ -1,28 +1,8 @@
-clk: ~2.91 GHz
-cpu: Apple M1 Max
-runtime: node 22.16.0 (arm64-darwin)
-
-benchmark avg (min … max) p75 / p99 (min … top 1%)
-------------------------------------------- -------------------------------
-stable-hash-x 7.87 µs/iter 7.38 µs █
- (6.67 µs … 749.13 µs) 11.42 µs ▇█▃
- (104.00 b … 859.30 kb) 10.89 kb ▁███▅▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁
- 4.41 ipc ( 1.81% stalls) 98.08% L1 data cache
- 28.04k cycles 123.52k instructions 29.75% retired LD/ST ( 36.75k)
-
-hash-object 15.07 µs/iter 14.95 µs █ █
- (14.77 µs … 16.93 µs) 15.00 µs ▅ ▅ ▅▅█ ▅█▅ ▅
- (659.78 b … 3.26 kb) 1.95 kb █▁▁█▁▁▁▁▁▁███▁▁███▁▁█
- 4.97 ipc ( 1.22% stalls) 99.33% L1 data cache
- 46.36k cycles 230.44k instructions 35.12% retired LD/ST ( 80.94k)
-
-json-stringify-deterministic 8.37 µs/iter 8.41 µs █
- (8.29 µs … 8.50 µs) 8.44 µs █ █
- ( 1.65 kb … 1.65 kb) 1.65 kb █▁████▁██▁█▁▁▁█▁█▁███
- 5.17 ipc ( 1.28% stalls) 99.40% L1 data cache
- 25.99k cycles 134.30k instructions 35.51% retired LD/ST ( 47.69k)
-
-summary
- stable-hash-x
- 1.06x faster than json-stringify-deterministic
- 1.91x faster than hash-object
+┌─────────┬────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
+│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
+├─────────┼────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
+│ 0 │ 'stable-hash-x' │ '7877.4 ± 1.57%' │ '7042.0 ± 167.00' │ '138708 ± 0.05%' │ '142005 ± 3449' │ 126975 │
+│ 1 │ 'hash-object' │ '17632 ± 0.73%' │ '16708 ± 458.00' │ '58820 ± 0.07%' │ '59852 ± 1600' │ 56716 │
+│ 2 │ 'json-stringify-deterministic' │ '10901 ± 0.83%' │ '10250 ± 250.00' │ '95860 ± 0.05%' │ '97561 ± 2439' │ 91739 │
+│ 3 │ 'stable-hash' │ '8318.5 ± 3.27%' │ '7042.0 ± 208.00' │ '138347 ± 0.06%' │ '142005 ± 4074' │ 120214 │
+└─────────┴────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
diff --git a/package.json b/package.json
index 6490f17..91126cb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "stable-hash-x",
- "version": "0.1.1",
+ "version": "0.2.0",
"type": "module",
"description": "Stable JS value hash.",
"repository": "https://github.com/un-ts/stable-hash-x",
@@ -34,7 +34,7 @@
"lib"
],
"scripts": {
- "benchmark": "sudo env NO_COLOR=1 node benchmark > benchmark.txt",
+ "benchmark": "node benchmark > benchmark.txt",
"build": "tsdown -d lib -f cjs,esm",
"dev": "vitest",
"docs": "vite",
@@ -51,32 +51,31 @@
"version": "changeset version && yarn --no-immutable"
},
"devDependencies": {
- "@1stg/common-config": "^14.1.0",
+ "@1stg/common-config": "^14.2.0",
"@changesets/changelog-github": "^0.5.1",
- "@changesets/cli": "^2.29.4",
+ "@changesets/cli": "^2.29.5",
"@commitlint/cli": "^19.8.1",
"@mdx-js/rollup": "^3.1.0",
"@mitata/counters": "^0.0.8",
- "@shikijs/rehype": "^3.5.0",
- "@types/node": "^22.15.29",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "@types/web": "^0.0.238",
+ "@shikijs/rehype": "^3.7.0",
+ "@types/node": "^22.15.33",
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@types/web": "^0.0.243",
"@vercel/analytics": "^1.5.0",
- "@vitejs/plugin-react-swc": "^3.10.1",
- "@vitest/coverage-v8": "3.2.1",
+ "@vitejs/plugin-react-swc": "^3.10.2",
+ "@vitest/coverage-v8": "3.2.4",
"ab64": "^0.1.6",
"clean-pkg-json": "^1.3.0",
"esbuild": "^0.25.5",
- "eslint": "^9.28.0",
+ "eslint": "^9.29.0",
"flattie": "^1.1.1",
"github-markdown-css": "^5.8.1",
"hash-object": "^5.0.1",
"json-stringify-deterministic": "^1.0.12",
- "mitata": "^1.0.34",
"nano-staged": "^0.8.0",
"npm-run-all2": "^8.0.4",
- "prettier": "^3.5.3",
+ "prettier": "^3.6.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.2",
@@ -84,16 +83,18 @@
"simple-git-hooks": "^2.13.0",
"size-limit": "^11.2.0",
"size-limit-preset-node-lib": "^0.4.0",
- "tsdown": "^0.12.6",
+ "stable-hash": "^0.0.6",
+ "tinybench": "^4.0.1",
+ "tsdown": "^0.12.9",
"type-coverage": "^2.29.7",
"typescript": "^5.8.3",
- "vite": "^6.3.5",
- "vitest": "^3.2.1",
+ "vite": "^7.0.0",
+ "vitest": "^3.2.4",
"yarn-berry-deduplicate": "^6.1.3"
},
"resolutions": {
- "prettier": "^3.5.3",
- "vite": "npm:rolldown-vite@^6.3.17"
+ "prettier": "^3.6.0",
+ "vite": "npm:rolldown-vite@^7.0.0"
},
"typeCoverage": {
"atLeast": 100,
diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png
new file mode 100755
index 0000000..23318bb
Binary files /dev/null and b/public/android-chrome-192x192.png differ
diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png
new file mode 100755
index 0000000..1729812
Binary files /dev/null and b/public/android-chrome-512x512.png differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
new file mode 100755
index 0000000..4aaec7f
Binary files /dev/null and b/public/apple-touch-icon.png differ
diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png
new file mode 100755
index 0000000..93693da
Binary files /dev/null and b/public/favicon-16x16.png differ
diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png
new file mode 100755
index 0000000..34f3487
Binary files /dev/null and b/public/favicon-32x32.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100755
index 0000000..fa8a816
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/site.webmanifest b/public/site.webmanifest
new file mode 100755
index 0000000..d5b5dd7
--- /dev/null
+++ b/public/site.webmanifest
@@ -0,0 +1 @@
+{"name":"stable-hash-x","short_name":"stable-hash","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
diff --git a/src/index.ts b/src/index.ts
index b7bfb32..5a032ba 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,6 +6,22 @@ const table = new WeakMap