From 60aa566e775cd9f1ed6467aa7cb49173c9537d31 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sat, 1 Mar 2025 19:31:39 +0100 Subject: [PATCH 1/3] build: export `isEqual` from main subpath for backward compat (#128) --- README.md | 23 ++++++++++++----------- src/index.ts | 3 +++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6ae80ac..a7671da 100644 --- a/README.md +++ b/README.md @@ -27,24 +27,26 @@ npx nypm i ohash ```js // ESM import -import { hash, serialize, digest } from "ohash"; -import { isEqual, diff } from "ohash/utils"; +import { hash, serialize, digest, isEqual } from "ohash"; +import { diff } from "ohash/utils"; // Dynamic import -const { hash, serialize, digest } = await import("ohash"); -const { isEqual, diff } = await import("ohash/utils"); +const { hash, serialize, digest, isEqual } = await import("ohash"); +const { diff } = await import("ohash/utils"); ```
Import from CDN ```js -import { hash, serialize, digest } from "https://esm.sh/ohash"; -import { isEqual, diff } from "https://esm.sh/ohash/utils"; +import { hash, serialize, digest, isEqual } from "https://esm.sh/ohash"; +import { diff } from "https://esm.sh/ohash/utils"; // Dynamic import -const { hash, serialize, digest } = await import("https://esm.sh/ohash"); -const { isEqual, diff } = await import("https://esm.sh/ohash/utils"); +const { hash, serialize, digest, isEqual } = await import( + "https://esm.sh/ohash" +); +const { diff } = await import("https://esm.sh/ohash/utils"); ```
@@ -66,8 +68,7 @@ console.log(hash({ foo: "bar" })); Serializes any input value into a string for hashing. -> [!IMPORTANT] -> `serialize` method uses best efforts to generate stable serialized values; however, it is not designed for security purposes. Keep in mind that there is always a chance of intentional collisions caused by user input. +> [!IMPORTANT] > `serialize` method uses best efforts to generate stable serialized values; however, it is not designed for security purposes. Keep in mind that there is always a chance of intentional collisions caused by user input. ```js import { serialize } from "ohash"; @@ -92,7 +93,7 @@ console.log(digest("Hello World!")); Compare two objects using `===` and then fallbacks to compare based on their [serialized](#serializeinput) values. ```js -import { isEqual } from "ohash/utils"; +import { isEqual } from "ohash"; // true console.log(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); diff --git a/src/index.ts b/src/index.ts index 17da0df..b1798f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,3 +4,6 @@ export { hash } from "./hash"; // Crypto export { digest } from "ohash/crypto"; + +// Utils (more from ohash/utils) +export { isEqual } from "./utils/is-equal"; From 12aa79409ad9a204446eab21fb3a268a9d04dd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20N=C3=A9meth?= Date: Sat, 1 Mar 2025 19:51:52 +0100 Subject: [PATCH 2/3] perf(serialize): faster serialization and less bundle size (#126) --- src/serialize.ts | 85 ++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/src/serialize.ts b/src/serialize.ts index f6a56e1..cbc7724 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -26,34 +26,41 @@ const Serializer = /*@__PURE__*/ (function () { return a - b; } - // Uses fast path to compare primitive values (string, number, bigint, boolean, null, undefined) - // Only symbol, function and object values need to be full serialized return String.prototype.localeCompare.call( - toComparableString(a) ?? this.serialize(a), - toComparableString(b) ?? this.serialize(b), + this.serialize(a, true), + this.serialize(b, true), ); } - serialize(value: any): string { - const type = value === null ? "null" : typeof value; - // @ts-ignore - const handler = this["$" + type]; - return handler.call(this, value); + serialize(value: any, noQuotes?: boolean): string { + if (value === null) { + return "null"; + } + + switch (typeof value) { + case "string": { + return noQuotes ? value : `'${value}'`; + } + case "bigint": { + return `${value}n`; + } + case "object": { + return this.$object(value); + } + case "function": { + return this.$function(value); + } + } + + return String(value); } serializeObject(object: any): string { const objString = Object.prototype.toString.call(object); - - let objType = ""; - const objectLength = objString.length; - - // '[object a]'.length === 10, the minimum - if (objectLength < 10) { - objType = "unknown:[" + objString + "]"; - } else { - // '[object '.length === 8 - objType = objString.slice(8, objectLength - 1); - } + const objType = + objString.length < 10 // '[object a]'.length === 10, the minimum + ? `unknown:${objString}` + : objString.slice(8, -1); // '[object '.length === 8 if ( objType !== "Object" && @@ -71,14 +78,14 @@ const Serializer = /*@__PURE__*/ (function () { throw new Error(`Cannot serialize ${objType}`); } - const constructor = object.constructor.name; - const objectName = constructor === "Object" ? "" : constructor; + const constructorName = object.constructor.name; + const objName = constructorName === "Object" ? "" : constructorName; if (typeof object.toJSON === "function") { - return objectName + this.$object(object.toJSON()); + return objName + this.$object(object.toJSON()); } - return this.serializeObjectEntries(objectName, Object.entries(object)); + return this.serializeObjectEntries(objName, Object.entries(object)); } serializeObjectEntries(type: string, entries: Iterable<[string, any]>) { @@ -97,14 +104,6 @@ const Serializer = /*@__PURE__*/ (function () { return content + "}"; } - $string(string: any) { - return `'${string}'`; - } - - $bigint(bigint: bigint) { - return `${bigint}n`; - } - $object(object: any) { let content = this.#context.get(object); @@ -155,17 +154,6 @@ const Serializer = /*@__PURE__*/ (function () { } } - for (const type of [ - "symbol", - "boolean", - "number", - "null", - "undefined", - ] as const) { - // @ts-ignore - Serializer.prototype["$" + type] = String; - } - for (const type of ["Error", "RegExp", "URL"] as const) { // @ts-ignore Serializer.prototype["$" + type] = function (val: any) { @@ -201,14 +189,3 @@ const Serializer = /*@__PURE__*/ (function () { } return Serializer; })(); - -function toComparableString(val: unknown): string | undefined { - if (val === null) { - return "null"; - } - const type = typeof val; - if (type === "function" || type === "object") { - return undefined; - } - return String(val); -} From b8a0cca900be2b8592531881337d466dd105602a Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sat, 1 Mar 2025 19:52:40 +0100 Subject: [PATCH 3/3] chore(release): v2.0.8 --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aeafc3..5ff221d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## v2.0.8 + +[compare changes](https://github.com/unjs/ohash/compare/v2.0.7...v2.0.8) + +### 🔥 Performance + +- **serialize:** Faster serialization and less bundle size ([#126](https://github.com/unjs/ohash/pull/126)) + +### 📦 Build + +- Export `isEqual` from main subpath for backward compat ([#128](https://github.com/unjs/ohash/pull/128)) + +### ❤️ Contributors + +- Balázs Németh ([@zsilbi](https://github.com/zsilbi)) +- Pooya Parsa ([@pi0](https://github.com/pi0)) + ## v2.0.7 [compare changes](https://github.com/unjs/ohash/compare/v2.0.6...v2.0.7) diff --git a/package.json b/package.json index 08dd69a..2ac89bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ohash", - "version": "2.0.7", + "version": "2.0.8", "description": "Simple object hashing, serialization and comparison utils.", "repository": "unjs/ohash", "license": "MIT",