toss / es-toolkit

A modern JavaScript utility library that's 2-3 times faster and up to 97% smaller—a major upgrade to lodash.
https://es-toolkit.slash.page
Other
7.13k stars 329 forks source link

Chainable API #415

Open mfulton26 opened 3 months ago

mfulton26 commented 3 months ago

I just discovered this library and it looks great. What do you think of supporting a chainable API too?

Pure functions are nice but when a developer needs to use multiple methods in a row it gets annoying to have to define intermediary variables or to nest function calls. JavaScript doesn't have a pipeline operator but prototypes can be safely augmented these days using symbols (rather than strings which can cause conflicts because they are not unique).

Example

import $ from 'es-toolkit/$';
import 'es-toolkit/$/array/chunk';

// Splits an array of numbers into sub-arrays of length 2
[1, 2, 3, 4, 5][$.chunk](2);
// Returns: [[1, 2], [3, 4], [5]]

// Splits an array of strings into sub-arrays of length 3
['a', 'b', 'c', 'd', 'e', 'f', 'g'][$.chunk](3);
// Returns: [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]

Implementation

Augmenting modules should be able to be programmatically generated from pure functions (e.g. using ts-morph):

// es-toolkit/$.ts
export interface Symbols {};
export default {} as Symbols;
// es-toolkit/$/.symbols/chunk.ts
import $ from 'es-toolkit/$.ts';

const key = 'chunk';
const value = Symbol(`es-toolkit/${key}`);

declare module 'es-toolkit/$.ts' {
  interface Symbols {
    [key]: typeof value;
  }
}

Object.defineProperty($, key, { value });
// es-toolkit/$/array/chunk.ts
import 'es-toolkit/.symbols/chunk';
import { chunk } from 'es-toolkit/array/chunk';

const key = $.chunk;
function value<T>(this: T[], size: number): T[] {
  return chunk(this, size);
}

declare global {
  interface Array<T> {
    [key](size: number): T[];
  }
}

Object.defineProperty(Array.prototype, key, { value });
D-Sketon commented 3 months ago

Do modifications to the Array.prototype make the sideEffects become true?

mfulton26 commented 3 months ago

Do modifications to the Array.prototype make the sideEffects become true?

I'm not familiar with that flag. Is that a webpack specific flag? I think it would, but the side-effects modules could be published under a different package name while reusing code to avoid such.

mfulton26 commented 3 months ago

After looking at some docs it looks like this can be set to true for only some modules. e.g.

  "sideEffects": ["./src/$/**/*.ts"]
Smrtnyk commented 1 month ago

mutating Array.prototype doesn't sound like the best idea to me

mfulton26 commented 1 month ago

mutating Array.prototype doesn't sound like the best idea to me

Understandably IMO. Historically this is considered unsafe but with the introduction of Symbol in JavaScript this can now be done safely without name collisions (as is possible with String-based keys but not with unique Symbol-based keys). This is commonly done today with polyfills (e.g. via zloirock/core-js: Standard Library). It is this same polyfill idea/process but applied to user-defined prototype methods using Symbol keys instead of TC39-define prototype methods using String keys.