Readit News logoReadit News
Posted by u/dhax_or 7 months ago
Show HN: SuperUtilsPlus – A Modern Alternative to Lodashgithub.com/dhaxor/super-u...
Hey HN!

After years of wrestling with Lodash's quirks and bundle size issues, I decided to build something better. SuperUtilsPlus is my attempt at creating the utility library I wish existed.

What makes it different?

TypeScript-first approach: Unlike Lodash's retrofitted types, I built this from the ground up with TypeScript. The type inference actually works the way you'd expect it to.

Sensible defaults: Some of Lodash's decisions always bugged me. Like isObject([]) returning true - arrays aren't objects in my mental model. Or isNumber(NaN) being true when NaN literally stands for "Not a Number". I fixed these footguns.

Modern JavaScript: Built for ES2020+ with proper ESM support. No more weird CommonJS/ESM dance. Actually tree-shakable: You can import from specific modules (super-utils/array, super-utils/object) for optimal bundling. Your users will thank you.

The best parts IMO:

compactNil() - removes only null/undefined, leaves falsy values like 0 and false alone

differenceDeep() - array difference with deep equality (surprisingly useful)

Better random utilities with randomUUID() and randomString()

debounce() that actually works how you expect with proper leading/trailing options

Also genuinely curious - what are your biggest pain points with utility libraries? Did I miss any must-have functions?

7bit · 7 months ago
> Like isObject([]) returning true - arrays aren't objects in my mental model.

Correct me if I am wrong, but Array factually are JS objects and "[] instanceof Object" is true.

Fair enough if that does not fit your mental model, but I would not use any library that treats facts like opinions.

williamdclt · 7 months ago
I agree with the author that it’s almost never what you want. But I agree with you that it’s the reality of the platform, ignoring it will cause its own problems.

I’d surface the footgun rather than trying to pretend it’s not there: isNonArrayObject and isObjectOrArray, or something like that

7bit · 7 months ago
Absolutely agree. I also hate that empty arrays are true, which is different from other languages. But I agree that it's better to face the reality of the language than create a function that evaluates [] to false. It trains you a bad habitnand some day that will cause you to introduce a bug.
moritzwarhier · 7 months ago
Lodash also has "isPlainObject". Not sure if that's as performant as using Array.isArray or even simpler trickery (see below), but it seems that this fixes it.

"isObject" is just not well-defined for other cases that might be interesting, in my opinion. Not just "null" or arrays.

Functions can have properties (although they don't have the object prototype or primitive type).

Class instances behave differently compared to plain objects when used with libraries or other code that inspects the shape or prototype of objects, or wants to serialize them to JSON.

If you deal with unknown foreign values that are expected to be a JSON-serializable value, you could go with something like

  isObject = (o) => !!o && typeof o === "object" && !Array.isArray(o)
But it does not deal with all the non-JSON objects, e.g. Map or other class instances that are not even JSON-serializable by default.

When data comes from parsing unknown JSON, the check above should be enough.

In other cases, the program should already know what is being passed into a function. No matter if through discipline or with the aid of an additional type syntax.

For library code that needs to reflect on unknown data at runtime, I think it's worth looking at Vue2's hilarious solution, probably a very good and performant choice, no matter how icky it might look:

  https://github.com/vuejs/vue/blob/547a64e9b93d24ca5927f653710b5734fa909673/src/util/lang.js#L293
Since the string representations of built-ins have long ago become part of the spec, it seems that there is no real issue with this!

dcsan · 7 months ago
your option is a bit more verbose but definitely more clear. confusing the underlying definitions of the language itself will lead to problems later.
dschuessler · 7 months ago
> any library that treats facts like opinions

The word 'object' has different meanings. One includes arrays and the other does not. They prefer the latter. You prefer the former. I don't think this has much to do with 'facts' and 'opinions', but rather with the practicality of choosing a certain way to speak.

I’d liken it to the word 'sorting'. JavaScript libraries sort in a certain way that is simple to implement. However, this is so different from what we typically mean by 'sorting' that people came up with natural sorting algorithms. Are these people treating facts like opinions on how to sort? I’d rather say, they acknowledge the relevance of a certain way to speak.

bryanrasmussen · 7 months ago
libraries of this sort, especially in JavaScript, often exist to enforce a more reasonable mental model rather than the model baked into the language.
7bit · 7 months ago
I understand that. I just don't think that it is a good habit. Instead of just learning the languages and it's quirks now you form a bad habit and start to rely on one dependency for all your projects.
ivanjermakov · 7 months ago
Distinction is tricky since you can use indexing on plain objects, e.g.

    const foo = {};
    foo[0] = "bar";

rafram · 7 months ago
Right. For the purposes of standard library functions that operate on array-like objects,

  { length: 1, 0: "item" }
is an array.

yoz-y · 7 months ago
What I’d like is a utility library like this, but instead of it being an actual library, be it some utility that generates a single file with exports of the few functions I need. Even just something that would make copy pasting them easier.

As in, I want actual zero dependencies, not even the library itself. The reason: I never want these to randomly update.

acbart · 7 months ago
Couldn't you just pin a specific version dependency? My brain says there's some way to also pin to a hash, but that would require googling and I'm on mobile.
mystifyingpoi · 7 months ago
Pinning is a good strategy (I'd say that it should be the default one), but depending on your level of paranoia (think left-pad), you might consider just downloading the lib as it is, and storing it in source control forever.
nodewrangler · 7 months ago
The problem is that even if you pin to a version, at some point you’ll need to update node, typescript, or some other package, and then if this package doesn’t update, then you may have to migrate from it to something else. While js tries to enforce backwards compatibility, and npm, etc. help with the complex landscape, in practice with node, typescript, etc., even with LLMs helping, it can be a pita and hours or days of work to update at times. It’s just not worth it for things you could’ve just implemented yourself. There are exceptions to this, though.
hinkley · 7 months ago
This is why running your own mirror is what most large companies do. Guarantee no take-backs.
fsloth · 7 months ago
Nobody wants anything _ever_ to ”randomly update”. Why this is the default setting on so many development setups boggles my mind.

I really havent figured out why professional systems insist running on the bleeding edge - it’s your feet are bleeding here I believe. 10 year … 15 year old code is generally excellent if you know it through and thorough.

chrisweekly · 7 months ago
CVEs are a thing. If vulnerabilities are discovered in your dependency graph, choosing to ignore them can have severe consequences.
skydhash · 7 months ago
As my experience grows, I'm getting fonder of stances like Debian or Common Lisp, which favors stability. Once you've solved a problem, it's not fun having your foundation morphs under you for no other reasons than bundling features and security updates.
parentheses · 7 months ago
OOC, what is the benefit of having a "library" that requires such manual labor to maintain and upgrade?

You'd miss out on CVEs because you don't use the common dependency paradigm.

You'd also miss out on bug fixes if you are not detecting the bug itself.

Help me understand because I'm with you on less dependencies but this does feel a bit extreme.

hofrogs · 7 months ago
Why would small functions like "difference", "groupBy", "flatten", etc. have CVEs and require bug fixes? Implementing those correctly is a one and done thing
programmarchy · 7 months ago
shadcn distribution model for utils is a good idea. i wanted something for react hooks as well and was surprised that didn’t seem to exist either.
michaelsbradley · 7 months ago
Why not copy & paste the code you need into a vendor/ subdir?

If the vendored code needs to be updated because of a change in your build tools or whatever then you’ll likely be making similar changes to other parts of your project.

yoz-y · 7 months ago
This is the approach I'm sometimes using, but it would be nice to have tooling around that :)
_1tan · 7 months ago
We use es-toolkit to replace Lodash - how would you compare your library?

We just migrated a React app with around 500k LOC and this worked quite well and flawless.

dhax_or · 7 months ago
I've not used es-toolkit but from what I see you get lower bundle size, typescript support and a better performance with our library. I will be releasing the the benchmark soon enough so do watch the repository if you can
uwemaurer · 7 months ago
I use es-toolkit. It is fully in Typescript. Every function can be imported without any extra overhead, for simple functions it just adds a few bytes then to the bundle. I doubt "better performance" since most helpers functions are just tiny and there is no room for significant improvements.

So I think trying to be better here is pointless, better focus on offering more helpful utility functions which might be missing in es-toolkit

dannyfritz07 · 7 months ago
I don't think we've really seen many successors to LoDash other than Ramda because the platform now has many of Underscore's functions built in.
gaaaaaaaarf · 7 months ago
Nice work! Reminds me of https://github.com/angus-c/just

Suggestion: add more tests and run some benchmarks

dhax_or · 7 months ago
Thanks for this! I've already started adding more tests. I ran some benchmarks and they were really impressive. I will get it out in the next release
addandsubtract · 7 months ago
Not to be confused with the other Just: https://microsoft.github.io/just/
latchkey · 7 months ago

Deleted Comment

meeech · 7 months ago
don't discount the value of a good docs site. that was one of things i loved about lodash that made it so easy to use, and to discover all the functionality it offered. So if you looking to replace it, would be good to have similar docs.
thih9 · 7 months ago
About pain points / feature requests:

Is there an idiomatic way to duplicate a hash while replacing one of its values, preferably something that supports nesting?

Whenever I work with react and immutable structures, this comes up and I hack something simple.

I don’t do FE on a regular basis though so my perspective may be skewed.

sethaurus · 7 months ago
These days that's pretty well-supported in the base language:

    const updated = { ...existing, someKey: someNewValue };
You mention nesting. That starts to look messier:

    const updated = { ...existing, someKey: { ...existing.someKey, ...someNewValue } };

There's a whole cottage industry of little libraries to make this ergonomic/fast in the general case (copying immutable objects with nested changes). `immer` is a popular choice. But the reality is that it gets complicated to do this generically; in my view it's usually better to just use the base language where possible, even if it means sprouting some util functions for the various kinds of updates you end up doing.

antifa · 7 months ago
There might be a function called produce in one of your immutable/react lib.