Show HN: SuperUtilsPlus – A Modern Alternative to Lodash
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?
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.
I’d surface the footgun rather than trying to pretend it’s not there: isNonArrayObject and isObjectOrArray, or something like that
I don’t mind that actually! I don’t think I have much use cases for “empty array is semantically different from non-empty”. Usually I find null/undefined are better choices, an empty array is just a normal array, I don’t expect it to be handled differently
Null/undefined is a better choice, but there's many occasions where you do not have the power of choice. For example with document.querySelectorAll, which returns an empty array if nothing is found. The simple thing to do is to just check for it's length or just iterate over it's nodes, but still. I prefer empty arrays being falsy.
Just to clarify, I'm not saying one is better than the other. I just prefer how it works in other languages like Python. But I still would rather work with the JS language properties, than import a library that changes how I test for empty arrays.
I don’t love it! I think it’s inconsistent with empty arrays being truthy.
In practice, I see a lot more mishandling of empty strings than empty arrays (eg “!myvar” catching null and empty strings when it’s not what was meant) which hints that it’s not the right thing to do: an empty string is a valid string, whether its semantically different from non-empty entirely depends on the context and shouldn’t be baked into the langage.
Generally, I think implicit casts are not a great idea and I’d rather they didn’t exist (one of the few things I think Go got right).
> For example with document.querySelectorAll, which returns an empty array if nothing is found
Yes I think it’s doing the right thing. It’s indeed returning the list of elements, whether there’s 0 elements or more it’s still a valid list. As you say, you don’t usually need to handle it any differently from a non-empty array (just iterate), so I don’t think it should cast to a different value.
However if for example you were passing something like a whitelist as an argument, I’d expect [] to mean “there is a whitelist, allow only these items (ie allow nothing)” and null to mean “no whitelist, everything allowed”. These two things are semantically different, I think it makes sense to cast to different values.
I do see your point and it’s definitely at least partly a matter of taste. Again id rather there was no implicit conversion at all, removing the risk of mishandling altogether!
"If is an anti pattern", "null values are an anti pattern", "`string' is an anti-type", "util libraries are an anti pattern", etc.
And of course "boolean is an anti type" but let's not get into that. :D
(Nor into the value of "idiomatic python" as an argument on healthy programming habits....)
"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
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:
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!I’ve been writing JS for so long I’ve forgotten all these language quirks, I feel like it’s fair for most people, these language choices are kind of meaningless in day to day, what’s meaningful is a function returning things that will make sense to most people. Or at least have two functions, languageStrictIsObject()
But when I’m writing a reusable lib and I can’t perfectly abstract a gotcha, I design the API so that the dev _has_ to make an explicit decision rather than defaulting on their behalf (defaults are evil). So, isObjectOrArray and isObjectNonArray: you have to think about which applies. Another example is sorting: does it sort in-place or immutably? Make it explicit in the function name. Does a function expect a sorted array or will it sort itself? Make it explicit in the function name. And maybe provide both variants.
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.
As in, I want actual zero dependencies, not even the library itself. The reason: I never want these to randomly update.
[0] https://bower.io
[1] https://addyosmani.com/blog/checking-in-front-end-dependenci...
I experienced both sides of this discussion (project that always pulled :latest disregarding any kind of versioning, and project that had node_modules commited inside the repo) and both extremes suck, but I lean towards the second one. I'll totally take a few days of pain over not knowing whether prod will work today or not.
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.
CVE database is an excellent way to be informed about vulnerabilities and there are services to automatically map CVE reports to code bases.
What's the alternative? Are you suggesting that backpatching transitive deps dating back over a decade-plus tineframe is a viable maintenance strategy?
The "10-15" year old comment can be taken in the context of language specifications for example. C++11 is a totally fine language standard, and since backward compatibility is the only reason for C++ to exist at this point there is no intrinsic benefit in using a later version.
This, I agree with. Though for modern codebases, leveraging tools like Dependabot is very helpful. Deliberate upgrades, with automation to make it practical.
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.
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.
We just migrated a React app with around 500k LOC and this worked quite well and flawless.
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
Suggestion: add more tests and run some benchmarks
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.
There’s genuinely never a reason to use new String(). You should treat non-primitive String instances as bugs.
edit: the types on remeda look great though! If I were doing a backend-only NodeJS project, I'd be super tempted to test it out.
What do you do differently?
No comments yet
No comments yet
I don’t understand everything on the HN frontpage either.
And so if Lodash is what they’re trying to replace, is that not enough info to infer what Lodash might be?
The pharma ad comparison seems more than a little hyperbolic to me.
You either care about Viagra and read or move on.
this github's readme says "alternative to lodash". other than being named "superutilsplus", someone who clicked on it would need to then go google for lodash to see it's another js utilities kit, to then figure out they don't care.
I stopped professional javascript development when i stopped having an office next to Ryan Dahl at Joyent, so, yeah i haven't cared for about a decade. Thanks for explaining about atoms, though, my esteemed chemists.