Skip to content

saqqdy/js-cool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

473 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

js-cool

Collection of common JavaScript / TypeScript utilities

NPM version npm download tree shaking gzip

ChangelogMigration Guide v5→v6简体中文


Online Playground

Try js-cool directly in your browser:

Open in StackBlitz Open in CodeSandbox


Installation

# pnpm
pnpm add js-cool

# npm
npm install js-cool

Usage

// ES Module
import { osVersion, copy, randomString } from 'js-cool'

// Node.js CommonJS
const { osVersion, copy, randomString } = require('js-cool')

// CDN (Browser)
<script src="https://unpkg.com/js-cool/dist/index.iife.min.js"></script>
<script>
  jsCool.browserVersion()
</script>

// Direct import (tree-shaking friendly)
import copy from 'js-cool/copy'
import { randomString } from 'js-cool'

Migration from v5.x to v6.x

📖 Full Migration Guide中文迁移指南

Quick Summary

Change v5.x v6.x
CJS output dist/index.cjs.js dist/index.js
IIFE output dist/index.global.prod.js dist/index.iife.min.js
Global variable window.JsCool window.jsCool
Client module client ua
getAppVersion() ❌ Use appVersion()
getOsVersion() ❌ Use osVersion()
getScrollPosition() ❌ Use scroll.getPosition()
Storage functions setCache, getCache, ... storage.local, storage.session, storage.cookie

clientua Migration

// v5.x
import { client } from 'js-cool'
client.isMobile()

// v6.x
import { ua } from 'js-cool'
ua.isMobile()

// Or tree-shake
import { isMobile, isWeChat } from 'js-cool/ua'

IE11 Compatibility

js-cool v6.x includes built-in IE11 compatibility without requiring external polyfills. All methods work seamlessly in IE11 through an internal compatibility layer.

How It Works

The library includes an internal _compat.ts module that provides IE11-compatible alternatives to ES6+ features:

ES6+ Feature IE11 Compatible Alternative
Array.from() arrayFrom()
Array.includes() arrayIncludes()
String.includes() strIncludes()
String.startsWith() strStartsWith()
String.endsWith() strEndsWith()
String.repeat() repeatString()
String.padStart() padStart()
String.padEnd() padEnd()
Number.isNaN() isNumberNaN()
Number.isFinite() isNumberFinite()
Number.isInteger() isNumberInteger()
Object.assign() objectAssign()
Object.values() objectValues()
Object.entries() objectEntries()
Object.fromEntries() objectFromEntries()
Object.hasOwn() hasOwn()
globalThis getGlobalObject()
new File() createFile() (falls back to Blob)
Symbol.iterator isIterableCompat()
[...new Set(arr)] arrayUnique()

Functions with IE11 Fallbacks

Some functions have built-in graceful degradation:

Function IE11 Behavior
isURL() Falls back to regex validation when URL API unavailable
getDirParams() Uses regex parsing when URL API unavailable
urlToBlob() Uses XHR when fetch unavailable
isDarkMode() Returns false (media query not supported)
base64ToFile() Returns Blob with name property instead of File

Type Considerations

// base64ToFile returns File | Blob in IE11
import { base64ToFile } from 'js-cool'

const file = base64ToFile(base64String, 'image.png')
// Type: File | Blob
// In modern browsers: File object
// In IE11: Blob with name property

Function Categories

js-cool provides 140+ utility functions organized into 16 categories:

Category Description Functions
String String manipulation camel2Dash, dash2Camel, upperFirst, lowerFirst, capitalize, kebabCase, snakeCase, constantCase, dotCase, pascalCase, titleCase, swapCase, changeCase, reverse, count, truncate, clearHtml, clearAttr, cutCHSString, getCHSLength, template, words, escape, unescape
Array Array processing unique, shuffle, sorter, sortPinyin, chunk, flatten, groupBy, keyBy, countBy, sample, sampleSize, intersect, intersectionBy, union, unionBy, differenceBy, minus, complement, contains, all, any, searchObject, drop, dropRight, take, takeRight, findIndex, findLastIndex, zip, unzip
Object Object manipulation clone, extend, getProperty, setProperty, omit, pick, cleanData, safeParse, safeStringify, mapKeys, mapValues, invert, mergeWith, transform, arrayToCSV, CSVToArray
Type Check Type checking getType, isArray, isObject, isPlainObject, isDate, isRegExp, isWindow, isIterable, isEqual, isEmpty, isNil
Validate Validation functions isEmail, isPhone, isURL, isIDCard, isCreditCard
URL & Browser URL parsing and browser detection getUrlParams, getUrlParam, parseUrlParam, spliceUrlParam, getDirParams, ua, appVersion, browserVersion, compareVersion, nextVersion
DOM DOM manipulation addEvent, removeEvent, stopBubble, stopDefault, copy, windowSize, download, saveFile, downloadFile, downloadUrlFile
Scroll Scroll utilities scroll, getPosition, getProgress, getDirection, isInViewport, scrollTo, scrollToTop, scrollToBottom, scrollBy, lockScroll, unlockScroll, getScrollbarWidth
Storage Browser storage storage, local, session, cookie
Convert Format conversion arrayToCSV, CSVToArray, CSVToJSON, JSONToCSV
Binary Binary data conversion (v6.0.0+) binary, binary.from(), binary.base64, binary.blob, binary.arrayBuffer, binary.file, binary.url, binary.svg, binary.text, binary.dataURL, binary.hex, binary.hash, binary.meta
Number Number processing clamp, round, sum, average, inRange
Date Date processing date, DateParser, formatDate, dateDiff, relativeTime, isToday, isYesterday, isTomorrow, isWeekend, isLeapYear, getDaysInMonth, getQuarter, getDayOfYear, getWeekOfYear, addDate, subtractDate, startOf, endOf
Color Color manipulation hexToRGB, rgbToHSL, RGBToHex, lighten, darken, isLightColor, randomColor
Utility General utilities delay, uuid, randomString, randomNumber, randomNumbers, nextIndex, getFileType, getGlobal, getNumber, fixNumber, toThousands, openUrl, punctualTimer, waiting, fingerprint
Async Flow Async flow control debounce, throttle, retry, awaitTo
Encode Encoding/decoding encodeUtf8, decodeUtf8
Network Network utilities fillIPv6

API Reference

Global

ua

User Agent detection utility.

import { ua } from 'js-cool'

// Get all user agent info
ua.info
// { device: {...}, os: {...}, browser: {...}, environment: {...} }

// Get single info
ua.get('browser') // { name: 'Chrome', version: '123.0.0.0', engine: 'Blink' }
ua.get('device') // { type: 'mobile', mobile: true, tablet: false, ... }
ua.get('os') // { name: 'Android', version: '14' }
ua.get('engine') // { name: 'Blink' }

// Get multiple info
ua.getMultiple(['device', 'os'])
// { device: {...}, os: {...} }

// Quick checks
ua.isMobile() // true/false
ua.isTablet() // true/false
ua.isDesktop() // true/false
ua.isiOS() // true/false
ua.isAndroid() // true/false
ua.isHarmonyOS() // true/false
ua.isWeChat() // true/false
ua.isQQ() // true/false
ua.isMiniProgram() // true/false

// Check if name in UA
ua.has('Chrome') // true/false

// Get language
ua.getLanguage() // 'en-US'

// Get network info
ua.getNetwork() // { online, type, effectiveType, downlink, rtt, saveData }

// Get screen info
ua.getScreen() // { width, height, pixelRatio, orientation, colorDepth }

// Get orientation
ua.getOrientationStatus() // 'portrait' | 'landscape'

patterns

Unified patterns module combining validation, extract, and UA patterns.

import { patterns, validation, extract, DEVICE_PATTERNS, BROWSER_PATTERNS } from 'js-cool'

// Using patterns object
patterns.validation.email.test('user@example.com') // true
patterns.validation.mobile.test('13800138000') // true
patterns.ua.device.mobile.test(navigator.userAgent) // true/false
patterns.ua.browser.chrome.test(navigator.userAgent) // true/false

// Extract patterns (NEW in v6.0.0)
'Price: $99.99'.match(patterns.extract.number) // ['99.99']
'Chrome/120.0.6099.109'.match(patterns.extract.version) // ['120.0.6099.109']
'abc123def456'.match(patterns.extract.integer) // ['123', '456']

// Or import directly
validation.email.test('user@example.com') // true
DEVICE_PATTERNS.mobile.test(navigator.userAgent) // true/false
BROWSER_PATTERNS.chrome.test(navigator.userAgent) // true/false

// Utility functions
patterns.ua.getUserAgent() // get UA string safely
patterns.ua.matchPattern(ua, /Chrome/i) // check if pattern exists
patterns.ua.extractVersion(ua, /Chrome\/(\d+)/i) // '91.0'

// Available validation patterns:
// validation.any, validation.email, validation.mobile, validation.url
// validation.number, validation.chinese, validation.idCard, validation.qq
// validation.ipv4, validation.ipv6, validation.ipv4Private, validation.mac
// validation.uuid, validation.semver, validation.base64, validation.slug
// validation.bankCard, validation.creditCard, validation.hexColor
// validation.password, validation.postcode, validation.username, validation.tel
// validation.json, validation.array, validation.float, validation.string
// validation.date, validation.time, validation.datetime

// Available extract patterns (NEW in v6.0.0):
// extract.number - matches numbers (including decimals)
// extract.integer - matches integers (including negative)
// extract.decimal - matches decimal numbers
// extract.positiveInteger - matches positive integers
// extract.version - matches version strings

// Available UA patterns:
// DEVICE_PATTERNS - mobile, tablet, phone, touch, iphone, ipad, androidPhone, androidTablet
// OS_PATTERNS - windows, macOS, iOS, iPadOS, android, linux, chromeOS, harmonyOS
// BROWSER_PATTERNS - chrome, firefox, safari, edge, opera, ie, samsung, uc, quark, vivaldi, arc, brave, yandex
// ENGINE_PATTERNS - blink, gecko, webkit, trident, edgeHTML
// ENV_PATTERNS - wechat, wxwork, dingtalk, qq, qqBrowser, weibo, alipay, douyin, kuaishou, baidu
//                xiaohongshu, meituan, dianping, taobao, tmall, jd, pinduoduo, miniProgram, miniGame

URL Utilities

URLSearchParams-like API for URL parsing and building, plus a chainable Url class.

import {
  url,
  Url,
  // Query string parsing & building (descriptive names)
  parseQueryString,
  stringifyQueryString,
  // URLSearchParams-like methods (descriptive names)
  getQueryParamValue,
  getAllQueryParamValues,
  hasQueryParam,
  setQueryParam,
  appendQueryParam,
  deleteParam,
  getQueryParamKeys,
  getQueryParamValues,
  getQueryParamEntries,
  // URL property extraction
  getOrigin,
  getHost,
  getHostname,
  getPathname,
  getSearch,
  getHash,
} from 'js-cool'

// Or use short names directly
import { get, getAll, has, set, append, keys, values, entries, parse, stringify } from 'js-cool/url'

// ============ Method 1: Url class (chainable) ============
const u = new Url('https://example.com?id=123')
u.get('id') // '123'
u.set('page', 2).delete('id').toString()
// 'https://example.com?page=2'

// Chainable URL building
new Url('https://api.example.com')
  .path('users', '123')
  .set('fields', 'name')
  .setHash('section')
  .toString()
// 'https://api.example.com/users/123?fields=name#section'

// URL property getters
u.origin // 'https://example.com'
u.host // 'example.com:8080' (with port)
u.hostname // 'example.com' (without port)
u.pathname // '/api/users'
u.search // '?id=123'
u.hash // '#section'

// Hash parameter support
const u2 = new Url('https://a.cn/?ss=1#/path?bb=343')
u2.get('ss') // '1' (from search)
u2.get('bb') // '343' (from hash)
u2.get('ss', 'search') // '1' - explicit scope
u2.get('bb', 'hash') // '343' - explicit scope
u2.toObject() // { ss: '1', bb: '343' }
u2.toDetailObject() // { search: {...}, hash: {...}, all: {...}, source: {...} }

// ============ Method 2: url namespace (static) ============
url.parse('?a=1&b=true', { convert: true }) // { a: 1, b: true }
url.stringify({ a: 1, b: 2 }) // '?a=1&b=2'
url.getOrigin('https://example.com:8080/path') // 'https://example.com:8080'

// URLSearchParams-like (static)
url.get('id', 'https://example.com?id=123') // '123'
url.getAll('id', 'https://example.com?id=1&id=2') // ['1', '2']
url.has('token', 'https://example.com?token=abc') // true
url.set('page', 2, 'https://example.com') // 'https://example.com/?page=2'
url.append('id', 3, 'https://example.com?id=1') // 'https://example.com/?id=1&id=3'
url.delete('token', 'https://example.com?token=abc&id=1') // 'https://example.com/?id=1'

// Iteration
url.keys('https://example.com?a=1&b=2') // ['a', 'b']
url.values('https://example.com?a=1&b=2') // ['1', '2']
url.entries('https://example.com?a=1&b=2') // [['a', '1'], ['b', '2']]

// URL property extraction (static)
url.getOrigin('https://example.com:8080/path') // 'https://example.com:8080'
url.getHost('https://example.com:8080/path') // 'example.com:8080'
url.getHostname('https://example.com:8080/path') // 'example.com'
url.getPathname('https://example.com/api/users?id=1') // '/api/users'
url.getSearch('https://example.com?key=value') // '?key=value'
url.getHash('https://example.com/path#section') // '#section'

// ============ Method 3: Direct function imports ============
import { get, set, parse, stringify } from 'js-cool'

get('id', 'https://example.com?id=123') // '123'
set('page', 2, 'https://example.com') // 'https://example.com/?page=2'
parse('?key1=100&key2=true', { convert: true }) // { key1: 100, key2: true }
stringify({ a: 1, b: 2 }) // '?a=1&b=2'

String

clearAttr

Remove all HTML tag attributes.

import { clearAttr } from 'js-cool'

clearAttr('<div id="test" class="box">content</div>')
// '<div>content</div>'

clearAttr('<a href="url" target="_blank">link</a>')
// '<a>link</a>'

clearAttr('<input type="text" name="field" />')
// '<input />'

clearAttr('<img src="pic.jpg" alt="image" />')
// '<img />'

clearHtml

Remove HTML tags.

import { clearHtml } from 'js-cool'

clearHtml('<div>test<br/>string</div>') // 'teststring'
clearHtml('<p>Hello <b>World</b></p>') // 'Hello World'
clearHtml('<a href="#">link</a>') // 'link'
clearHtml('plain text') // 'plain text'

escape / unescape

Escape/unescape HTML special characters.

import { escape, unescape } from 'js-cool'

// Escape
escape('<div>test</div>') // '&lt;div&gt;test&lt;/div&gt;'
escape('a < b & c > d') // 'a &lt; b &amp; c &gt; d'
escape('"hello" & \'world\'') // '&quot;hello&quot; &amp; &#39;world&#39;'

// Unescape
unescape('&lt;div&gt;test&lt;/div&gt;') // '<div>test</div>'
unescape('&amp;lt;') // '&lt;'
unescape('&quot;hello&quot;') // '"hello"'

getNumber

Extract number(s) from string.

import { getNumber } from 'js-cool'

// Basic usage - returns string
getNumber('Chrome123.45') // '123.45'
getNumber('price: $99.99') // '99.99'
getNumber('version 2.0.1') // '2.0.1'
getNumber('no numbers here') // ''
getNumber('123abc456') // '123456'
getNumber('-12.34') // '-12.34'

// Return as number type
getNumber('Price: $99.99', { type: 'number' }) // 99.99

// Extract all numbers
getNumber('a1b2c3', { multiple: true }) // ['1', '2', '3']
getNumber('Range: 10-20', { multiple: true }) // [10, 20]

// With decimal places
getNumber('Temperature: 36.567°', { decimals: 1 }) // '36.6'

// Multiple numbers as numbers
getNumber('1, 2, 3', { multiple: true, type: 'number' }) // [1, 2, 3]

camel2Dash / dash2Camel

Convert between camelCase and kebab-case.

import { camel2Dash, dash2Camel } from 'js-cool'

// camelCase to kebab-case
camel2Dash('jsCool') // 'js-cool'
camel2Dash('backgroundColor') // 'background-color'
camel2Dash('marginTop') // 'margin-top'
camel2Dash('XMLParser') // 'xml-parser' (consecutive uppercase handled)
camel2Dash('HTMLElement') // 'html-element'
camel2Dash('XMLHttpRequest') // 'xml-http-request'

// kebab-case to camelCase
dash2Camel('js-cool') // 'jsCool'
dash2Camel('background-color') // 'backgroundColor'
dash2Camel('margin-top') // 'marginTop'
dash2Camel('-webkit-transform') // 'WebkitTransform'

upperFirst

Capitalize first letter.

import { upperFirst } from 'js-cool'

upperFirst('hello') // 'Hello'
upperFirst('HELLO') // 'HELLO'
upperFirst('h') // 'H'
upperFirst('') // ''

lowerFirst

Lowercase first letter.

import { lowerFirst } from 'js-cool'

lowerFirst('Fred') // 'fred'
lowerFirst('FRED') // 'fRED'
lowerFirst('hello') // 'hello'
lowerFirst('A') // 'a'
lowerFirst('') // ''

capitalize

Capitalize first character and lowercase rest.

import { capitalize } from 'js-cool'

capitalize('FRED') // 'Fred'
capitalize('HELLO WORLD') // 'Hello world'
capitalize('Hello') // 'Hello'
capitalize('a') // 'A'
capitalize('') // ''

randomString

Generate random string with various options.

import { randomString } from 'js-cool'

// Default: 32 chars with uppercase, lowercase, numbers
randomString() // 'aB3dE7fG9hJ2kL5mN8pQ1rS4tU6vW0xY'

// Specify length
randomString(8) // 'xY7mN2pQ'
randomString(16) // 'aB3dE7fG9hJ2kL5m'

// Using options object
randomString({ length: 16 })
// 'kL5mN8pQ1rS4tU6v'

// Only numbers
randomString({ length: 6, charTypes: 'number' })
// '847291'

// Only lowercase letters
randomString({ length: 8, charTypes: 'lowercase' })
// 'qwertyui'

// Only uppercase letters
randomString({ length: 8, charTypes: 'uppercase' })
// 'ASDFGHJK'

// Multiple char types
randomString({ length: 16, charTypes: ['uppercase', 'number'] })
// 'A3B7C9D2E5F8G1H4'

// Include special characters
randomString({ length: 16, charTypes: ['lowercase', 'number', 'special'] })
// 'a1@b2#c3$d4%e5^f6'

// All char types
randomString({
  length: 20,
  charTypes: ['uppercase', 'lowercase', 'number', 'special'],
})
// 'A1a@B2b#C3c$D4d%E5e^'

// Exclude confusing characters (o, O, 0, l, 1, I)
randomString({ length: 16, noConfuse: true })
// 'aB3dE7fG9hJ2kL5m' (no o, O, 0, l, 1, I)

// Strict mode: must include at least one of each char type
randomString({
  length: 16,
  charTypes: ['uppercase', 'lowercase', 'number'],
  strict: true,
})
// Guaranteed to have at least 1 uppercase, 1 lowercase, 1 number

// Old API style (still supported)
randomString(16, true) // 16 chars with special characters
randomString(true) // 32 chars with special characters

getCHSLength

Get string byte length (full-width chars = 2 bytes). Includes Chinese, emoji, and other wide characters.

import { getCHSLength, isFullWidth } from 'js-cool'

getCHSLength('hello') // 5
getCHSLength('你好') // 4 (2 Chinese chars × 2)
getCHSLength('hello世界') // 9 (5 + 4)
getCHSLength('🎉') // 2 (emoji is full-width)
getCHSLength('') // 0

// Check if a character is full-width
isFullWidth('中') // true
isFullWidth('a') // false
isFullWidth('🎉') // true

cutCHSString

Truncate string by byte length (full-width chars = 2 bytes).

import { cutCHSString } from 'js-cool'

cutCHSString('hello世界', 6) // 'hello世' (5 + 2 = 7 bytes, but we stop at 6)
cutCHSString('hello世界', 6, true) // 'hello世...' (with ellipsis)
cutCHSString('测试字符串', 4) // '测试' (4 bytes = 2 Chinese chars)
cutCHSString('abc', 10) // 'abc' (no truncation needed)

words

Split string into an array of words.

import { words } from 'js-cool'

// Default: split by word boundaries
words('fred, barney, & pebbles') // ['fred', 'barney', 'pebbles']
words('camelCaseHTML') // ['camel', 'Case', 'HTML']
words('PascalCase') // ['Pascal', 'Case']
words('snake_case_string') // ['snake', 'case', 'string']
words('kebab-case-string') // ['kebab', 'case', 'string']

// With custom pattern
words('camelCaseHTML', /[A-Z]{2,}/g) // ['HTML']
words('hello world', /\w+/g) // ['hello', 'world']

// Handle numbers
words('version2Update') // ['version', '2', 'Update']

// Consecutive uppercase
words('HTMLParser') // ['HTML', 'Parser']

constantCase

Convert string to CONSTANT_CASE.

import { constantCase } from 'js-cool'

constantCase('foo-bar') // 'FOO_BAR'
constantCase('foo_bar') // 'FOO_BAR'
constantCase('foo bar') // 'FOO_BAR'
constantCase('fooBar') // 'FOO_BAR'
constantCase('XML-parser') // 'XML_PARSER'

dotCase

Convert string to dot.case.

import { dotCase } from 'js-cool'

dotCase('fooBar') // 'foo.bar'
dotCase('foo-bar') // 'foo.bar'
dotCase('foo_bar') // 'foo.bar'
dotCase('foo bar') // 'foo.bar'

pascalCase

Convert string to PascalCase.

import { pascalCase } from 'js-cool'

pascalCase('foo-bar') // 'FooBar'
pascalCase('foo_bar') // 'FooBar'
pascalCase('foo bar') // 'FooBar'
pascalCase('XML-parser') // 'XmlParser'

titleCase

Convert string to Title Case.

import { titleCase } from 'js-cool'

titleCase('hello world') // 'Hello World'
titleCase('foo-bar-baz') // 'Foo Bar Baz'
titleCase('foo_bar_baz') // 'Foo Bar Baz'
titleCase('fooBarBaz') // 'Foo Bar Baz'

swapCase

Swap the case of each character in a string.

import { swapCase } from 'js-cool'

swapCase('Hello World') // 'hELLO wORLD'
swapCase('JavaScript') // 'jAVAsCRIPT'
swapCase('ABCdef') // 'abcDEF'
swapCase('123abc') // '123ABC'

changeCase

Unified API for all case conversions.

import { changeCase } from 'js-cool'

changeCase('fooBar', 'kebab') // 'foo-bar'
changeCase('foo-bar', 'camel') // 'fooBar'
changeCase('foo_bar', 'pascal') // 'FooBar'
changeCase('fooBar', 'snake') // 'foo_bar'
changeCase('fooBar', 'constant') // 'FOO_BAR'
changeCase('fooBar', 'dot') // 'foo.bar'
changeCase('foo bar', 'title') // 'Foo Bar'
changeCase('Hello', 'swap') // 'hELLO'
changeCase('hello', 'upper') // 'HELLO'
changeCase('HELLO', 'lower') // 'hello'
changeCase('hello', 'upperFirst') // 'Hello'
changeCase('Hello', 'lowerFirst') // 'hello'

reverse

Reverse a string (Unicode aware).

import { reverse } from 'js-cool'

reverse('hello') // 'olleh'
reverse('你好世界') // '界世好你'
reverse('Hello 世界') // '界世 olleH'
reverse('café') // 'éfac'

count

Count occurrences of a substring.

import { count } from 'js-cool'

count('hello hello hello', 'hello') // 3
count('aaa', 'aa') // 1 (non-overlapping by default)
count('aaa', 'aa', { overlapping: true }) // 2 (overlapping matches)
count('Hello World', 'hello', { caseSensitive: false }) // 1
count('abc', 'd') // 0

template

Simple template engine with variable interpolation.

import { template } from 'js-cool'

// Basic usage
const compiled = template('Hello, {{ name }}!')
compiled({ name: 'World' }) // 'Hello, World!'

// HTML escaping (default)
const safe = template('{{ content }}')
safe({ content: '<script>alert("xss")</script>' })
// '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'

// Raw output (triple braces)
const raw = template('{{{ html }}}')
raw({ html: '<strong>bold</strong>' }) // '<strong>bold</strong>'

// Nested properties
const nested = template('{{ user.name }} is {{ user.age }} years old.')
nested({ user: { name: 'John', age: 30 } }) // 'John is 30 years old.'

// Using function as data resolver
const fnCompiled = template('Hello, {{ name }}!')
fnCompiled((path) => ({ name: 'World' }[path])) // 'Hello, World!'

// Custom delimiters
const custom = template('Hello, ${ name }!', { open: '${', close: '}' })
custom({ name: 'World' }) // 'Hello, World!'

// Multiple variables
const multi = template('{{ a }} and {{ b }} and {{ c }}')
multi({ a: 1, b: 2, c: 3 }) // '1 and 2 and 3'

Array

shuffle

Shuffle array or string.

import { shuffle } from 'js-cool'

// Shuffle array
shuffle([1, 2, 3, 4, 5]) // [3, 1, 5, 2, 4]
shuffle(['a', 'b', 'c']) // ['c', 'a', 'b']

// Shuffle string
shuffle('hello') // 'lleho'
shuffle('abcdefg') // 'gfedcba'

// Shuffle with size limit
shuffle([1, 2, 3, 4, 5], 3) // [4, 1, 5] (3 random elements)
shuffle('hello', 3) // 'leh' (3 random chars)

unique

Remove duplicates from array.

import { unique } from 'js-cool'

unique([1, 2, 2, 3, 3, 3]) // [1, 2, 3]
unique(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']
unique([1, '1', 1]) // [1, '1']
unique([true, false, true]) // [true, false]
unique([null, null, undefined]) // [null, undefined]

intersect

Intersection of multiple arrays.

import { intersect } from 'js-cool'

intersect([1, 2, 3], [2, 3, 4]) // [2, 3]
intersect([1, 2, 3], [2, 3, 4], [3, 4, 5]) // [3]
intersect(['a', 'b'], ['b', 'c']) // ['b']
intersect([1, 2], [3, 4]) // []

union

Union of multiple arrays.

import { union } from 'js-cool'

union([1, 2], [3, 4]) // [1, 2, 3, 4]
union([1, 2], [2, 3]) // [1, 2, 3]
union([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]
union(['a'], ['b'], ['c']) // ['a', 'b', 'c']

minus

Difference of multiple arrays (elements in first but not in others).

import { minus } from 'js-cool'

minus([1, 2, 3], [2, 3, 4]) // [1]
minus([1, 2, 3, 4], [2, 3]) // [1, 4]
minus([1, 2, 3], [2], [3]) // [1]
minus(['a', 'b', 'c'], ['b']) // ['a', 'c']

complement

Complement of multiple arrays (elements not in all arrays combined).

import { complement } from 'js-cool'

complement([1, 2], [2, 3]) // [1, 3]
complement([1, 2], [3, 4]) // [1, 2, 3, 4]
complement(['a', 'b'], ['b', 'c']) // ['a', 'c']

contains

Check if array contains element.

import { contains } from 'js-cool'

contains([1, 2, 3], 2) // true
contains([1, 2, 3], 4) // false
contains(['a', 'b'], 'a') // true
contains([null], null) // true
contains([NaN], NaN) // true

all / any

Check array elements against predicate.

import { all, any } from 'js-cool'

// all - check if all elements pass
all([1, 2, 3], x => x > 0) // true
all([1, 2, 3], x => x > 1) // false
all(['a', 'b'], x => x.length === 1) // true
all([], x => x > 0) // true (empty array)

// any - check if any element passes
any([1, 2, 3], x => x > 2) // true
any([1, 2, 3], x => x > 10) // false
any(['hello', 'world'], x => x.includes('o')) // true
any([], x => x > 0) // false (empty array)

countBy

Count occurrences grouped by iteratee.

import { countBy } from 'js-cool'

countBy([6.1, 4.2, 6.3], Math.floor) // { '4': 1, '6': 2 }
countBy(['one', 'two', 'three'], 'length') // { '3': 2, '5': 1 }
countBy([{ type: 'a' }, { type: 'b' }, { type: 'a' }], 'type') // { a: 2, b: 1 }
countBy(['apple', 'banana', 'apricot'], item => item[0]) // { a: 2, b: 1 }

intersectionBy

Intersection with iteratee.

import { intersectionBy } from 'js-cool'

intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor) // [2.1]
intersectionBy([{ x: 1 }, { x: 2 }], [{ x: 1 }], 'x') // [{ x: 1 }]
intersectionBy([1, 2, 3], [2, 3, 4], n => n) // [2, 3]

unionBy

Union with iteratee.

import { unionBy } from 'js-cool'

unionBy([2.1], [1.2, 2.3], Math.floor) // [2.1, 1.2]
unionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], 'x') // [{ x: 1 }, { x: 2 }]
unionBy([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]

differenceBy

Difference with iteratee.

import { differenceBy } from 'js-cool'

differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor) // [1.2]
differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], 'x') // [{ x: 2 }]
differenceBy([1, 2, 3], [], n => n) // [1, 2, 3]

drop / dropRight

Drop elements from array.

import { drop, dropRight } from 'js-cool'

// drop - from beginning
drop([1, 2, 3, 4, 5], 3) // [4, 5]
drop([1, 2, 3]) // [2, 3] (default: 1)
drop([1, 2, 3], 0) // [1, 2, 3]
drop([1, 2, 3], 5) // []

// dropRight - from end
dropRight([1, 2, 3, 4, 5], 3) // [1, 2]
dropRight([1, 2, 3]) // [1, 2] (default: 1)
dropRight([1, 2, 3], 0) // [1, 2, 3]
dropRight([1, 2, 3], 5) // []

take / takeRight

Take elements from array.

import { take, takeRight } from 'js-cool'

// take - from beginning
take([1, 2, 3, 4, 5], 3) // [1, 2, 3]
take([1, 2, 3]) // [1] (default: 1)
take([1, 2, 3], 0) // []
take([1, 2, 3], 5) // [1, 2, 3]

// takeRight - from end
takeRight([1, 2, 3, 4, 5], 3) // [3, 4, 5]
takeRight([1, 2, 3]) // [3] (default: 1)
takeRight([1, 2, 3], 0) // []
takeRight([1, 2, 3], 5) // [1, 2, 3]

findIndex / findLastIndex

Find index with predicate.

import { findIndex, findLastIndex } from 'js-cool'

const users = [
  { user: 'barney', active: false },
  { user: 'fred', active: false },
  { user: 'pebbles', active: true },
]

// findIndex - from beginning
findIndex(users, ({ active }) => active) // 2
findIndex(users, { user: 'fred' }) // 1
findIndex(users, ['user', 'barney']) // 0
findIndex(users, 'active') // 2 (truthy check)

// findLastIndex - from end
findLastIndex([1, 2, 3, 4], n => n > 2) // 3
findLastIndex(users, { user: 'fred' }) // 1
findLastIndex(users, 'active') // 2

zip / unzip

Zip and unzip arrays.

import { zip, unzip } from 'js-cool'

// zip - combine arrays
zip(['a', 'b', 'c'], [1, 2, 3]) // [['a', 1], ['b', 2], ['c', 3]]
zip(['a', 'b'], [1, 2], [true, false]) // [['a', 1, true], ['b', 2, false]]

// unzip - separate arrays
unzip([['a', 1], ['b', 2]]) // [['a', 'b'], [1, 2]]
unzip([['a', 1, true], ['b', 2, false]]) // [['a', 'b'], [1, 2], [true, false]]

Object

extend

Deep merge objects.

import { extend } from 'js-cool'

// Shallow copy
const result1 = extend({}, { a: 1 }, { b: 2 })
// { a: 1, b: 2 }

// Deep merge
const result2 = extend(true, {}, { a: { b: 1 } }, { a: { c: 2 } })
// { a: { b: 1, c: 2 } }

// Override values
const result3 = extend(true, {}, { a: 1, b: 2 }, { b: 3 })
// { a: 1, b: 3 }

// Merge arrays
const result4 = extend(true, {}, { arr: [1, 2] }, { arr: [3] })
// { arr: [3, 2] }

// Multiple sources
const result5 = extend(true, {}, { a: 1 }, { b: 2 }, { c: 3 })
// { a: 1, b: 2, c: 3 }

clone

Deep clone object.

import { clone } from 'js-cool'

const obj = { a: { b: 1, c: [1, 2, 3] } }
const cloned = clone(obj)

cloned.a.b = 2
cloned.a.c.push(4)

obj.a.b // still 1
obj.a.c // still [1, 2, 3]

// Clone array
const arr = [{ a: 1 }, { b: 2 }]
const clonedArr = clone(arr)

// Clone Date
const date = new Date()
const clonedDate = clone(date)

// Clone RegExp
const regex = /test/gi
const clonedRegex = clone(regex)

isEqual

Deep equality comparison.

import { isEqual } from 'js-cool'

isEqual({ a: 1 }, { a: 1 }) // true
isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (order doesn't matter)
isEqual([1, 2, 3], [1, 2, 3]) // true
isEqual(NaN, NaN) // true
isEqual(null, null) // true
isEqual(undefined, undefined) // true

isEqual({ a: 1 }, { a: 2 }) // false
isEqual([1, 2], [2, 1]) // false (order matters)
isEqual({ a: 1 }, { a: 1, b: 2 }) // false

// Deep comparison
isEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } }) // true

getProperty / setProperty

Get/set nested property by path.

import { getProperty, setProperty } from 'js-cool'

const obj = {
  a: {
    b: [{ c: 1 }, { c: 2 }],
    d: { e: 'hello' },
  },
}

// Get property
getProperty(obj, 'a.b.0.c') // 1
getProperty(obj, 'a.b.1.c') // 2
getProperty(obj, 'a.d.e') // 'hello'
getProperty(obj, 'a.b') // [{ c: 1 }, { c: 2 }]

// With default value
getProperty(obj, 'a.b.5.c', 'default') // 'default'
getProperty(obj, 'x.y.z', null) // null

// Set property
setProperty(obj, 'a.b.0.c', 100)
// obj.a.b[0].c === 100

setProperty(obj, 'a.d.e', 'world')
// obj.a.d.e === 'world'

setProperty(obj, 'a.new', 'value')
// obj.a.new === 'value'

mapKeys / mapValues

Transform object keys or values.

import { mapKeys, mapValues } from 'js-cool'

// mapKeys - transform keys
mapKeys({ a: 1, b: 2 }, (value, key) => key + value) // { a1: 1, b2: 2 }

// mapValues - transform values
mapValues({ a: 1, b: 2 }, n => n * 2) // { a: 2, b: 4 }
mapValues({ a: { x: 1 }, b: { x: 2 } }, 'x') // { a: 1, b: 2 }

invert

Invert object keys and values.

import { invert } from 'js-cool'

invert({ a: '1', b: '2', c: '3' }) // { '1': 'a', '2': 'b', '3': 'c' }
invert({ a: 1, b: 2, c: 1 }) // { '1': 'c', '2': 'b' } (duplicate values: last wins)
invert({ x: 'apple', y: 'banana' }) // { apple: 'x', banana: 'y' }

mergeWith

Merge objects with custom strategy function.

import { mergeWith } from 'js-cool'

// Custom array merge (concat instead of replace)
mergeWith({ a: [1, 2] }, { a: [3, 4] }, (objValue, srcValue) => {
  if (Array.isArray(objValue)) {
    return objValue.concat(srcValue)
  }
})
// { a: [1, 2, 3, 4] }

// Skip certain properties
mergeWith({ a: 1, b: 2 }, { a: 10, b: 20 }, (objValue, srcValue, key) => {
  if (key === 'b') return objValue // keep original
})
// { a: 10, b: 2 }

// Merge multiple objects
mergeWith({ a: 1 }, { b: 2 }, { c: 3 }, (objValue, srcValue) => srcValue ?? objValue)
// { a: 1, b: 2, c: 3 }

transform

Transform object or array with custom accumulator.

import { transform } from 'js-cool'

// Transform object to array
transform({ a: 1, b: 2, c: 3 }, (result, value, key) => {
  result.push({ key, value })
  return result
}, [])
// [{ key: 'a', value: 1 }, { key: 'b', value: 2 }, { key: 'c', value: 3 }]

// Filter and transform
transform({ a: 1, b: 2, c: 3, d: 4 }, (result, value, key) => {
  if (value % 2 === 0) result[key] = value * 2
}, {})
// { b: 4, d: 8 }

// Early exit by returning false
transform({ a: 1, b: 2, c: 3 }, (result, value, key) => {
  result[key] = value
  if (key === 'b') return false
}, {})
// { a: 1, b: 2 }

searchObject

Deep search in object tree.

import { searchObject } from 'js-cool'

const tree = {
  id: 1,
  name: 'root',
  children: [
    { id: 2, name: 'child1', children: [] },
    { id: 3, name: 'child2', children: [{ id: 4, name: 'grandchild' }] },
  ],
}

// Search by predicate
searchObject(tree, item => item.id === 3, { children: 'children' })
// [{ id: 3, name: 'child2', ... }]

// Search by name
searchObject(tree, item => item.name.includes('child'), {
  children: 'children',
})
// [{ id: 2, ... }, { id: 3, ... }, { id: 4, ... }]

// Custom key set
searchObject(tree, item => item.id > 2, {
  children: 'children',
  id: 'id',
})

Type Checking

import { isArray, isDate, isIterable, isObject, isPlainObject, isRegExp, isWindow } from 'js-cool'

// isArray
isArray([1, 2, 3]) // true
isArray('array') // false
isArray(null) // false

// isObject
isObject({}) // true
isObject([]) // true (arrays are objects)
isObject(null) // false
isObject(function () {}) // true

// isPlainObject
isPlainObject({}) // true
isPlainObject(Object.create(null)) // true
isPlainObject([]) // false
isPlainObject(new Date()) // false

// isDate
isDate(new Date()) // true
isDate('2024-01-01') // false
isDate(1234567890000) // false

// isRegExp
isRegExp(/test/) // true
isRegExp(new RegExp('test')) // true
isRegExp('/test/') // false

// isWindow
isWindow(window) // true (in browser)
isWindow({}) // false

// isIterable
isIterable([1, 2, 3]) // true
isIterable('string') // true
isIterable(new Set()) // true
isIterable(new Map()) // true
isIterable({}) // false
isIterable(null) // false

Environment

import { inBrowser, inNodeJs, isDarkMode, windowSize } from 'js-cool'

// inBrowser - check if running in browser
inBrowser() // true in browser, false in Node.js

// inNodeJs - check if running in Node.js
inNodeJs() // true in Node.js, false in browser

// isDarkMode - check if dark mode is enabled
isDarkMode() // true if user prefers dark mode

// windowSize - get window dimensions
windowSize() // { width: 1920, height: 1080 }
windowSize() // { width: 375, height: 667 } (mobile)

Date

js-cool provides a comprehensive date module with chainable API.

date namespace

import { date, DateParser } from 'js-cool'

// Create DateParser instance
const parser = date() // now
const parser = date('2024-01-15') // from string
const parser = date(1705286400000) // from timestamp

// Chainable API
date('2024-01-15').add(1, 'day').format('YYYY-MM-DD') // '2024-01-16'

date().startOf('month').format() // First day of month at 00:00:00

// Date properties
parser.year // 2024
parser.month // 1-12
parser.day // 1-31
parser.hours // 0-23
parser.minutes // 0-59
parser.seconds // 0-59
parser.dayOfWeek // 0-6 (Sunday = 0)

// Comparison methods
parser.isToday()
parser.isYesterday()
parser.isTomorrow()
parser.isWeekend()
parser.isWeekday()
parser.isLeapYear()
parser.isBefore('2024-02-01')
parser.isAfter('2024-01-01')
parser.isSame('2024-01-15', 'day')

// Manipulation (returns new instance)
parser.add(1, 'day') // Add 1 day
parser.subtract(1, 'week') // Subtract 1 week
parser.startOf('month') // Start of month
parser.endOf('day') // End of day (23:59:59)

// Getters
parser.getQuarter() // 1-4
parser.getWeekOfYear() // 1-52
parser.getDayOfYear() // 1-366
parser.getDaysInMonth() // Days in current month

// Difference
const diff = date('2024-01-01').diff('2024-01-03')
// { days: 2, hours: 0, minutes: 0, ... }

// Relative time
date(Date.now() - 3600000).relativeTime() // '1 hour(s) ago'
date(Date.now() - 3600000).relativeTime(undefined, 'zh') // '1小时前'

// Static methods on date namespace
date.format(new Date(), 'YYYY-MM-DD')
date.diff('2024-01-01', '2024-01-03')
date.isToday(new Date())
date.isLeapYear(2024) // true
date.getDaysInMonth(2024, 1) // 29 (February in leap year)
date.getQuarter('2024-06-15') // 2
date.compare('2024-01-01', '2024-01-02') // -1
date.min('2024-01-01', '2024-01-02').format() // '2024-01-01'
date.max('2024-01-01', '2024-01-02').format() // '2024-01-02'

formatDate

import { formatDate } from 'js-cool'

formatDate(new Date('2024-01-15T10:30:45'), 'YYYY-MM-DD HH:mm:ss')
// '2024-01-15 10:30:45'

formatDate(new Date(), 'YYYY年MM月DD日')
// '2024年01月15日'

formatDate(new Date('2024-01-15T14:30:45'), 'hh:mm A')
// '02:30 PM'

// Format tokens: YYYY, YY, MM, M, DD, D, HH, H, hh, h, mm, m, ss, s, SSS, A, a

dateDiff

import { dateDiff } from 'js-cool'

const diff = dateDiff('2024-01-01', '2024-01-03')
// {
//   days: 2,
//   hours: 0,
//   minutes: 0,
//   seconds: 0,
//   milliseconds: 0,
//   total: { days: 2, hours: 48, minutes: 2880, ... }
// }

relativeTime

import { relativeTime } from 'js-cool'

relativeTime(new Date(Date.now() - 5000)) // '5 seconds ago'
relativeTime(new Date(Date.now() - 3600000)) // '1 hour(s) ago'
relativeTime(new Date(Date.now() + 3600000)) // 'in 1 hour(s)'

// Chinese locale
relativeTime(new Date(Date.now() - 3600000), undefined, 'zh') // '1小时前'

Other date functions

import {
  isToday,
  isYesterday,
  isTomorrow,
  isWeekend,
  isLeapYear,
  isBefore,
  isAfter,
  isSame,
  getDaysInMonth,
  getQuarter,
  getDayOfYear,
  getWeekOfYear,
  addDate as add,
  subtractDate as subtract,
  startOf,
  endOf,
} from 'js-cool'

isToday(new Date()) // true
isYesterday(new Date(Date.now() - 86400000)) // true
isTomorrow(new Date(Date.now() + 86400000)) // true
isWeekend(new Date('2024-03-16')) // true (Saturday)
isLeapYear(2024) // true

getDaysInMonth(2024, 1) // 29 (February in leap year)
getQuarter('2024-06-15') // 2
getDayOfYear('2024-12-31') // 366 (leap year)
getWeekOfYear('2024-01-01') // 1

// Date manipulation
const tomorrow = add(new Date(), 1, 'day')
const yesterday = subtract(new Date(), 1, 'day')
const start = startOf(new Date(), 'month')
const end = endOf(new Date(), 'day')

Tree-shaking with subpath import

// Import only what you need
import { date, DateParser } from 'js-cool/date'
import { formatDate, relativeTime } from 'js-cool/date'
import { isToday, isLeapYear } from 'js-cool/date'

Browser Detection

import { appVersion, browserVersion, fingerprint, isNumberBrowser, osVersion } from 'js-cool'

// appVersion - get app version from UA
appVersion('Chrome') // '123.0.0.0'
appVersion('Safari') // '17.0'
appVersion('Firefox') // '123.0'
appVersion('MicroMessenger') // '8.0' (WeChat)

// With custom UA
appVersion('Chrome', 'Mozilla/5.0 Chrome/100.0.0.0')
// '100.0.0.0'

// osVersion - get OS name and version
osVersion()
// { name: 'Windows', version: '10.0' }
// { name: 'Mac OS', version: '10.15.7' }
// { name: 'Android', version: '13' }
// { name: 'iOS', version: '17.0' }
// { name: 'Harmony', version: '4.0' }

// browserVersion - get browser name and version
browserVersion()
// { name: 'Chrome', version: '123.0.0.0' }
// { name: 'Safari', version: '17.0' }
// { name: 'Firefox', version: '123.0' }
// { name: 'Edge', version: '123.0.0.0' }

// isNumberBrowser - check if 360 browser
isNumberBrowser() // true if 360 browser

// fingerprint - generate browser fingerprint
fingerprint() // 'wc7sWJJA8'
fingerprint('example.com') // 'xK9mN2pL5' (with custom domain)

URL & Query

compareVersion

Compare version numbers.

import { compareVersion } from 'js-cool'

compareVersion('1.2.3', '1.2.2') // 1 (greater)
compareVersion('1.2.3', '1.2.3') // 0 (equal)
compareVersion('1.2.3', '1.2.4') // -1 (less)
compareVersion('2.0.0', '1.9.9') // 1
compareVersion('1.10.0', '1.9.0') // 1

// With pre-release tags (priority: rc > beta > alpha)
compareVersion('1.0.0-rc.1', '1.0.0-beta.1') // 1
compareVersion('1.0.0-beta.1', '1.0.0-alpha.1') // 1
compareVersion('1.0.0-alpha.1', '1.0.0') // -1

// Practical usage
const needsUpdate = compareVersion(currentVersion, minVersion) < 0

parseUrlParam

Parse URL parameters string.

import { parseUrlParam } from 'js-cool'

// Basic parsing
parseUrlParam('a=1&b=2&c=3')
// { a: '1', b: '2', c: '3' }

// With type conversion
parseUrlParam('a=1&b=2&c=true', true)
// { a: 1, b: 2, c: true }

// Complex values
parseUrlParam('name=hello%20world&list=1,2,3')
// { name: 'hello world', list: '1,2,3' }

// Empty string
parseUrlParam('') // {}

// Special values (not converted even with true)
parseUrlParam('hex=0xFF&bin=0b111&oct=0o77&exp=1e3', true)
// { hex: '0xFF', bin: '0b111', oct: '0o77', exp: '1e3' }

spliceUrlParam

Build URL parameters string.

import { spliceUrlParam } from 'js-cool'

spliceUrlParam({ a: 1, b: 2 })
// 'a=1&b=2'

spliceUrlParam({ name: 'hello world' })
// 'name=hello%20world'

spliceUrlParam({ a: 1, b: null, c: undefined })
// 'a=1' (null and undefined are skipped)

// With options
spliceUrlParam({ a: 1, b: 2 }, { encode: true })
// 'a=1&b=2'

spliceUrlParam({ a: 1, b: 2 }, { encode: false })
// 'a=1&b=2'

// Complex values
spliceUrlParam({ arr: [1, 2, 3] })
// 'arr=1,2,3'

getUrlParam / getUrlParams

Get URL parameters (from location.search, before #).

import { getUrlParam, getUrlParams } from 'js-cool'

// Get single param
getUrlParam('a', '?a=1&b=2') // '1'
getUrlParam('b', '?a=1&b=2') // '2'
getUrlParam('c', '?a=1&b=2') // null

// Get all params
getUrlParams('?a=1&b=2&c=3')
// { a: '1', b: '2', c: '3' }

// With type conversion
getUrlParams('?a=1&b=2', true)
// { a: 1, b: 2 }

// From full URL
getUrlParams('https://example.com?foo=bar')
// { foo: 'bar' }

getQueryParam / getQueryParams

Get query parameters (after #).

import { getQueryParam, getQueryParams } from 'js-cool'

// Get single query param
getQueryParam('a', '#/?a=1&b=2') // '1'
getQueryParam('b', 'https://example.com#/page?a=1&b=2') // '1'

// Get all query params
getQueryParams('#/?a=1&b=2')
// { a: '1', b: '2' }

// With type conversion
getQueryParams('#/?a=1&b=true', true)
// { a: 1, b: true }

getDirParams

Get directory-style URL params with structured result.

import { getDirParams } from 'js-cool'

getDirParams('https://example.com/a/b/c')
// {
//   origin: 'https://example.com',
//   host: 'example.com',
//   hostname: 'example.com',
//   pathname: '/a/b/c',
//   segments: ['a', 'b', 'c'],
//   query: '',
//   hash: ''
// }

getDirParams('/user/123/profile')
// {
//   origin: '',
//   host: '',
//   hostname: '',
//   pathname: '/user/123/profile',
//   segments: ['user', '123', 'profile'],
//   query: '',
//   hash: ''
// }

// With query and hash
getDirParams('https://example.com/api/users?id=123#section')
// {
//   origin: 'https://example.com',
//   host: 'example.com',
//   hostname: 'example.com',
//   pathname: '/api/users',
//   segments: ['api', 'users'],
//   query: 'id=123',
//   hash: 'section'
// }

Storage

The storage namespace provides a unified API for browser storage with expiration support, generic types, and error handling.

import { storage } from 'js-cool'

// Or from subpath for tree-shaking
import { storage, local, session, cookie } from 'js-cool/storage'

storage.local (localStorage)

// Set item
storage.local.set('name', 'value')
storage.local.set('token', 'abc', { expires: 3600 }) // 1 hour expiration

// Get item (returns null if not found or expired)
const name = storage.local.get('name')

// Check existence
storage.local.has('name') // true

// Delete item
storage.local.delete('name')

// Get all keys
storage.local.keys() // ['key1', 'key2', ...]

// Get count
storage.local.length // 2

// Clear all
storage.local.clear()

// Generic type support
interface User { id: number; name: string }
storage.local.set<User>('user', { id: 1, name: 'John' })
const user = storage.local.get<User>('user') // User | null

storage.session (sessionStorage)

Same API as storage.local:

storage.session.set('temp', 'value', { expires: 1800 }) // 30 min expiration
const temp = storage.session.get('temp')
storage.session.has('temp')
storage.session.delete('temp')
storage.session.keys()
storage.session.clear()
storage.session.length

storage.cookie (Cookie)

// Set cookie (default: 1 day)
storage.cookie.set('name', 'value')

// Set with full options
storage.cookie.set('session', 'xyz', {
  expires: 86400, // Expiration in seconds
  path: '/', // Cookie path
  domain: '.example.com', // Cookie domain
  secure: true, // HTTPS only
  sameSite: 'Strict', // 'Strict' | 'Lax' | 'None'
})

// Get cookie
storage.cookie.get('name') // 'value'
storage.cookie.get('nonexistent') // null

// Get all cookies
storage.cookie.getAll() // { name: 'value', other: 'data' }

// Check existence
storage.cookie.has('name') // true

// Delete cookie
storage.cookie.delete('name')
storage.cookie.delete('name', { path: '/', domain: '.example.com' })

// Clear all cookies
storage.cookie.clear()

Error Handling

import { storage, StorageQuotaError, StorageUnavailableError } from 'js-cool'

try {
  storage.local.set('key', largeData)
} catch (e) {
  if (e instanceof StorageQuotaError) {
    console.error('Storage quota exceeded')
  } else if (e instanceof StorageUnavailableError) {
    console.error('Storage not available (SSR or private mode)')
  }
}

Migration from v5.x

v5.x v6.x
setCache(k, v) storage.local.set(k, v)
setCache(k, v, seconds) storage.local.set(k, v, { expires: seconds })
getCache(k) storage.local.get(k)
delCache(k) storage.local.delete(k)
setSession(k, v) storage.session.set(k, v)
getSession(k) storage.session.get(k)
delSession(k) storage.session.delete(k)
setCookie(k, v, seconds) storage.cookie.set(k, v, { expires: seconds })
getCookie(k) storage.cookie.get(k)
getCookies() storage.cookie.getAll()
delCookie(k) storage.cookie.delete(k)

Encoding

Base64

import { decodeBase64, encodeBase64 } from 'js-cool'

// Encode
encodeBase64('hello') // 'aGVsbG8='
encodeBase64('你好') // '5L2g5aW9'
encodeBase64('{"a":1}') // 'eyJhIjoxfQ=='
encodeBase64(12345) // 'MTIzNDU='

// Decode
decodeBase64('aGVsbG8=') // 'hello'
decodeBase64('5L2g5aW9') // '你好'
decodeBase64('eyJhIjoxfQ==') // '{"a":1}'

UTF-8

import { decodeUtf8, encodeUtf8 } from 'js-cool'

encodeUtf8('hello') // encoded string
encodeUtf8('你好') // encoded string
decodeUtf8(encoded) // original string

Safe JSON

import { safeParse, safeStringify } from 'js-cool'

// safeParse - never throws
safeParse('{"a":1}') // { a: 1 }
safeParse('invalid json') // null (no error!)
safeParse('{"a":BigInt(1)}') // { a: BigInt(1) }
safeParse(null) // null
safeParse(undefined) // null

// safeStringify - handles BigInt, circular refs
safeStringify({ a: 1 }) // '{"a":1}'
safeStringify({ a: BigInt(9007199254740993n) }) // handles BigInt
safeStringify({ a: () => {} }) // '{"a":null}'

Event

import { addEvent, removeEvent, stopBubble, stopDefault } from 'js-cool'

// Stop bubbling
document.getElementById('btn').addEventListener('click', e => {
  stopBubble(e) // e.stopPropagation()
})

// Prevent default
document.getElementById('link').addEventListener('click', e => {
  stopDefault(e) // e.preventDefault()
})

// Event delegation
const handler = e => {
  console.log('clicked:', e.target)
}

// Add delegated event
addEvent(document, 'click', handler)

// Remove delegated event
removeEvent(document, 'click', handler)

// Delegate to specific container
addEvent(document.getElementById('list'), 'click', e => {
  if (e.target.tagName === 'LI') {
    console.log('List item clicked')
  }
})

Scroll Utilities

import {
  scroll,
  getPosition,
  getProgress,
  createDirectionTracker,
  isInViewport,
  scrollTo,
  scrollToTop,
  scrollToBottom,
  scrollBy,
  lockScroll,
  unlockScroll,
  getScrollbarWidth,
} from 'js-cool'

// Get scroll position state
getPosition() // 'top' | 'bottom' | undefined
getPosition(element, 5) // with custom element and threshold

// Get scroll progress (0-100)
getProgress() // 0-100
getProgress(element) // custom element

// Track scroll direction
const tracker = createDirectionTracker()
window.addEventListener('scroll', () => {
  const dir = tracker() // 'up' | 'down' | null
})

// Check if element is in viewport
isInViewport(element) // true | false
isInViewport(element, { fully: false }) // allow partial visibility

// Scroll to element
scrollTo('#section')
scrollTo(element, { offset: -80, behavior: 'smooth' })

// Scroll to top/bottom
scrollToTop()
scrollToBottom()

// Scroll by amount
scrollBy(200) // scroll down 200px
scrollBy(-100) // scroll up 100px

// Lock/unlock scroll (useful for modals)
lockScroll()
unlockScroll()
scroll.toggle() // toggle lock state
scroll.isLocked() // check if locked

// Get scrollbar width
getScrollbarWidth() // e.g., 15

Utility

uuid

import { uuid } from 'js-cool'

uuid() // '550e8400-e29b-41d4-a716-446655440000'
uuid() // '6ba7b810-9dad-11d1-80b4-00c04fd430c8'
uuid() // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'

copy

import { copy } from 'js-cool'

// Basic usage
await copy('text to copy') // true

// In async function
async function handleCopy() {
  const success = await copy('Copied text')
  if (success) {
    console.log('Copied!')
  } else {
    console.log('Copy failed')
  }
}

// Copy with fallback
const result = await copy('fallback text')
console.log(result ? 'Success' : 'Failed')

download

import { download, saveFile, downloadFile, downloadUrlFile } from 'js-cool'

// Download with URL (multiple types)
download('https://example.com/file.pdf') // default: anchor download
download('https://example.com/file.pdf', 'document.pdf', 'open') // open in new tab
download('https://example.com/file.pdf', 'document.pdf', 'href') // navigate directly
download('https://example.com/file.pdf', 'document.pdf', 'request') // XHR download

// Save data directly as file
saveFile('Hello World', 'hello.txt')
saveFile(JSON.stringify(data), 'data.json')

// Save Blob or ArrayBuffer
const blob = new Blob(['content'], { type: 'text/plain' })
saveFile(blob, 'file.txt')

// Download file using anchor element
downloadFile('https://example.com/file.pdf', 'document.pdf')

// Download via XHR (for authenticated downloads)
downloadUrlFile('https://api.example.com/download', 'data.json')

openUrl

import { openUrl } from 'js-cool'

openUrl('https://example.com') // Opens in new tab
openUrl('https://example.com/file.pdf') // Downloads if can't parse

toThousands

Format number with thousands separator.

import { toThousands } from 'js-cool'

toThousands(1234567.89) // '1,234,567.89'
toThousands(1234567890) // '1,234,567,890'
toThousands(1234.567) // '1,234.567'
toThousands('1234567') // '1,234,567'
toThousands(null) // ''
toThousands(undefined) // ''

// Custom separator
toThousands(1234567.89, { separator: ' ' }) // '1 234 567.89'
toThousands(1234567.89, { separator: "'" }) // "1'234'567.89"

// With decimals limit
toThousands(1234.5678, { decimals: 2 }) // '1,234.57'

// With currency prefix
toThousands(1234.56, { prefix: '$' }) // '$1,234.56'
toThousands(1234.56, { prefix: '¥', decimals: 2 }) // '¥1,234.56'

// With suffix
toThousands(98.5, { suffix: '%', decimals: 1 }) // '98.5%'

// Combined options
toThousands(1234567.89, { prefix: '$', separator: ' ', decimals: 2 }) // '$1 234 567.89'

randomNumber / randomNumbers

import { randomNumber, randomNumbers } from 'js-cool'

// Random integer
randomNumber() // random int between 1-10
randomNumber(1, 100) // random int between 1-100
randomNumber(0, 1) // 0 or 1
randomNumber(-10, 10) // random int between -10 and 10

// Random float
randomNumber(0.1, 0.9) // random float between 0.1 and 0.9

// Random numbers with fixed sum
randomNumbers(4, 100) // [25, 30, 20, 25] (sum = 100)
randomNumbers(3, 10) // [3, 4, 3] (sum = 10)
randomNumbers(5, 1) // [0, 0, 1, 0, 0] (sum = 1)

// Allow zeros (default: true)
randomNumbers(4, 100, true) // no zeros allowed
randomNumbers(4, 100, false) // zeros allowed

randomColor

import { randomColor } from 'js-cool'

// Random color
randomColor() // '#bf444b'

// Lighter colors (higher minimum)
randomColor(200) // '#d6e9d7' (all channels >= 200)
randomColor(128) // '#a1b2c3' (all channels >= 128)

// Range for all channels
randomColor(200, 255) // '#d3f9e4' (200-255)

// Individual channel ranges
randomColor([0, 100, 0], [100, 255, 100])
// Red: 0-100, Green: 100-255, Blue: 0-100

// Warm colors (more red/yellow)
randomColor([200, 100, 0], [255, 200, 100])

// Cool colors (more blue/green)
randomColor([0, 100, 200], [100, 200, 255])

// Dark colors
randomColor(0, 100) // '#3a2b1c'

// Pastel colors
randomColor(150, 230) // '#c8e6c9'

nextIndex

import { nextIndex } from 'js-cool'

nextIndex() // 1001
nextIndex() // 1002
nextIndex() // 1003

// Useful for modals, tooltips
modal.style.zIndex = nextIndex()

nextVersion

import { nextVersion } from 'js-cool'

nextVersion('1.2.3', 'major') // '2.0.0'
nextVersion('1.2.3', 'minor') // '1.3.0'
nextVersion('1.2.3', 'patch') // '1.2.4'
nextVersion('1.2.3', 'premajor') // '2.0.0-0'
nextVersion('1.2.3', 'preminor') // '1.3.0-0'
nextVersion('1.2.3', 'prepatch') // '1.2.4-0'
nextVersion('1.2.3-alpha.1', 'prerelease') // '1.2.3-alpha.2'

// Default is patch
nextVersion('1.2.3') // '1.2.4'

getType

import { getType } from 'js-cool'

getType([1, 2, 3]) // 'array'
getType({}) // 'object'
getType(null) // 'null'
getType(undefined) // 'undefined'
getType('string') // 'string'
getType(123) // 'number'
getType(true) // 'boolean'
getType(() => {}) // 'function'
getType(new Date()) // 'date'
getType(/regex/) // 'regexp'
getType(new Error()) // 'error'
getType(new Map()) // 'map'
getType(new Set()) // 'set'
getType(Symbol()) // 'symbol'
getType(BigInt(1)) // 'bigint'

getFileType

import { getFileType } from 'js-cool'

getFileType('document.pdf') // 'pdf'
getFileType('image.png') // 'image'
getFileType('video.mp4') // 'video'
getFileType('audio.mp3') // 'audio'
getFileType('archive.zip') // 'archive'
getFileType('code.js') // 'code'
getFileType('https://example.com/file.pdf') // 'pdf'

getGlobal

Safely get a global value by path. A secure alternative to dynamic code execution.

import { getGlobal } from 'js-cool'

// Get global functions
getGlobal('JSON.parse') // ƒ parse()
getGlobal('Number') // ƒ Number()
getGlobal('console.log') // ƒ log()

// Get nested properties
getGlobal('document.body') // <body> element (browser)

// Non-existent path
getGlobal('nonExistent') // undefined
getGlobal('a.b.c') // undefined

fixNumber

import { fixNumber } from 'js-cool'

fixNumber(3.14159) // '3.14' (default 2 decimal places)
fixNumber(3.14159, 2) // '3.14'
fixNumber(3.14159, 4) // '3.1416'
fixNumber(3.1, 4) // '3.1' (no trailing zeros)
fixNumber(100, 2) // '100'

delay

import { delay } from 'js-cool'

const d = delay()

// Debounce mode (first trigger executes immediately)
d.register('search', () => console.log('search'), 300, true)

// Throttle mode (delayed execution)
d.register('scroll', () => console.log('scroll'), 300, false)

// Destroy specific handler
d.destroy('search')

// The delay object stores all registered handlers

waiting

import { waiting } from 'js-cool'

// Basic wait
await waiting(1000) // wait 1 second

// With throw on timeout
await waiting(5000, true) // throws after 5 seconds

// In async function
async function example() {
  console.log('start')
  await waiting(1000)
  console.log('after 1 second')
}

// Practical usage
async function poll() {
  while (true) {
    const result = await checkStatus()
    if (result.done) break
    await waiting(1000) // wait before next poll
  }
}

mapTemplate

import { mapTemplate } from 'js-cool'

// ${xxx} style
mapTemplate('Hello, ${name}!', { name: 'World' })
// 'Hello, World!'

// {{xxx}} style
mapTemplate('Hello, {{name}}!', { name: 'World' })
// 'Hello, World!'

// {xxx} style
mapTemplate('Hello, {name}!', { name: 'World' })
// 'Hello, World!'

// Multiple variables
mapTemplate('${greeting}, ${name}! You have ${count} messages.', {
  greeting: 'Hello',
  name: 'John',
  count: 5,
})
// 'Hello, John! You have 5 messages.'

// Nested object
mapTemplate('User: ${user.name}', { user: { name: 'John' } })
// 'User: John'

sorter

import { sorter } from 'js-cool'

const users = [
  { name: 'Bob', age: 30 },
  { name: 'Alice', age: 25 },
  { name: 'Charlie', age: 35 },
]

// Sort by string field
users.sort(sorter('name', 'asc'))
// [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, ...]

users.sort(sorter('name', 'desc'))
// [{ name: 'Charlie', age: 35 }, { name: 'Bob', age: 30 }, ...]

// Sort by number field
users.sort(sorter('age', 'asc'))
// [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, ...]

// With transform function
users.sort(sorter('name', 'asc', name => name.toLowerCase()))

sortPinyin

Sort Chinese by pinyin with accurate character detection.

import { sortPinyin } from 'js-cool'

// As compare function
['张三', '李四', '王五'].sort(sortPinyin)
// ['李四', '王五', '张三']

// Using sort method (returns new array)
sortPinyin.sort(['张三', '李四', '王五'])
// ['李四', '王五', '张三']

// Mixed content with null/undefined
sortPinyin.sort(['中文', null, 'English', undefined])
// ['English', '中文', null, undefined] - null/undefined at the end

// With options
sortPinyin.sort(['张三', '李四'], { numeric: true })

punctualTimer

import { punctualTimer } from 'js-cool'

// Create timer
const timer = punctualTimer(() => {
  console.log('Tick at:', new Date())
}, 1000)

// Stop timer
timer.stop()

// Restart timer
timer.start()

// Check if running
timer.isRunning // true/false

awaitTo

import { awaitTo } from 'js-cool'

// Basic usage
const [err, data] = await awaitTo(fetch('/api/data'))
if (err) {
  console.error('Error:', err)
  return
}
console.log('Data:', data)

// With async function
async function getUser(id) {
  const [err, user] = await awaitTo(fetch(`/api/users/${id}`))
  if (err) return null
  return user.json()
}

// Multiple promises
const [err, results] = await awaitTo(Promise.all([fetch('/api/users'), fetch('/api/posts')]))

Resource Loading

import { loadSource, mountCss, mountImg, mountJs, mountStyle, preloader } from 'js-cool'

// Load JS file
await mountJs('https://example.com/script.js')
await mountJs('https://example.com/script.js', { async: true })

// Load CSS file
await mountCss('https://example.com/styles.css')

// Inject CSS styles
mountStyle('.modal { display: none; }')
mountStyle(`
  .container { max-width: 1200px; }
  .header { height: 60px; }
`)

// Load image
await mountImg('https://example.com/image.png')
const img = await mountImg('https://example.com/image.png', {
  crossOrigin: 'anonymous',
})

// Generic resource loader
await loadSource({ type: 'js', url: 'https://example.com/script.js' })
await loadSource({ type: 'css', url: 'https://example.com/styles.css' })
await loadSource({ type: 'img', url: 'https://example.com/image.png' })

// Preload images
await preloader(['image1.jpg', 'image2.jpg', 'image3.jpg'])

Binary Module (v6.0.0+)

The binary module provides a unified, chainable API for binary data conversion. It's the recommended replacement for legacy conversion functions.

import { binary } from 'js-cool'

// ============ Chainable Conversion ============
const blob = new Blob(['Hello World'], { type: 'text/plain' })

// Convert to various formats
const base64 = await binary.from(blob).toBase64()
const buffer = await binary.from(blob).toArrayBuffer()
const dataUrl = await binary.from(blob).toDataURL()
const { url, revoke } = await binary.from(blob).toURL()

// From File
const file = input.files[0]
const base64 = await binary.from(file).toBase64()

// From Base64
const blob = await binary.from(base64String, { mime: 'image/png' }).toBlob()
const file = await binary.from(base64String).toFile('image.png', 'image/png')

// ============ Type Detection ============
binary.isBlob(data) // true/false
binary.isFile(data) // true/false
binary.isArrayBuffer(data) // true/false
binary.isDataURL(str) // true/false
binary.isBase64(str) // true/false

// ============ Sub-modules ============

// binary.base64 - Base64 encoding/decoding
const encoded = binary.base64.encode('Hello World')
const decoded = binary.base64.decode(encoded)
const blob = binary.base64.toBlob(base64String, 'image/png')

// binary.blob - Blob operations
const base64 = await binary.blob.toBase64(blob)
const buffer = await binary.blob.toArrayBuffer(blob)
const { url, revoke } = binary.blob.toURL(blob)
const combined = binary.blob.concat([blob1, blob2], 'text/plain')

// binary.arrayBuffer - ArrayBuffer conversions
const base64 = binary.arrayBuffer.toBase64(buffer)
const blob = binary.arrayBuffer.toBlob(buffer, 'image/png')
const hex = binary.arrayBuffer.toHex(buffer)

// binary.file - File conversions
const base64 = await binary.file.toBase64(file)
const buffer = await binary.file.toArrayBuffer(file)

// binary.url - URL to Blob
const blob = await binary.url.toBlob('https://example.com/image.png')
const dataUrl = await binary.url.toDataURL('https://example.com/image.png')

// binary.svg - SVG conversions
const blob = binary.svg.toBlob('<svg>...</svg>')
const dataUrl = binary.svg.toDataURL('<svg>...</svg>')

// binary.text - Text encoding/decoding
const buffer = binary.text.encode('Hello World')
const str = binary.text.decode(buffer)

// binary.dataURL - Data URL parsing
const { mime, base64, data } = binary.dataURL.parse(dataUrl)
const valid = binary.dataURL.isValid(str)

// binary.hex - Hexadecimal encoding/decoding
const hex = binary.hex.encode(buffer)
const buffer = binary.hex.decode(hexString)

// binary.hash - Hash calculations
const md5 = await binary.hash.md5(data)
const sha1 = await binary.hash.sha1(data)
const sha256 = await binary.hash.sha256(data)
const crc = binary.hash.crc32(data) // synchronous

// binary.meta - File metadata
const meta = binary.meta.get(file)
// { size, mime, name, extension, isImage, isVideo, isAudio, isText }

// ============ Utility Methods ============

// Compare two binary inputs
const same = await binary.compare(blob1, blob2) // true/false

// Clone binary data
const cloned = binary.clone(blob)

// Download as file
binary.download(blob, 'file.txt')

// Parse binary data
const info = binary.parse(data)
// { data, type, mime, name, size }

CSV Conversion

import { CSVToArray, CSVToJSON, JSONToCSV, arrayToCSV } from 'js-cool'

const csv = 'name,age,city\nJohn,30,NYC\nJane,25,LA'

// CSV to 2D array
CSVToArray(csv)
// [['name', 'age', 'city'], ['John', '30', 'NYC'], ['Jane', '25', 'LA']]

// 2D array to CSV
arrayToCSV([
  ['name', 'age'],
  ['John', 30],
  ['Jane', 25],
])
// 'name,age\nJohn,30\nJane,25'

// CSV to JSON
CSVToJSON(csv)
// [
//   { name: 'John', age: '30', city: 'NYC' },
//   { name: 'Jane', age: '25', city: 'LA' }
// ]

// JSON to CSV
JSONToCSV(
  [
    { name: 'John', age: 30 },
    { name: 'Jane', age: 25 },
  ],
  ['name', 'age']
)
// 'name,age\nJohn,30\nJane,25'

Color

RGBToHex

import { RGBToHex } from 'js-cool'

RGBToHex(255, 0, 0) // '#ff0000'
RGBToHex(0, 255, 0) // '#00ff00'
RGBToHex(0, 0, 255) // '#0000ff'
RGBToHex(255, 255, 255) // '#ffffff'
RGBToHex(0, 0, 0) // '#000000'

Support & Issues

Please open an issue here.

License

MIT

Sponsor this project

Packages

 
 
 

Contributors

Languages