js-cool
v6.0.0
Published
Collection of common JavaScript / TypeScript utilities
Downloads
2,435
Maintainers
Readme
js-cool
Collection of common JavaScript / TypeScript utilities
Changelog • Migration Guide v5→v6 • 简体中文
Online Playground
Try js-cool directly in your browser with StackBlitz:
Installation
# pnpm
pnpm add js-cool
# npm
npm install js-coolUsage
// 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
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 |
client → ua 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 propertyFunction 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('[email protected]') // 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('[email protected]') // 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, miniGameURL 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>') // '<div>test</div>'
escape('a < b & c > d') // 'a < b & c > d'
escape('"hello" & \'world\'') // '"hello" & 'world''
// Unescape
unescape('<div>test</div>') // '<div>test</div>'
unescape('&lt;') // '<'
unescape('"hello"') // '"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 charactersgetCHSLength
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('🎉') // truecutCHSString
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') // 0template
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>' })
// '<script>alert("xss")</script>'
// 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) // trueall / 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') // 2zip / 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 } } }) // truegetProperty / 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) // falseEnvironment
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, adateDiff
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) < 0parseUrlParam
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 | nullstorage.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.lengthstorage.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 stringSafe 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., 15Utility
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 parsetoThousands
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 allowedrandomColor
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') // undefinedfixNumber
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 handlerswaiting
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/falseawaitTo
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 asyn