You-Dont-Need-Lodash-Underscore
github.com
github.com
I feel that most people repeating this "you don't need lodash" meme are just saying it because they read it somewhere, and think that bashing on lodash makes them sound cool and looking like they know what they are talking about, much like it happened with jQuery.
There is way too much focus on micro-optimizations like handwriting/copy/pasting common utility functions each time to save 3.5kb in a bundle.
It's just one more of those many examples of engineering perfectionism gone counterproductive, which is, unfortunately, an attitude that is pervasive in this profession and that it rarely gets called out.
For personal projects, self education, or fun - go ahead and implement the entire lodash lib by hand! But in a business setting a 3.5kb reduction in bundle size and 10 hours spend refactoring the lodash code out of your project is probably a waste of time and money.
I'll agree with the advice that you shouldn't add dependencies until you need them, but saying "you never need lodash" is bad advice IMO. Absolutism is a bad quality to harbor, especially when it's over trivial things.
I know jQuery fell out of favor with the JS community and now is treated like a sign of incompetence but - just as everything in programming holy wars - at the end of the day everything is a tool, and you need to pick the right tool for the job. If the job is "get an MVP up as quickly as possible" and lodash/jQuery/underscore helps you do that, why wouldn't you use it?
This gets a bit weird in the JS optimization world because it is true that each library and parts of the library you don't use are going to be extra useless bytes the user has to download. With tree shaking removing the parts of the library you don't use I think this becomes less of an issue.
I do agree with this specific instance though, that you should drop lodash.
Historically lodash/underscore was written for a time before a lot of syntax/standard libraries were added. The new javascript features obviously had lodash in hindsight so they are pretty good at making common things syntactically easier to do or just straight up taking the good functions and standardizing it (e.g., spread operators, arrow functions, Object.assign, Array.flat, Array.flatMap, etc, etc)
However, I don't necessarily agree with this approach of providing snippets for functions which aren't implemented natively. Underscore and Lodash likely have better implementations and much more testing. If you're concerned about bundle size, use tree shaking or individual function imports.
I think one exception to this is if you are writing a library and want to keep dependencies to an absolute minimum.
I get that, but that's not what this list is: this list is re-implementing lodash / underscore using built-in functions. They're not a 1-for-1 replacement, they're complex implementations which may or may not be correct or efficient. If their solution for replacing _.chunk(...) was some built-in Array.chunk(...) or something, that would be fine. But instead, it's this:
> const chunk = (input, size) => {
> return input.reduce((arr, item, idx) => {
> return idx % size === 0
> ? [...arr, [item]]
> : [...arr.slice(0, -1), [...arr.slice(-1)[0], item]];
> }, []);
> };
That's definitely worse than using lodash.
When I see stuff like
const cleanInput = _.compact(input);
I have no idea what the fuck just happened. I have to open the damn documentation of lodash (or whatever else kind of library the author chose to use) to figure out what's going on.Now if that was
const cleanInput = input.filter(elem => !!elem);
I could've gotten on with my day and saved some time.
This does not need any documentation or comment.This might seem like a small issue to you, but damn, I spend a lot of time debugging random projects on GitHub, and the inability of the JavaScript community to write readable code really trips me up every time. Instead of just writing 2-3 lines other people can parse in a second or two, they spend 5 minutes looking up the relevant library function (which doesn't do what they expect half the time) to produce one shitty line of code other people will also spend 5 minutes on trying to decipher it.
I honestly have no more clue what const cleanInput = input.filter(elem => !!elem); means than const cleanInput = _.compact(input); without going to the documentation.
What you meant to say is "This does not need any documentation or comment for myself, because I have already memorized what all the parts of this line of code mean, whereas I have not yet memorized what _.compact means."
Knowing how "filter" works is valuable because now you can use that knowledge in the future on any project that uses JS, unlike "_.compact" which is only applicable in projects that use lodash. You can be relatively confident that you can write "filter(elem => <some function>)" and that other developers will know what "filter" is doing.
The second part is more tricky, but still useful.
The !! operator is just two "!" operators. That's pretty normal too. The first "!" converts an object/number/whatever into the opposite of its boolean value. The second "!" negates the first boolean, thus turning the opposite of the boolean value into the object's original boolean value.
Even if figuring this out on your own is slightly more difficult, it gives you valuable insight into the language (JS is very dynamically-typed and the not operator can cast objects to booleans), which will help you write better/safer/more concise/clearer code in the future.
Sure it does. You've just already read the documentation for filter.
(And frankly, I just had to look up what !! does, despite years of writing JS code.)
It's literally just 2 NOT operators chained together. I won't fault you for never having seen it, but it is curious considering how common it is.
In any case. Looking that up just made you a more knowledgeable JavaScript developer, while looking up some random lodash function won't buy you much at all.
Memorizing multiplication up to 100 will also make you faster at calculating stuff in your head, but I couldn't ever be bothered to do that either.
I don't know if you understand how lucky you are to be able to do this in the first place.
Also, the "compact" method, I don't know if this is the reason it gets its name in lodash etc., exists in the Ruby language and does the same thing.
You just got lodash'd!
Because the ruby method DOESN'T do the same thing. And this is precisely why you have to consult the documentation every single time you see something weird like that.
The difference is that the ruby method doesn't remove stuff like "0" and "false", just "nil".
Still, I prefer having documentation over the possible scenario where I have a few undocumented functions created by other developers for whatever reason. Maybe multiple functions do the same thing. Not to say this is the only alternative, but it is the more common alternative I've seen years ago, and it's a certain type of hell.
JS just has the concept of "false-ish", which you'll likely already know as a JS developer.
No, it doesn't.
> JS just has the concept of "false-ish", which you'll likely already know as a JS developer.
Ruby has a concept of false-ish (which is narrower than JS’s), but Ruby’s “compact” isn't about false-ish-ness, Filtering out false-ish values is .select(&:itself), not .compact
Whether you want to call it the "same" function or "similar" function, lodash's excellent docs reveal its functionality quite handily if someone's confused by it.
They're 2 different functions/methods with 2 different ideas behind them. They're absolutely not each other's equivalent in their respective language.
I'd say that any argument for/against lodash can go both ways: if one argues that it is a micro-optimization to handwrite `range`, another person could argue that it's also a lazy devexp micro-optimization to use lodash. `range`, for example, can be implemented with a one liner in like 5 mins and tested thoroughly with a couple of asserts.
Maybe one could argue that some of the more compositional stuff is not so trivial to write (e.g. cond and friends), but if one's using those over normal small procedural functions, who's really engaging in engineering perfectionism gone counterproductive?
I feel like this happens for a lot of topics in this industry, positively and negatively. e.g TypeScript has been on the receiving end for both.
I understand that by default with JS you can write code that in one place touch every part of the page.
But no framework or language protect you from bad code practices and bad abstractions. They may avoid it at a specific abstraction level but unless you are disciplined and you know what you are doing, you are still screwed beyond that.
Component libraries like React did not invent encapsulation, abstractions, separation of concerns, loose coupling and high cohesion, they just enforce them at their level of abstraction but let you paying the "unoptimized" recalculate all by default.
In fact I saw a bunch of code written in React that are big balls of mud communicating together without a good abstraction logic.
Redux, in the React ecosystem, is pretty good at keep the state in one place but pretty bad to hide implementation details to those component that should not be concerned with them... result? Refactoring a reducer can results in refactoring a lot of smart components. And the verbosity of using Redux has always been off the chart.
The best part of React documentation is here:https://reactjs.org/docs/thinking-in-react.html - Especially in steps 3 and 4. Learn that and use it with the framework of choice or just vanillaJS... You do not use that and you will be in trouble with React or any other framework.
Lodash is convenient, consistent, well-documented, readable, and maintainable. Yes, there's some pretty needlessly redundant methods (isNull for example), but it provides a nice API to do it. The trade-off of a few microseconds is worth it.
Omitting lodash from your entire codebase is not going to magically fix all your problems; there are much more important things you can be doing with your time, such as delivering features or eliminating technical debt.
To reduce the amount you're requiring, you can just get a specific method like require('lodash/omit'). If overhead is a concern, that helps mitigate it.
We have set it to warn, not to error; it's good to raise awareness that you're doing something that has a potential performance cost, but for things like omit the recommended native alternative just isn't worth it.
What I do find it useful for is small projects (like a lambda function) where micro-optimizations DO count.
With native APIs, you're essentially just doing the same things with a slightly different syntax and sure, it feels cleaner. It is cleaner - where those native APIs actually exist. But if you have to support anything but the latest versions of evergreen browsers, now you're worrying about polyfills, build targets, etc etc.
It's not that hard, but it's just one more thing to worry about and one more thing to be surprised by when it fails on a corner case in production. Meanwhile, if you'd just used boring old lodash, it would be working and you could be paying attention to something more important.
The .chunk is a good example (a 7 line function in native vs a 1 line function in Lodash).
I imagine some native functions are faster, but often it doesn't matter.
I say this only partially jokingly: maybe there should be a "You don't need javascript," and we should go back to assembly.
I do think it's silly to include 100 functions and use 2. But if we could properly tree shake lodash there should be no problem in using lodash, you would end up around the same as if you did it natively.
- In C's stdio, I've used a fraction of calls
- In Ruby/Rails + gems, I use a fraction of calls
- In Node/Express, I use a fraction of calls
- In frontend javascript, I use a fraction of the native calls
In frontend dev, I get that this means forcing users to download a library, but for many use cases 4 kb gzipped isn't a big deal (esp if CDNs are involved).
I think it'd be interesting to poll developers on what part of Lodash/Underscore causes them concern:
1. The extra download time/bandwidth
2. The extra costs of packaging (failed builds, extra packaging process)
3. The speed of computation (assuming that some native functions are faster)
4. The concern that libraries should only be used when they are highly utilized (e.g., at least 20% of a library's code should be used before it is appropriate to include it)
How about 0kb?
Whatever happened to shared-resource URLs, where there was a good chance the library was already cached?
Your site loads slowly if the CDN is under load
Your users are now leaked to the CDN (privacy issue)
Of course, if the resource is cached, it's less of an issue, but it's still an issue. If you don't care about those things, go ahead.
I been using ramda heavily for tasks like merging data from normalized redux stores and it's been really handy, but it's also great for simple tasks as well. I find the resulting utilities really easy to test and since Typescript got much better at higher order type inference, it's become much easier to use ramda in a Typescript project.
EDIT: The version of typescript that improved higher order type inference was 3.4[1]
0: https://ramdajs.com/ 1: https://devblogs.microsoft.com/typescript/announcing-typescr...
The resulting code was a mix and match of Ramda and Underscore helpers.
Flipping between Ramda and Underscore documentation the decipher the dozens of helper / utility methods is quite infuriating.
Worse, the Ramda API was previously under heavy development with breaking changes on every upgrade. To stay up to date requires reading through pages and pages of migration guides describing how to migrate off of one deprecated utility method to another.
It definitely had short-term value to the person who wrote the original code with it, but that value quickly faded.
For example, `map` takes the function first and then the collection. That's handy because everything is curried so you can partially apply map:
const double = R.map(x => x * 2);
and can build up helper functions. Something similar in lodash would be: const double = (collection) => _.map(collection, x => x * 2);
which there is nothing wrong with, it's just that the first is more ergonomic with `pipe` (and the potential future feature pipeline operator `|>`).That said, it took a while to adopt at work. It wasn't until we had a critical mass of people who enjoyed functional programming that it really took off. Before that time, we used a few methods lightly (utils like `pathOr` and `propOr` are so nice when you want to safely traverse an object without `o && o.foo && o.foo.bar`) and were very conscious that it might not be everyone's cup of tea.
> You-Dont-Need-To-Write-And-Maintain-Everything-From-Scratch
I've tried writing some of the lodash functions myself to save on a dependency and usually end up missing some edge case that was already considered years ago by the lodash maintainers. I could have just required lodash and been done with it.
It's easy to become allergic to the API design, which subjectively I find old-hat compared to either native implementations or Ramda. (Yes there is lodash/fp, but there's still incongruities). And given the little variances, within the same project you generally don't want to have lodash here, FP or Ramda there - there is a cognitive cost. Dash way or the lo way? I accept that not everyone finds that a problem.
More substantially problematic, if you import all of lodash and you don't enforce an agreed upon subset of its API, you might end up having its broader philosophies imposed upon you.
There are others, but two that really bug me:
- I'd rather not have to grok _.chain when a codebase already has a more idiomatic use of chaining.
- Defensive coding is its own shed of bikes, but lodash applies it liberally, returning an empty array instead of throwing an error when fed undefined. This can obscure root causes, passing problems downstream and imposing an in-the-debugger mindset. Static analysis coverage and type safety can easily fall by the wayside.
(Linters exist to alarm you if you want to prefer native versions, or exclude explicitly specific parts of lodash.)
Especially nowadays with tree-shaking and importing by module, I just don't think it makes sense unless you only need one or two functions.
I much prefer using Lodash because it's reliable and well documented. Everyone on my team knows what it does, knows where to find the documentation, knows where to find the examples, and knows where to find documentation of edge cases. Lodash also includes some wonderful shortcuts and edge case handling.
Seriously, the best part of lodash isn't the code. It's how freaking easy it is to understand and use. If you want to replace lodash, you need to look at everything the package entails - not just the actual lines of code.
array.reduce(function(a, b) {
return a.filter(function(value) {
return b.includes(value);
});
}))
https://github.com/you-dont-need/You-Dont-Need-Lodash-Unders...The suggestion is basically "don't use this well-tested, popular library, copy/paste all this other code (with no tests) into your source tree directly!" which is just ludicrous