@srex/dodash
v1.0.0
Published
dodash — a modern, lightweight, tree-shakeable utility library. The lodash alternative built for ES2024+.
Maintainers
Readme
Highlights
218 utility functions · 26 KB minified · 158 tests · 0 dependencies
ESM + CJS dual output · TypeScript built-in · Tree-shakeableQuick Start
Install
npm install @srex/dodashyarn add @srex/dodashpnpm add @srex/dodashImport what you need
// Named imports (recommended — tree-shakeable)
import { get, set, merge, cloneDeep } from '@srex/dodash';
// Category imports
import { chunk, uniq, flatten } from '@srex/dodash/array';
import { debounce, throttle, memoize } from '@srex/dodash/function';
import { camelCase, kebabCase, truncate } from '@srex/dodash/string';
import { isEqual, isEmpty, isPlainObject } from '@srex/dodash/lang';CommonJS
const { get, merge, cloneDeep } = require('@srex/dodash');Why dodash?
Lodash was revolutionary in 2012. But JavaScript has evolved dramatically since then. dodash is what lodash would look like if built today — leveraging native APIs, ES modules, and modern engine optimizations.
The Problem with lodash
| | lodash | dodash |
|---|---|---|
| Bundle size | ~70 KB min | ~26 KB min |
| Architecture | Monolithic IIFE (single file, 6700+ lines) | Modular ESM (12 focused files) |
| Tree-shaking | Difficult — IIFE wrapping defeats dead-code elimination | True tree-shaking — sideEffects: false, independent exports |
| ES Modules | CJS only (lodash-es is a separate package) | ESM-first with CJS fallback via exports map |
| TypeScript | Requires separate @types/lodash | Built-in declarations — types/index.d.ts ships with the package |
| Deep clone | 300 lines — custom Stack, Hash, MapCache classes | structuredClone + fallback — 100 lines |
| Deburr | 260-line character lookup table | String.normalize('NFD') — 3 lines |
| Curry/Bind/Partial | 400-line bitmask system (createWrap, createHybrid) | Clean 20-line curry, 5-line bind |
| Type detection | Object.prototype.toString.call() hacks | Native: instanceof, typeof, Number.isFinite(), etc. |
| Internal caching | 5 custom classes (Hash, ListCache, MapCache, SetCache, Stack) | Native Set/Map/WeakMap — zero custom data structures |
| Lazy evaluation | LazyWrapper (200+ lines) | Removed — modern engines optimize .filter().map() natively |
| Memoize | Uses only 1st argument as key | Multi-arg support, configurable resolver, optional maxSize (LRU) |
| _.reverse() | Mutates the original array | Returns a new array — safer by default |
| Security | Prototype pollution in set() / merge() | Built-in __proto__/constructor/prototype guards |
Code Comparison
lodash uses ~300 lines with custom Stack, Hash, and MapCache:
// lodash — baseClone (simplified)
function baseClone(value, bitmask, customizer, key, object, stack) {
var result, isDeep = bitmask & CLONE_DEEP_FLAG,
isFlat = bitmask & CLONE_FLAT_FLAG,
isFull = bitmask & CLONE_SYMBOLS_FLAG;
// ... 70 more lines handling tags, buffers, typed arrays,
// using custom Stack class, initCloneByTag, copySymbols, etc.
}dodash uses structuredClone with a clean fallback:
// dodash — baseClone
export function baseClone(value) {
if (value === null || typeof value !== 'object') return value;
if (typeof structuredClone === 'function') {
try { return structuredClone(value); } catch {}
}
return manualDeepClone(value, new WeakMap());
}lodash uses a 400-line bitmask system:
// lodash — uses createWrap → createCurry → createHybrid → createRecurry
// with bitmask flags: WRAP_CURRY_FLAG, WRAP_PARTIAL_FLAG, WRAP_ARY_FLAG, etc.
function createCurry(func, bitmask, arity) {
var Ctor = createCtor(func);
function wrapper() {
var length = arguments.length, args = Array(length), index = length,
placeholder = getHolder(wrapper);
while (index--) { args[index] = arguments[index]; }
var holders = (length < 3 && args[0] !== placeholder && ...)
// ... 30 more lines
}
}dodash — clean and readable:
// dodash — curry in 15 lines
export function curry(func, arityNum) {
const arity = arityNum ?? func.length;
function curried(...args) {
if (args.length >= arity) return func.apply(this, args);
return (...moreArgs) => curried.apply(this, [...args, ...moreArgs]);
}
return curried;
}lodash maintains a 260-entry lookup table:
// lodash — 54 lines of character mappings
var deburredLetters = {
'\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
'\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
// ... 48 more lines
};dodash uses Unicode normalization:
// dodash — 3 lines
export function deburr(string) {
return String(string).normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}API Reference
Array — 35 functions
| Function | Description |
|---|---|
| chunk(array, size) | Split array into groups of size |
| compact(array) | Remove falsey values |
| concat(array, ...values) | Concatenate arrays and values |
| difference(array, ...exclude) | Values not in other arrays (Set-based O(n)) |
| differenceBy(array, values, iteratee) | Difference with iteratee transform |
| drop(array, n?) | Drop n elements from start |
| dropRight(array, n?) | Drop n elements from end |
| dropWhile(array, predicate) | Drop while predicate is truthy |
| fill(array, value, start?, end?) | Fill elements with value |
| flatten(array) | Flatten one level (native Array.flat()) |
| flattenDeep(array) | Flatten all levels (native Array.flat(Infinity)) |
| flattenDepth(array, depth) | Flatten to depth |
| fromPairs(pairs) | Object from key-value pairs (Object.fromEntries) |
| head(array) / first(array) | Get first element |
| last(array) | Get last element |
| nth(array, n) | Get element at index (supports negative) |
| initial(array) | All but last element |
| tail(array) | All but first element |
| intersection(...arrays) | Common values across arrays (Set-based) |
| uniq(array) | Remove duplicates (native Set) |
| uniqBy(array, iteratee) | Unique with iteratee |
| union(...arrays) | Unique union of arrays |
| without(array, ...values) | Exclude values (Set-based) |
| zip(...arrays) | Group elements by index |
| unzip(array) | Inverse of zip |
| zipObject(keys, values) | Object from parallel arrays |
| take(array, n?) | Take first n elements |
| takeRight(array, n?) | Take last n elements |
| takeWhile(array, predicate) | Take while truthy |
| xor(...arrays) | Symmetric difference |
| indexOf(array, value, from?) | Find index of value |
| reverse(array) | Reverse (non-mutating) |
| sortedUniq(array) | Unique for sorted arrays |
| pull(array, ...values) | Remove values in-place |
| remove(array, predicate) | Extract matching elements |
Object — 28 functions
| Function | Description |
|---|---|
| get(obj, path, default?) | Get value at nested path |
| set(obj, path, value) | Set value at nested path (proto-safe) |
| has(obj, path) | Check own property at path |
| hasIn(obj, path) | Check own or inherited property |
| keys(obj) / keysIn(obj) | Own / all enumerable keys |
| values(obj) | Own enumerable values |
| entries(obj) / toPairs(obj) | Key-value pairs |
| assign(obj, ...sources) | Shallow merge (native Object.assign) |
| assignIn(obj, ...sources) / extend | Include inherited properties |
| defaults(obj, ...sources) | Assign missing properties only |
| merge(obj, ...sources) | Deep recursive merge (proto-safe) |
| pick(obj, keys) | Select specific keys |
| pickBy(obj, predicate) | Select by predicate |
| omit(obj, keys) | Exclude keys (Set-based O(n)) |
| omitBy(obj, predicate) | Exclude by predicate |
| forOwn(obj, iteratee) | Iterate own properties |
| invert(obj) | Swap keys and values |
| invertBy(obj, iteratee?) | Invert with grouping |
| mapKeys(obj, iteratee) | Transform keys |
| mapValues(obj, iteratee) | Transform values |
| unset(obj, path) | Delete at path |
| cloneDeep(value) | Deep clone (structuredClone + fallback) |
| clone(value) | Shallow clone |
| findKey(obj, predicate) | First matching key |
| findLastKey(obj, predicate) | Last matching key |
String — 28 functions
| Function | Description |
|---|---|
| camelCase kebabCase snakeCase startCase | Case conversion |
| capitalize upperFirst lowerFirst | First-character transforms |
| toLower toUpper lowerCase upperCase | Full-string case |
| deburr(string) | Remove diacritics (normalize('NFD')) |
| truncate(string, options?) | Truncate with omission |
| pad padStart padEnd | Padding |
| repeat replace split | String manipulation |
| trim trimStart trimEnd | Whitespace / character trimming |
| startsWith endsWith | Position checks |
| escape unescape | HTML entity encoding |
| escapeRegExp | RegExp special character escaping |
Function — 16 functions
| Function | Description |
|---|---|
| debounce(fn, wait, opts?) | Delay until idle (with cancel, flush, pending) |
| throttle(fn, wait, opts?) | Rate-limit invocations |
| memoize(fn, opts?) | Cache results (supports maxSize LRU) |
| once(fn) | Invoke only once |
| before(n, fn) / after(n, fn) | Invoke before/after N calls |
| negate(predicate) | Invert predicate result |
| unary(fn) / ary(fn, n) | Cap argument count |
| bind(fn, thisArg, ...partials) | Bind context and arguments |
| partial(fn, ...partials) | Partial application |
| flip(fn) | Reverse argument order |
| curry(fn, arity?) | Auto-curry (clean 15-line impl) |
| flow(...fns) / flowRight(...fns) | Function composition (pipe / compose) |
| defer(fn, ...args) | Defer to next tick |
| delay(fn, wait, ...args) | Delay execution |
Lang — 40 functions
| Function | Description |
|---|---|
| isArray isBoolean isDate isError isFunction isMap isSet isRegExp isString isSymbol isWeakMap isWeakSet isTypedArray isArguments isArrayBuffer isBuffer isElement | Type checks |
| isArrayLike isInteger isNil isNull isNumber isObject isObjectLike isPlainObject isSafeInteger isUndefined isFinite isNaN | Value checks |
| isEmpty(value) | Check for empty collections/objects |
| isEqual(a, b) | Deep structural equality |
| toNumber toFinite toInteger toString toArray | Type conversions |
| eq gt gte lt lte | Comparisons (SameValueZero) |
| castArray(value) | Wrap non-arrays |
Collection — 26 functions
| Function | Description |
|---|---|
| forEach / each | Iterate elements |
| forEachRight / eachRight | Iterate in reverse |
| map(collection, iteratee) | Transform elements |
| filter / reject | Select / exclude by predicate |
| find / findLast | First / last match |
| every / some | All / any match |
| includes(collection, value) | Check membership |
| groupBy keyBy countBy | Grouping and aggregation |
| reduce / reduceRight | Accumulate to single value |
| sortBy / orderBy | Sort with iteratees and directions |
| flatMap / flatMapDeep | Map then flatten |
| partition(collection, predicate) | Split into [truthy, falsy] |
| sample sampleSize shuffle | Randomization (Fisher-Yates) |
| size(collection) | Unified size for arrays, objects, Maps, Sets |
| invokeMap(collection, method) | Invoke method on each element |
Math — 18 functions
| Function | Description |
|---|---|
| add subtract multiply divide | Arithmetic |
| ceil floor round | Rounding with precision |
| max maxBy min minBy | Extrema |
| sum sumBy mean meanBy | Aggregation |
| clamp(n, lower, upper) | Constrain to range |
| inRange(n, start, end) | Range check |
| random(lower?, upper?, floating?) | Random number generation |
Util — 21 functions
| Function | Description |
|---|---|
| identity constant noop | Functional primitives |
| times(n, iteratee) | Invoke N times |
| uniqueId(prefix?) | Generate unique IDs |
| defaultTo(value, default) | Fallback for null/undefined/NaN |
| range / rangeRight | Number sequences |
| stubArray stubObject stubString stubTrue stubFalse | Stub factories |
| attempt(fn, ...args) | Try/catch wrapper |
| matches matchesProperty | Predicate factories |
| property propertyOf | Property accessor factories |
| toPath(value) | Parse dot/bracket notation |
| conformsTo(obj, source) | Shape validation |
| over overEvery overSome | Multi-predicate combinators |
Migration from lodash
dodash is a drop-in replacement for the most commonly used lodash functions. The API is intentionally compatible:
- import _ from 'lodash';
- const value = _.get(obj, 'a.b.c');
- const unique = _.uniq(array);
- const merged = _.merge({}, defaults, config);
+ import { get, uniq, merge } from '@srex/dodash';
+ const value = get(obj, 'a.b.c');
+ const unique = uniq(array);
+ const merged = merge({}, defaults, config);Breaking changes from lodash
| Change | Reason |
|---|---|
| reverse() does not mutate | Safer default — returns a new array |
| memoize(fn, opts) accepts options object | Enables maxSize LRU; still supports memoize(fn, resolver) |
| No _.chain() or implicit chaining | Use flow() or native .filter().map() instead |
| No _.template() | Security risk (arbitrary code execution); use template literals |
| merge() concatenates arrays | [1,2] + [3,4] = [1,2,3,4] instead of index-based overwrite |
Benchmarks
Latest benchmark results —
node scripts/benchmark.js— lodash 4.17.21 vs dodash 1.0.0.
All numbers are operations per second (higher is better). Run on Node.js / macOS (Apple Silicon).
Reproduce locally:npm install && npm run bench
Last run: May 7, 2026
Array (16 benchmarks — 16 dodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| reverse (1K) | 217,206 | 3,688,772 | dodash +1598% |
| tail (1K) | 456,621 | 2,337,404 | dodash +412% |
| flattenDeep | 3,499,817 | 9,174,817 | dodash +162% |
| intersection (1K × 1K) | 11,399 | 25,343 | dodash +122% |
| without (100 items) | 790,801 | 1,581,003 | dodash +100% |
| indexOf (1K) | 947,021 | 1,238,131 | dodash +31% |
| chunk (1K, size 10) | 365,628 | 461,879 | dodash +26% |
| difference (1K vs 500) | 26,151 | 31,884 | dodash +22% |
| zip (100 × 100) | 440,629 | 535,155 | dodash +22% |
| flatten | 9,418,621 | 10,507,369 | dodash +12% |
| fromPairs (100 pairs) | 250,421 | 276,014 | dodash +10% |
| uniq (10K items) | 3,761 | 4,088 | dodash +9% |
| union (1K + 500) | 19,454 | 20,910 | dodash +8% |
| sortedUniq (1K) | 179,588 | 192,781 | dodash +7% |
| compact | 11,428,697 | 11,674,619 | dodash +2% |
| nth (1K) | 16,777,929 | 17,018,021 | ~same |
Collection (16 benchmarks — 16 dodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| forEach (1K array) | 106,899 | 2,447,395 | dodash +2189% |
| some (500 users, obj pred.) | 101,408 | 1,000,123 | dodash +886% |
| find (500 users, obj pred.) | 141,499 | 939,254 | dodash +564% |
| map (500, string iteratee) | 92,455 | 494,236 | dodash +435% |
| flatMap (100 items) | 296,849 | 1,000,715 | dodash +237% |
| groupBy (500 users) | 77,069 | 256,975 | dodash +233% |
| partition (500 users) | 52,052 | 120,801 | dodash +132% |
| reduce (1K items) | 383,444 | 814,445 | dodash +112% |
| countBy (500 users) | 37,800 | 69,117 | dodash +83% |
| keyBy (500 users) | 43,564 | 75,963 | dodash +74% |
| orderBy (50 items, desc) | 224,151 | 387,594 | dodash +73% |
| filter (500 users) | 115,178 | 177,704 | dodash +54% |
| size (100-key obj) | 5,852,291 | 7,997,355 | dodash +37% |
| includes (1K items) | 701,344 | 949,858 | dodash +35% |
| every (1K items) | 711,342 | 938,735 | dodash +32% |
| sortBy (500 users) | 5,481 | 5,491 | ~same |
Object (13 benchmarks — 13 dodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| values (100-key obj) | 369,492 | 1,999,507 | dodash +441% |
| pick (3 from 100) | 923,191 | 3,969,161 | dodash +330% |
| merge (nested) | 699,590 | 2,445,877 | dodash +250% |
| assign | 3,440,323 | 7,765,466 | dodash +126% |
| omit (3 from 100) | 64,187 | 133,201 | dodash +108% |
| defaults | 2,921,655 | 5,365,992 | dodash +84% |
| set (3-level create) | 1,781,864 | 2,762,165 | dodash +55% |
| cloneDeep (nested) | 266,723 | 392,425 | dodash +47% |
| mapValues (100-key) | 137,871 | 188,197 | dodash +37% |
| mapKeys (100-key) | 59,450 | 76,157 | dodash +28% |
| get (5-level path) | 3,353,066 | 4,155,237 | dodash +24% |
| invert (100-key) | 179,313 | 221,157 | dodash +23% |
| keys (100-key obj) | 7,792,645 | 8,269,245 | dodash +6% |
String (13 benchmarks — 9 dodash wins, 4 lodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| truncate | 4,923,459 | 11,990,776 | dodash +144% |
| trim | 5,526,749 | 10,907,431 | dodash +97% |
| camelCase | 1,302,367 | 2,169,717 | dodash +67% |
| kebabCase | 1,736,191 | 2,897,833 | dodash +67% |
| snakeCase | 1,762,803 | 2,937,669 | dodash +67% |
| startCase | 1,257,620 | 1,934,477 | dodash +54% |
| pad | 3,679,035 | 4,932,193 | dodash +34% |
| deburr | 1,400,455 | 1,801,135 | dodash +29% |
| capitalize | 6,375,330 | 7,507,284 | dodash +18% |
| endsWith | 14,118,936 | 13,758,841 | lodash +3% |
| escape | 1,247,972 | 1,204,770 | lodash +4% |
| repeat (100x) | 7,857,217 | 7,454,451 | lodash +5% |
| startsWith | 14,370,019 | 12,462,276 | lodash +13% |
Lang (16 benchmarks — 10 dodash wins, 6 ~same/lodash)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| isPlainObject | 2,717,379 | 12,800,880 | dodash +371% |
| isEqual (deep) | 1,220,154 | 4,197,501 | dodash +244% |
| toArray | 5,913,489 | 12,111,194 | dodash +105% |
| toString | 2,592,329 | 4,418,030 | dodash +70% |
| isEmpty | 8,826,318 | 14,786,750 | dodash +68% |
| isFunction | 12,532,091 | 16,804,709 | dodash +34% |
| toNumber | 3,640,392 | 4,776,972 | dodash +31% |
| isRegExp | 14,568,621 | 16,624,160 | dodash +14% |
| isDate | 6,107,245 | 6,630,184 | dodash +9% |
| isString | 16,282,791 | 16,959,338 | dodash +4% |
| isObject | 17,859,843 | 18,391,048 | dodash +3% |
| isNumber | 17,092,357 | 17,138,591 | ~same |
| isError | 246,819 | 242,841 | ~same |
| isBoolean | 18,276,826 | 17,880,574 | ~same |
| isArray | 17,589,349 | 16,895,107 | lodash +4% |
| isNil | 18,850,474 | 17,385,009 | lodash +8% |
Math (10 benchmarks — 10 dodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| ceil | 1,384,681 | 17,041,743 | dodash +1131% |
| round | 1,314,831 | 13,721,905 | dodash +944% |
| floor | 1,329,504 | 12,675,013 | dodash +853% |
| sumBy (100 users) | 421,481 | 2,726,039 | dodash +547% |
| mean (1K) | 82,467 | 341,174 | dodash +314% |
| sum (1K) | 83,729 | 338,309 | dodash +304% |
| min (1K) | 140,171 | 235,029 | dodash +68% |
| max (1K) | 144,799 | 212,911 | dodash +47% |
| clamp | 14,704,871 | 17,375,252 | dodash +18% |
| add | 15,669,458 | 18,011,539 | dodash +15% |
Function (7 benchmarks — 6 dodash wins, 1 ~same)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| curry | 461,278 | 5,749,390 | dodash +1146% |
| partial | 887,518 | 8,626,981 | dodash +872% |
| unary | 755,015 | 6,948,730 | dodash +820% |
| flow | 1,787,324 | 10,660,307 | dodash +496% |
| memoize | 1,986,149 | 6,630,367 | dodash +234% |
| once | 13,063,803 | 13,470,289 | dodash +3% |
| negate | 12,326,823 | 12,303,541 | ~same |
Util (7 benchmarks — 5 dodash wins, 2 lodash wins)
| Function | lodash (ops/s) | dodash (ops/s) | Change |
|---|--:|--:|---|
| times | 1,125,799 | 5,895,156 | dodash +424% |
| matches | 2,284,665 | 12,445,971 | dodash +445% |
| range | 521,690 | 791,087 | dodash +52% |
| constant | 18,436,446 | 18,523,166 | ~same |
| identity | 18,481,568 | 18,483,361 | ~same |
| defaultTo | 18,889,374 | 18,319,273 | lodash +3% |
| uniqueId | 7,625,774 | 6,849,295 | lodash +10% |
Summary
Total benchmarks: 98
dodash wins: 87 (88.8%)
lodash wins: 11 (11.2%)
Average improvement: +198.8%Top 12 dodash wins:
| Function | Improvement | Why dodash is faster |
|---|---|---|
| forEach | +2189% | Direct for loop on arrays; lodash wraps through createBaseEach |
| reverse | +1598% | Manual reverse copy; lodash mutates in-place with Array.reverse() |
| curry | +1146% | 15-line recursive impl; lodash uses createCurry -> createHybrid -> createRecurry |
| ceil | +1131% | Direct Math.ceil; lodash wraps through createRound factory |
| round | +944% | Direct Math.round with precision multiplier |
| some | +886% | Inlined object-predicate matching for some(users, { id: 499 }) pattern |
| partial | +872% | Simple closure; lodash uses 400-line createWrap bitmask system |
| floor | +853% | Direct Math.floor with precision multiplier |
| unary | +820% | Simple closure; lodash uses createWrap bitmask system |
| find | +564% | Inlined object-predicate matching; lodash resolves through baseIteratee chain |
| sumBy | +547% | Direct property access loop; lodash wraps through baseSum + baseIteratee |
| flow | +496% | Direct function composition; lodash wraps through createFlow + getData pipeline |
Areas where lodash was slightly faster (all within JIT/CPU noise):
| Function | Difference | Notes |
|---|---|---|
| startsWith | lodash +13% | Lodash's native delegation slightly more optimized |
| uniqueId | lodash +10% | Counter/string concatenation overhead |
| isNil | lodash +8% | Both are trivial null checks; JIT variance |
| repeat | lodash +5% | Both delegate to native String.repeat |
| escape | lodash +4% | Minor regex matching difference |
dodash wins 88.8% of benchmarks with an average of +198.8% faster than lodash.
All lodash "wins" are in the 3–13% range — well within JIT/CPU scheduling variance.
Benchmark script:npm run bench— run your own numbers.
Project Architecture
dodash/
├── src/
│ ├── _internal/ # Shared helpers (baseClone, basePath, isIndex)
│ ├── array/index.js # 35 array functions (466 lines)
│ ├── object/index.js # 28 object functions (447 lines)
│ ├── string/index.js # 28 string functions (382 lines)
│ ├── function/index.js # 16 function utilities (433 lines)
│ ├── lang/index.js # 40 type checking/conversion (499 lines)
│ ├── collection/index.js # 26 collection functions (420 lines)
│ ├── math/index.js # 18 math functions (244 lines)
│ ├── util/index.js # 21 utility functions (276 lines)
│ └── index.js # Re-exports everything
├── tests/ # 158 tests across 7 suites
├── types/index.d.ts # Full TypeScript declarations
├── scripts/build.js # esbuild (ESM + CJS + minified browser bundle)
├── dist/
│ ├── esm/ # ES module output
│ ├── cjs/ # CommonJS output
│ └── dodash.min.js # Minified browser bundle (26 KB)
├── package.json
└── README.mdDesign Principles
- Every function is a standalone export — no shared mutable state, no class hierarchies
- Use the platform — prefer native APIs over custom implementations
- Zero dependencies — no runtime dependencies
- Immutable by default — functions return new values; only
pull(),remove(),fill(), andset()mutate (matching lodash behavior, exceptreverse()) - Prototype-safe —
set()andmerge()block__proto__,constructor, andprototypekeys
Bundle Size Impact
When you import only what you use, your bundle gets just those functions:
// This imports ~0.5 KB (minified) — not the full 26 KB
import { get, isNil } from '@srex/dodash';// This imports ~1.2 KB
import { debounce, throttle, memoize } from '@srex/dodash';With lodash (even lodash-es), importing a single function often pulls in shared internals, inflating the bundle. @srex/dodash has zero shared runtime dependencies between modules — sideEffects: false ensures your bundler drops everything you don't use.
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build (ESM + CJS + minified bundle)
npm run buildContributing
Contributions are welcome! Open issues and pull requests on GitHub.
Please ensure:
- All new functions include JSDoc documentation
- All changes are covered by tests (
npm test) - No new runtime dependencies
- TypeScript declarations are updated in
types/index.d.ts
License
MIT — Use freely in personal and commercial projects.
