Skip to content

Commit d8f647d

Browse files
committed
Slim down the size of the isEqual helper by importing a simpler one
We were previously using isEqual from lodash for simplicity in a structural object comparison, but it is a remarkably big package at 50kb uncompressed. This pulls in a smaller version of a very similar utility without any dependencies to replace it. Before: ``` ✓ 176 modules transformed. dist/api-read-stats.html 212.12 kB │ gzip: 47.04 kB dist/test-bundle-api-read.js 236.85 kB │ gzip: 43.50 kB dist/api-read-stats.html 212.64 kB │ gzip: 47.09 kB dist/test-bundle-api-read.cjs 155.96 kB │ gzip: 36.01 kB ✓ built in 861ms vite v4.4.7 building for production... ✓ 452 modules transformed. dist/react-read-stats.html 303.58 kB │ gzip: 57.00 kB dist/test-bundle-react-read.js 342.04 kB │ gzip: 72.64 kB dist/react-read-stats.html 307.55 kB │ gzip: 57.01 kB dist/test-bundle-react-read.cjs 230.74 kB │ gzip: 61.17 kB ✓ built in 1.23s vite v4.4.7 building for production... ✓ 986 modules transformed. dist/shopify-read-stats.html 517.13 kB │ gzip: 77.52 kB dist/test-bundle-shopify-read.js 661.41 kB │ gzip: 131.38 kB dist/shopify-read-stats.html 517.64 kB │ gzip: 77.55 kB dist/test-bundle-shopify-read.cjs 464.83 kB │ gzip: 112.29 kB ✓ built in 2.39s ``` After: ``` vite v4.4.7 building for production... ✓ 174 modules transformed. dist/api-read-stats.html 211.29 kB │ gzip: 46.97 kB dist/test-bundle-api-read.js 223.49 kB │ gzip: 40.77 kB dist/api-read-stats.html 211.85 kB │ gzip: 46.99 kB dist/test-bundle-api-read.cjs 146.84 kB │ gzip: 33.65 kB ✓ built in 795ms vite v4.4.7 building for production... ✓ 450 modules transformed. dist/react-read-stats.html 302.75 kB │ gzip: 56.82 kB dist/test-bundle-react-read.js 328.63 kB │ gzip: 69.94 kB dist/react-read-stats.html 306.74 kB │ gzip: 56.90 kB dist/test-bundle-react-read.cjs 221.58 kB │ gzip: 58.80 kB ✓ built in 1.21s vite v4.4.7 building for production... ✓ 984 modules transformed. dist/shopify-read-stats.html 516.32 kB │ gzip: 77.44 kB dist/test-bundle-shopify-read.js 647.97 kB │ gzip: 128.67 kB dist/shopify-read-stats.html 516.83 kB │ gzip: 77.47 kB dist/test-bundle-shopify-read.cjs 455.63 kB │ gzip: 109.84 kB ✓ built in 2.60s ``` 6% bundle size reduction
1 parent 6daace9 commit d8f647d

File tree

4 files changed

+95
-20
lines changed

4 files changed

+95
-20
lines changed

packages/api-client-core/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@
3535
"graphql-ws": "^5.13.1",
3636
"isomorphic-ws": "^5.0.0",
3737
"lodash.clonedeep": "^4.5.0",
38-
"lodash.isequal": "^4.5.0",
3938
"ws": "^8.13.0"
4039
},
4140
"devDependencies": {
4241
"tiny-graphql-query-compiler": "*",
4342
"@types/lodash.clonedeep": "^4.5.6",
44-
"@types/lodash.isequal": "^4.5.5",
4543
"@types/node": "^16.11.7",
4644
"conditional-type-checks": "^1.0.6",
4745
"gql-tag": "^1.0.1",

packages/api-client-core/src/GadgetRecord.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import cloneDeep from "lodash.clonedeep";
2-
import isEqual from "lodash.isequal";
32
import type { Jsonify } from "type-fest";
4-
import { toPrimitiveObject } from "./support.js";
3+
import { isEqual, toPrimitiveObject } from "./support.js";
54

65
export enum ChangeTracking {
76
SinceLoaded,

packages/api-client-core/src/support.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,97 @@ export const storageAvailable = (type: "localStorage" | "sessionStorage") => {
436436
return false;
437437
}
438438
};
439+
440+
// smaller implementation of lodash's isEqual from https://github.com/NickGard/tiny-isequal but made a bit more performant and typesafe
441+
const toString = Object.prototype.toString,
442+
getPrototypeOf = Object.getPrototypeOf,
443+
getOwnProperties = Object.getOwnPropertySymbols
444+
? (c: any) => (Object.keys(c) as any[]).concat(Object.getOwnPropertySymbols(c))
445+
: Object.keys;
446+
447+
const checkEquality = (a: any, b: any, refs: any[]): boolean => {
448+
// trivial case: primitives and referentially equal objects
449+
if (a === b) return true;
450+
451+
// if both are null/undefined, the above check would have returned true
452+
if (a == null || b == null) return false;
453+
454+
// check to see if we've seen this reference before; if yes, return true
455+
// eslint-disable-next-line lodash/prefer-includes
456+
if (refs.indexOf(a) > -1 && refs.indexOf(b) > -1) return true;
457+
458+
const aType = toString.call(a);
459+
const bType = toString.call(b);
460+
461+
let aElements, bElements, element;
462+
463+
// save results for circular checks
464+
refs.push(a, b);
465+
466+
if (aType != bType) return false; // not the same type of objects
467+
468+
// for non-null objects, check all custom properties
469+
aElements = getOwnProperties(a);
470+
bElements = getOwnProperties(b);
471+
if (
472+
aElements.length != bElements.length ||
473+
aElements.some(function (key) {
474+
return !checkEquality(a[key], b[key], refs);
475+
})
476+
) {
477+
return false;
478+
}
479+
480+
switch (aType.slice(8, -1)) {
481+
case "Symbol":
482+
return a.valueOf() == b.valueOf();
483+
case "Date":
484+
case "Number":
485+
return +a == +b || (+a != +a && +b != +b); // convert Dates to ms, check for NaN
486+
case "RegExp":
487+
case "Function":
488+
case "String":
489+
case "Boolean":
490+
return "" + a == "" + b;
491+
case "Set":
492+
case "Map": {
493+
aElements = a.entries();
494+
bElements = b.entries();
495+
do {
496+
element = aElements.next();
497+
if (!checkEquality(element.value, bElements.next().value, refs)) {
498+
return false;
499+
}
500+
} while (!element.done);
501+
return true;
502+
}
503+
case "ArrayBuffer":
504+
(a = new Uint8Array(a)), (b = new Uint8Array(b)); // fall through to be handled as an Array
505+
case "DataView":
506+
(a = new Uint8Array(a.buffer)), (b = new Uint8Array(b.buffer)); // fall through to be handled as an Array
507+
case "Float32Array":
508+
case "Float64Array":
509+
case "Int8Array":
510+
case "Int16Array":
511+
case "Int32Array":
512+
case "Uint8Array":
513+
case "Uint16Array":
514+
case "Uint32Array":
515+
case "Uint8ClampedArray":
516+
case "Arguments":
517+
case "Array":
518+
if (a.length != b.length) return false;
519+
for (element = 0; element < a.length; element++) {
520+
if (!(element in a) && !(element in b)) continue; // empty slots are equal
521+
// either one slot is empty but not both OR the elements are not equal
522+
if (element in a != element in b || !checkEquality(a[element], b[element], refs)) return false;
523+
}
524+
return true;
525+
case "Object":
526+
return checkEquality(getPrototypeOf(a), getPrototypeOf(b), refs);
527+
default:
528+
return false;
529+
}
530+
};
531+
532+
export const isEqual = (a: any, b: any) => checkEquality(a, b, []);

pnpm-lock.yaml

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)