Coder Social home page Coder Social logo

toss / es-toolkit Goto Github PK

View Code? Open in Web Editor NEW
4.2K 21.0 173.0 2.89 MB

A modern JavaScript utility library that's 2-3 times faster and up to 97% smaller—a major upgrade to lodash.

Home Page: https://es-toolkit.slash.page

License: Other

TypeScript 98.68% JavaScript 1.18% Shell 0.15%

es-toolkit's Introduction

es-toolkit · MIT License codecov NPM badge JSR badge

English | 한국어 | 简体中文

es-toolkit is a state-of-the-art, high-performance JavaScript utility library with a small bundle size and strong type annotations.

  • es-toolkit offers a variety of everyday utility functions with modern implementations, such as debounce, delay, chunk, sum, and pick.
  • Designed with performance in mind, es-toolkit achieves 2-3× better performance in modern JavaScript environments.
  • es-toolkit supports tree shaking out of the box, and reduces JavaScript code by up to 97% compared to other libraries.
  • es-toolkit includes built-in TypeScript support, with straightforward yet robust types. It also provides useful type guards such as isNotNil.
  • es-toolkit is battle-tested with 100% test coverage, ensuring reliability and robustness.

Examples

// import from '@es-toolkit/es-toolkit' in jsr.
import { debounce, chunk } from 'es-toolkit';

const debouncedLog = debounce(message => {
  console.log(message);
}, 300);

// This call will be debounced
debouncedLog('Hello, world!');

const array = [1, 2, 3, 4, 5, 6];
const chunkedArray = chunk(array, 2);

console.log(chunkedArray);
// Output: [[1, 2], [3, 4], [5, 6]]

Contributing

We welcome contribution from everyone in the community. Read below for detailed contribution guide.

CONTRIBUTING

License

MIT © Viva Republica, Inc. See LICENSE for details.

Toss

es-toolkit's People

Contributors

bertyhell avatar changwoolab avatar choi2021 avatar d-sketon avatar dayongkr avatar de-novo avatar fvsch avatar guesung avatar haejunejung avatar hanna922 avatar hautest avatar ho991217 avatar jgjgill avatar juno7803 avatar kangju2000 avatar l2hyunwoo avatar manudeli avatar mass2527 avatar minchodang avatar minsoo-web avatar piquark6046 avatar po4tion avatar raon0211 avatar seungrodotlee avatar sossost avatar ssi02014 avatar sunrabbit123 avatar tanggd avatar wondonghwi avatar woowan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

es-toolkit's Issues

Improving Type Definitions in Object Utils

Summary

I believe that when handling functions in Object Utils, it is necessary to define the allowed types more clearly. Currently, the code accepts all types, which negatively impacts the DX(developer experience), particularly with Array.

If Object Utils aims to handle cases related to object, I believe it should also properly handle Array. Otherwise, for utilities dealing with object forms, like Record<string, any>, it would be better to apply Type Narrowing.

1️⃣ Accepts All Types

export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>

The generic type T allows all types, leading to various cases.

expect(omit(1, ["toFixed"])).toEqual({}); // is pass

As shown in the code above, cases that are not used in real environments are allowed. If this utility is meant to handle objects, I think the type should be restricted to objects only.

- export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>
+ export function omit<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>

2️⃣ Array is Also Allowed

When looking into Object Utils, it is evident that array also work. This is because in JavaScript, arrays are a primitive type of object. I'm curious if Object Utils is designed to work sufficiently with arrays.

Below is an example related to the pick utility. This issue is likely present in other Object Utils as well.

// pick
describe("pick", () => {
  // ... other tests
  it("should pick elements from an array", () => {
    const array = [1, 2, 3];

    expect(pick(array, [1, 2])).toEqual({ "1": 2, "2": 3 }); // is pass
  });
});

If this utility is to allow Array(object), I believe the type inference or code needs improvement. For instance, it degrades DX in the pickBy utility example below.

// pickBy
it("Array Test", () => {
  const array = [1, 2, 3];
  const shouldPick = (value: number, key: number) => value === 2;
  const result = pickBy(array, shouldPick); // shouldPick type error
  expect(result).toEqual({ "1": 2 });
});

The value in shouldPick causes a type error. The T[keyof T] for arrays is extremely complex.

image

Specifying all types for the value makes it too numerous and less readable. Therefore, shouldPick has to be inline (callback function) because TypeScript automatically infers the type.

Support for intersectionBy without duplicates

Currently, intersectionBy function doesn't remove duplicates for firstArr. How about creating a new function called intersectionByWithoutDuplicates?

If you agree, can I proceed with this task? :)

Support for `random`

What if you could support a random function that returns a random number for a specific range? I think it would be useful in many different situations.
I would like to work on this if it is possible.

Performance Improvement for `randomInt`

현재는 randomInt가 내부적으로는 Math.floor로 random 함수를 워핑하는 것 같습니다.

다만, Math.floor가 비트연산자 | 0 내지는 ~~로 소숫점 이하를 버리는 것과 결과가 대개(Infinity나 NaN, 음수를 넣었을 때의 결과 제외) 똑같고,
비트연산자 혹은 ~~을 사용하는 것이 퍼포먼스 측면에서 더우 빠르다는 점이 널리 알려져있습니다.

따라서 Math.floor 대신 비트연산자 | 0, 내지 ~~와 같은 방법으로 대체하실 의향은 없으신지 의견드립니다. (비록, 안정성 측면에서 깊은 고려가 필요할겁니다.)

참고 : ~~는 32-bit 숫자에서 performance issue가 있는 것 같습니다.

Currently it seems that randomInt internally warps the random function to Math.floor.

However, Math.floor is a bitwise operator | The result is usually the same as discarding decimal places with 0 or ~~ (excluding the results when Infinity, NaN, or negative numbers are entered),
It is widely known that using bitwise operators or ~~ is faster in terms of performance.

Therefore, instead of Math.floor, we use the bitwise operator | I would like to ask if you are willing to replace it with 0, to ~~. (Although, deep consideration will be needed in terms of stability.)

Note: ~~ seems to have a performance issue with 32-bit numbers.

⛳ Goal: Almost feature parity with lodash

For easy migration from lodash to es-toolkit, our goal is to achieve almost complete feature parity with lodash.

For details, please refer to our compatibility documentation.

We intend to migrate most of the functions that lodash offers, with the following exceptions:

  • Functions that have specialized implementations for specific types of arrays, like sortedUniq.
    • Such functions fall outside the scope of es-toolkit.

If you encounter any lodash features that could complicate migration to es-toolkit, please inform us via comments on the issue.

Support for `minBy`

Support minBy function in two ways would be helpful. (similar as maxBy)

Compares the values of selected properties of the elements in the array when the array is not empty and returns the element with the minimum value. If empty, returns undefined.

Incorrect documentation for `range` function

 * @example
 * // Returns []
 * range(0, -4, -1);

This is a current example of the range function documentation.
However, it now returns [0, -1, -2, -3].
What is the intended behavior of this function?

Does this function support a negative step or not?

I will fix this if you let me know the intended behavior.

Why don't we use Vitest globals?

Vitest provides a convenient testing environment. One of them, the "globals" option, provides auto import functionality.

With this feature, We will no longer need to "import" repeatedly when writing test codes.

You can refer to it here.
You can refer to the global API provided through the "globals" option in this link.

If you accept this issue positively, I would like to work on it.

Support for `invert`

hello!
If you need lodash's invert function,

Can I work on it?

I've given this a try:

type InvertObject = { [key: string]: string };

export function invert<T extends Record<string, any>>(obj: T): InvertObject {
  const result: InvertObject = {};

  for (const key of Object.keys(obj)) {
    const value = obj[key];
    result[value] = key;
  }

  return result;
}

The reason for using the any type in this code is to ensure flexibility. The invert function is designed to work with objects that can have values of any type.

If the above code looks good, may I proceed with enhancing the documentation and tests, and then submit a pull request?

Current bench implementation does not ensure that `es-toolkit` is always faster than `lodash`.

I think implementation for bench should cover various length of array to ensure that it is always faster than lodash

Example

With current implementation, intersection is faster than lodash.

Screenshot 2024-06-15 at 2 49 14 PM

But after I just changed the length to 10000, intersection is slower than lodash

const array1 = Array.from({ length: 10000 }, () => Math.floor(Math.random() * 1000));
const array2 = Array.from({ length: 10000 }, () => Math.floor(Math.random() * 1000));

describe('intersection', () => {
  bench('es-toolkit', () => {
    intersectionToolkit(array1, array2);
  });

  bench('lodash', () => {
    intersectionLodash(array1, array2);
  });
});
Screenshot 2024-06-15 at 2 48 27 PM

Proposal: Adding ESLint Plugin for JSDoc

I think it would be great if we add the ESLint plugin for JSDoc.

By using eslint-plugin-jsdoc, we can easily find mistakes with minimal effort. For example, if a template is missing, it will notify us about that.

Screenshot 2024-07-01 at 9:44:47 PM

In addition to this, it will notify us if we don't use @param or @returns, and it will also let us know if the parameter names don't match the ones used in the function.

I would like to know your opinion on this.

Chunk throwing breaks drop-in replacement

The chunk method throws for int sizes less than 1 (which you've documented). This is different from lodash behavior which always returns an empty array and therefore breaks with the stated design goal of being a drop-in replacement.

Support for `deepFlatThenMap`

👋 Hi!

I'd like to propose a completely new function that doesn't exist in lodash. 🙏

The deepFlatThenMap function I'm proposing doesn't do the flattening after the map call, like lodash's flatMapDeep(map().flat()).

It's a function that flattens an array of all depths, and then applies a callback function to each element.

Below is the code I'm considering

Interface

/**
 * Utility type for recursively unpacking nested array types to extract the type of the innermost element 
 * 
 * ExtractNestedArrayType<(number | (number | number[])[])[]> // number
 * ExtractNestedArrayType<(boolean | (string | number[])[])[]>; // string | number | boolean
 */
type ExtractNestedArrayType<T> = T extends readonly (infer U)[] ? ExtractNestedArrayType<U> : T;

/**
 * @example deepFlatThenMap([1, 2, [3, 4, [5, 6]]])
 */
export function deepFlatThenMap<T>(
  arr: readonly T[]
): ExtractNestedArrayType<T>[];

/**
 * @example deepFlatThenMap([1, 2, [3, 4, [5, 6]]], (item) => ({ id: item }))
 */
export function deepFlatThenMap<T, U>(
  arr: readonly T[],
  iteratee: (item: ExtractNestedArrayType<T>) => U
): U[];

Implementation code

I've tried to keep things as simple as possible. We utilize the Array.prototype.flat that JS provides by default 🙏

export function deepFlatThenMap<T, U>(arr: readonly T[], iteratee?: (item: ExtractNestedArrayType<T>) => U) {
  const flattenList = arr.flat(Infinity) as ExtractNestedArrayType<T>[];

  if (!iteratee) {
    return flattenList;
  }

  return flattenList.map(item => iteratee(item));
}

Compare with usage examples

Default Usage

  • Suggested deepFlatThenMap
const arr = [1, 2, [3, 4, [5, 6]]];
deepFlatThenMap(arr); 
// type: number[] 
// value: [1, 2, 3, 4, 5, 6]
  • Lodash flatMapDeep
const arr = [1, 2, [3, 4, [5, 6]]];
_.flatMapDeep(arr); 
// type: number[] 
// value: [1, 2, 3, 4, 5, 6]

With Iteratee

  • Suggested deepFlatThenMap
const arr = [1, 2, [3, 4, [5, 6]]];
deepFlatThenMap(arr, (item) => ({ id: item }));
// type: { id: number }[]
// value: [{ id: 1}, { id: 2}, { id: 3}, { id: 4}, { id: 5}, { id: 6}];
  • Lodash flatMapDeep
const arr = [1, 2, [3, 4, [5, 6]]];
_.flatMapDeep(arr, (item) => ({ id: item }));
// type: { id: number | (number | number[])[] }[];
// value: [{ id: 1}, { id: 2}, { id: [3, 4, [5, 6]]}];

Browser Compatibility

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat

I think the compatibility of flat is now stable as well.

스크린샷 2024-07-04 오전 1 23 31

`pick` function does not check key value in obj

The current pick function

 for (const key of keys) {
    result[key] = obj[key];
  }

If obj does not have the corresponding key value, I think the problem may occur as below.

const keys = ["key1", "key2", "key4"]
const obj = {key1: 1, key2: 2, key3: 3}

pick(obj, keys) // {key1: 1, key2: 2, key4: undefined}

Why don't we add logic to obj to check if there is a corresponding key value?

Type unity of `debounce`

 39 export function debounce<F extends (...args: any[]) => void>(
 ...
 ...
 46 const debounced = function (...args: Parameters<F>) {

Hi, the type of the (...args) argument of the debounce function is defined as Parameters<F> below, but as any[] above. Is there any other reason for this?

`uniqBy` is slower than lodash

Currently, uniqBy function is implemented with time complexity O(N^2) while lodash's implementation is O(N).

I just report this as an issue because I failed to make this faster than lodash. Even though I made it O(N) using Map or Set, lodash was much faster than my implementation when the array's length was more than 10000.

Below is the benchmark for uniqBy.

const array = Array.from({ length: 10000 }, () => Math.random() * 1000);

describe('uniqBy', () => {
  bench('es-toolkit', () => {
    uniqByToolkit(array, Math.floor);
  });

  bench('lodash', () => {
    uniqByLodash(array, Math.floor);
  });
});
  1. length = 100
Screenshot 2024-06-15 at 2 04 21 PM
  1. length = 1000
Screenshot 2024-06-15 at 2 03 36 PM
  1. length = 10000
Screenshot 2024-06-15 at 2 04 53 PM

Support for `maxBy`

Support max function in two ways would be helpful.

  1. if input param's type is number[]: same as lodash's max
  2. if input param's type is any[]: same as lodash's maxBy

Support for typed `Object.entries`

The omitBy and pickBy function have implemented like this:

const result: Partial<T> = {};

for (const [key, value] of Object.entries(obj)) {
  if (shouldOmit(value, key)) { // or if(!shouldPick(value, key)) {
    continue;
  }

  (result as any)[key] = value;
}

The problem is, typescript does not know if the key from Object.entries(obj) is part of the obj's key. So result had to be asserted as any even though it was declared at the top, which is less type-safe. Also, the value is inferred as any type, too.

Adding type assertion to Object.entries(obj) might be helpful. And changing type of key from string to keyof T. I want to know whether you intentionally set type of key to string instead of keyof T or not.

If you want to utilize typed Object.entries more, please consider adding toPairs function which implemented in lodash.

Let me know what you think!

No `LICENSE` file

Can't find the LICENSE file anywhere in the repo. Therefore the link in the README is broken.

스크린샷 2024-06-13 13 31 36

Generate declaration maps

          Thanks for your contribution! I wish we had time to better configure the settings to generate declaration maps and make the chunk names reflect their actual names. (rather than `chunk-*.mjs` files..)

Originally posted by @raon0211 in #21 (review)

Support for `toFilled`

Fill was added to es-toolkit, but the problem is that it mutates the original array.

We need a new function, toFilled(...), which does not mutate the original array.

Support for `forEachRight`

Hello,
If you need loadsh's forEachRight function, Can I work it?

If it is okay to proceed, lodash's forEachRight supports both Array and Object, but in [#91], I would like to ask if it is only necessary to implement array in the array category.

Support `cancel` to `throttle`

The existing throttle function does not include a cancel feature.
How about adding a cancel feature to the throttle function?

Additionally, using AbortSignal can also be useful.

Let me know your opinion. Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.