Ultra-lightweight string utilities with zero dependencies. Tree-shakeable, fully typed, and optimized for modern JavaScript.
🚀 Migration Guide - Step-by-step guide for migrating from lodash/underscore
- 🚀 Zero dependencies - No bloat, just pure functions
- 📦 < 1KB per function - Minimal bundle impact
- 🌳 Tree-shakeable - Only import what you need
- 💪 Fully typed - Complete TypeScript support with function overloads and template literal types
- ⚡ Fast performance - 2-25x faster than lodash for many operations
- ⚡ ESM & CJS - Works everywhere
- 🧪 100% tested - Reliable and production-ready
- 🔒 Type-safe - Written in strict TypeScript with enhanced type inference and compile-time transformations
- 🛡️ Null-safe - All functions handle null/undefined gracefully without throwing errors
- 📝 Well documented - JSDoc comments for all functions
nano-string-utils works seamlessly across all modern JavaScript runtimes:
Runtime | Support | Installation | CLI |
---|---|---|---|
Node.js ≥18 | ✅ Full | npm install nano-string-utils |
✅ |
Deno | ✅ Full | deno add @zheruel/nano-string-utils |
✅ |
Bun | ✅ Full | bun add nano-string-utils |
✅ |
Browser | ✅ Full | Via bundler or CDN | ❌ |
All core functions use standard JavaScript APIs and work identically across runtimes. The CLI tool supports Node.js, Deno, and Bun.
npm install nano-string-utils
# or
yarn add nano-string-utils
# or
pnpm add nano-string-utils
// From JSR (recommended)
import { slugify } from "jsr:@zheruel/nano-string-utils";
// Or add to your project
deno add @zheruel/nano-string-utils
bun add nano-string-utils
<!-- Latest version -->
<script src="https://unpkg.com/nano-string-utils/dist/index.iife.js"></script>
<!-- Or specific version -->
<script src="https://cdn.jsdelivr.net/npm/nano-string-utils@0.16.0/dist/index.iife.js"></script>
<script>
// All functions available on global nanoStringUtils object
const slug = nanoStringUtils.slugify("Hello World!");
console.log(slug); // 'hello-world'
</script>
For modern browsers with ES modules:
<script type="module">
import {
slugify,
camelCase,
} from "https://unpkg.com/nano-string-utils/dist/index.js";
console.log(slugify("Hello World")); // 'hello-world'
console.log(camelCase("hello-world")); // 'helloWorld'
</script>
import {
slugify,
camelCase,
truncate,
isEmail,
fuzzyMatch,
} from "nano-string-utils";
// Transform strings with ease
slugify("Hello World!"); // 'hello-world'
camelCase("hello-world"); // 'helloWorld'
truncate("Long text here", 10); // 'Long te...'
// Validate inputs
isEmail("user@example.com"); // true
isEmail("invalid.email"); // false
// Advanced string matching
fuzzyMatch("gto", "goToLine"); // { matched: true, score: 0.546 }
fuzzyMatch("abc", "xyz"); // null (no match)
Convert any string to a URL-safe slug.
slugify("Hello World!"); // 'hello-world'
slugify(" Multiple Spaces "); // 'multiple-spaces'
slugify("Special@#Characters!"); // 'special-characters'
Convert strings to camelCase for JavaScript variables.
camelCase("hello-world"); // 'helloWorld'
camelCase("HELLO_WORLD"); // 'helloWorld'
camelCase("Hello World"); // 'helloWorld'
Intelligently truncate text with customizable suffix.
truncate("Long text here", 10); // 'Long te...'
truncate("Long text here", 10, "→"); // 'Long tex→'
Validate email addresses with a robust regex.
isEmail("user@example.com"); // true
isEmail("test@sub.domain.com"); // true
isEmail("invalid.email"); // false
Perform fuzzy string matching for search features.
fuzzyMatch("usrctrl", "userController.js"); // { matched: true, score: 0.444 }
fuzzyMatch("of", "openFile"); // { matched: true, score: 0.75 }
📖 See all 45 functions in the API Reference below
Nano String Utils includes a command-line interface for quick string transformations directly in your terminal. The CLI works with Node.js, Deno, and Bun!
# Global installation
npm install -g nano-string-utils
nano-string slugify "Hello World"
# Using npx (no installation required)
npx nano-string-utils slugify "Hello World"
# Direct execution (no installation required)
deno run --allow-read https://unpkg.com/nano-string-utils/bin/nano-string.js slugify "Hello World"
# Or if installed locally
deno run --allow-read node_modules/nano-string-utils/bin/nano-string.js slugify "Hello World"
# Using bunx (no installation required)
bunx nano-string-utils slugify "Hello World"
# Or if installed
bun run nano-string-utils slugify "Hello World"
nano-string <function> <input> [options]
nano-string slugify "Hello World!" # hello-world
nano-string camelCase "hello-world" # helloWorld
nano-string kebabCase "hello_world" # hello-world
nano-string capitalize "hello" # Hello
nano-string reverse "hello" # olleh
echo "Hello World" | nano-string slugify # hello-world
cat file.txt | nano-string truncate --length 50
# Truncate with custom length
nano-string truncate "Long text here" --length 10 # Long te...
# Template interpolation
nano-string template "Hello {{name}}" --data '{"name":"World"}' # Hello World
# Pad strings
nano-string padStart "hi" --length 5 --char "*" # ***hi
# Generate random strings
nano-string randomString --length 10 # Generates 10-character string
# Text processing
nano-string smartSplit "Dr. Smith went to the store. He bought milk." # ['Dr. Smith went to the store.', 'He bought milk.']
nano-string humanizeList "apple,banana,orange" --conjunction "or" # apple, banana, or orange
nano-string isEmail "test@example.com" # true
nano-string isUrl "https://example.com" # true
nano-string isASCII "hello" # true
nano-string wordCount "hello world test" # 3
nano-string levenshtein "kitten" "sitting" # 3
nano-string diff "hello" "hallo" # Shows differences
To see all available functions:
nano-string --help
For help on a specific function:
nano-string slugify --help
The library provides 48 string utility functions organized by category. Click on any category to explore the available functions.
🔤 Case Conversion Functions (10 functions)
Transform strings between different naming conventions commonly used in programming.
Converts a string to a URL-safe slug.
slugify("Hello World!"); // 'hello-world'
slugify(" Multiple Spaces "); // 'multiple-spaces'
Converts a string to camelCase.
camelCase("hello world"); // 'helloWorld'
camelCase("hello-world"); // 'helloWorld'
camelCase("hello_world"); // 'helloWorld'
Converts a string to snake_case.
snakeCase("hello world"); // 'hello_world'
snakeCase("helloWorld"); // 'hello_world'
snakeCase("hello-world"); // 'hello_world'
Converts a string to kebab-case.
kebabCase("hello world"); // 'hello-world'
kebabCase("helloWorld"); // 'hello-world'
kebabCase("hello_world"); // 'hello-world'
Converts a string to PascalCase.
pascalCase("hello world"); // 'HelloWorld'
pascalCase("hello-world"); // 'HelloWorld'
pascalCase("hello_world"); // 'HelloWorld'
Converts a string to CONSTANT_CASE.
constantCase("hello world"); // 'HELLO_WORLD'
constantCase("helloWorld"); // 'HELLO_WORLD'
constantCase("hello-world"); // 'HELLO_WORLD'
constantCase("XMLHttpRequest"); // 'XML_HTTP_REQUEST'
Converts a string to dot.case.
dotCase("hello world"); // 'hello.world'
dotCase("helloWorld"); // 'hello.world'
dotCase("hello-world"); // 'hello.world'
dotCase("XMLHttpRequest"); // 'xml.http.request'
dotCase("com/example/package"); // 'com.example.package'
Converts a string to path/case (forward slash separated).
pathCase("hello world"); // 'hello/world'
pathCase("helloWorld"); // 'hello/world'
pathCase("hello-world"); // 'hello/world'
pathCase("hello_world"); // 'hello/world'
pathCase("XMLHttpRequest"); // 'xml/http/request'
pathCase("src.components.Header"); // 'src/components/header'
pathCase("com.example.package"); // 'com/example/package'
Converts a string to title case with proper capitalization rules.
titleCase("the quick brown fox"); // 'The Quick Brown Fox'
titleCase("a tale of two cities"); // 'A Tale of Two Cities'
titleCase("mother-in-law"); // 'Mother-in-Law'
titleCase("don't stop believing"); // "Don't Stop Believing"
titleCase("NASA launches rocket"); // 'NASA Launches Rocket'
titleCase("2001: a space odyssey"); // '2001: A Space Odyssey'
// With custom exceptions
titleCase("the lord of the rings", {
exceptions: ["versus"],
}); // 'The Lord of the Rings'
Converts a string to sentence case (first letter of each sentence capitalized).
sentenceCase("hello world"); // 'Hello world'
sentenceCase("HELLO WORLD"); // 'Hello world'
sentenceCase("hello. world! how are you?"); // 'Hello. World! How are you?'
sentenceCase("this is the first. this is the second."); // 'This is the first. This is the second.'
sentenceCase("the u.s.a. is large"); // 'The u.s.a. is large'
sentenceCase("i love javascript"); // 'I love javascript'
sentenceCase("what? when? where?"); // 'What? When? Where?'
✂️ String Manipulation (11 functions)
Essential functions for transforming and manipulating text content.
Capitalizes the first letter of a string and lowercases the rest.
capitalize("hello world"); // 'Hello world'
capitalize("HELLO"); // 'Hello'
Reverses a string.
reverse("hello"); // 'olleh'
reverse("world"); // 'dlrow'
Truncates a string to a specified length with an optional suffix.
truncate("Long text here", 10); // 'Long te...'
truncate("Long text here", 10, "→"); // 'Long tex→'
Creates a smart excerpt from text with word boundary awareness.
excerpt("The quick brown fox jumps over the lazy dog", 20); // 'The quick brown fox...'
excerpt("Hello world. This is a test.", 15); // 'Hello world...'
excerpt("Long technical documentation text here", 25, "…"); // 'Long technical…'
excerpt("Supercalifragilisticexpialidocious", 10); // 'Supercalif...'
Pads a string to a given length by adding characters to both sides (centers the string).
pad("Hi", 6); // ' Hi '
pad("Hi", 6, "-"); // '--Hi--'
pad("Hi", 7, "-"); // '--Hi---'
Pads a string to a given length by adding characters to the left.
padStart("5", 3, "0"); // '005'
padStart("Hi", 5, "."); // '...Hi'
padStart("Hi", 6, "=-"); // '=-=-Hi'
Pads a string to a given length by adding characters to the right.
padEnd("Hi", 5, "."); // 'Hi...'
padEnd("Hi", 6, "=-"); // 'Hi=-=-'
padEnd("5", 3, "0"); // '500'
Removes diacritics/accents from Latin characters.
deburr("café"); // 'cafe'
deburr("naïve"); // 'naive'
deburr("Bjørn"); // 'Bjorn'
deburr("São Paulo"); // 'Sao Paulo'
deburr("Müller"); // 'Muller'
Counts the number of words in a string.
wordCount("Hello world test"); // 3
wordCount("One-word counts as one"); // 5
Generates a random string of specified length.
randomString(10); // 'aBc123XyZ9'
randomString(5, "abc"); // 'abcab'
randomString(8, "0123456789"); // '42318765'
Generates a simple hash from a string (non-cryptographic).
hashString("hello"); // 99162322
hashString("world"); // 113318802
📝 Text Processing (14 functions)
Advanced text processing utilities for handling HTML, whitespace, special characters, and entity extraction.
Removes HTML tags from a string.
stripHtml("<p>Hello <b>world</b>!</p>"); // 'Hello world!'
stripHtml("<div>Text</div>"); // 'Text'
Security-focused string sanitization for safe use in web applications by removing or escaping dangerous content.
sanitize("<script>alert('xss')</script>Hello"); // 'Hello'
sanitize("<b>Bold</b> text", { allowedTags: ["b"] }); // '<b>Bold</b> text'
sanitize("javascript:alert(1)"); // ''
sanitize("<div onclick='alert(1)'>Click</div>"); // 'Click'
Redacts sensitive information from text for UI/logging purposes. Supports SSN, credit cards, emails, and phone numbers with customizable redaction strategies.
// Default: redact all types with partial strategy (show last 4)
redact("My SSN is 123-45-6789"); // 'My SSN is ***-**-6789'
redact("Card: 4532-1234-5678-9010"); // 'Card: **** **** **** 9010'
redact("Email: user@example.com"); // 'Email: use***@example.com'
redact("Phone: (555) 123-4567"); // 'Phone: (***) ***-4567'
// Selective type redaction
redact("Email: user@example.com, SSN: 123-45-6789", {
types: ["email"], // Only redact emails
}); // 'Email: use***@example.com, SSN: 123-45-6789'
// Full redaction (no partial reveal)
redact("SSN: 123-45-6789", { strategy: "full" }); // 'SSN: ***-**-****'
// Custom patterns
redact("Secret: ABC-123", {
customPatterns: [{ pattern: /[A-Z]{3}-\d{3}/g, replacement: "[REDACTED]" }],
}); // 'Secret: [REDACTED]'
Escapes HTML special characters.
escapeHtml('<div>Hello & "world"</div>'); // '<div>Hello & "world"</div>'
escapeHtml("It's <b>bold</b>"); // 'It's <b>bold</b>'
Normalizes various Unicode whitespace characters to regular spaces.
normalizeWhitespace("hello world"); // 'hello world'
normalizeWhitespace("hello\u00A0world"); // 'hello world' (non-breaking space)
normalizeWhitespace(" hello "); // 'hello'
normalizeWhitespace("hello\n\nworld"); // 'hello world'
// With options
normalizeWhitespace(" hello ", { trim: false }); // ' hello '
normalizeWhitespace("a b", { collapse: false }); // 'a b'
normalizeWhitespace("hello\n\nworld", { preserveNewlines: true }); // 'hello\n\nworld'
// Handles various Unicode spaces
normalizeWhitespace("café\u2003test"); // 'café test' (em space)
normalizeWhitespace("hello\u200Bworld"); // 'hello world' (zero-width space)
normalizeWhitespace("日本\u3000語"); // '日本 語' (ideographic space)
Removes non-printable control characters and formatting characters from strings.
removeNonPrintable("hello\x00world"); // 'helloworld' (removes NULL character)
removeNonPrintable("hello\nworld"); // 'helloworld' (removes newline by default)
removeNonPrintable("hello\u200Bworld"); // 'helloworld' (removes zero-width space)
removeNonPrintable("hello\u202Dworld"); // 'helloworld' (removes directional override)
// With options
removeNonPrintable("hello\nworld", { keepNewlines: true }); // 'hello\nworld'
removeNonPrintable("hello\tworld", { keepTabs: true }); // 'hello\tworld'
removeNonPrintable("hello\r\nworld", { keepCarriageReturns: true }); // 'hello\rworld'
// Preserves emoji with zero-width joiners
removeNonPrintable("👨👩👧👦"); // '👨👩👧👦' (family emoji preserved)
removeNonPrintable("text\x1B[32mgreen\x1B[0m"); // 'text[32mgreen[0m' (ANSI escapes removed)
Converts a string to ASCII-safe representation by removing diacritics, converting common Unicode symbols, and optionally replacing non-ASCII characters.
toASCII("café"); // 'cafe'
toASCII("Hello "world""); // 'Hello "world"'
toASCII("em—dash"); // 'em-dash'
toASCII("€100"); // 'EUR100'
toASCII("½ + ¼ = ¾"); // '1/2 + 1/4 = 3/4'
toASCII("→ ← ↑ ↓"); // '-> <- ^ v'
toASCII("α β γ"); // 'a b g'
toASCII("Привет"); // 'Privet'
toASCII("你好"); // '' (removes non-convertible characters)
toASCII("你好", { placeholder: "?" }); // '??'
toASCII("Hello 世界", { placeholder: "?" }); // 'Hello ??'
toASCII("© 2024 Müller™"); // '(c) 2024 Muller(TM)'
Converts a singular word to its plural form using English pluralization rules. Optionally takes a count to conditionally pluralize.
pluralize("box"); // 'boxes'
pluralize("baby"); // 'babies'
pluralize("person"); // 'people'
pluralize("analysis"); // 'analyses'
pluralize("cactus"); // 'cacti'
// With count parameter
pluralize("item", 1); // 'item' (singular for count of 1)
pluralize("item", 0); // 'items' (plural for count of 0)
pluralize("item", 5); // 'items' (plural for count > 1)
// Preserves casing
pluralize("Box"); // 'Boxes'
pluralize("PERSON"); // 'PEOPLE'
Converts a plural word to its singular form using English singularization rules.
singularize("boxes"); // 'box'
singularize("babies"); // 'baby'
singularize("people"); // 'person'
singularize("analyses"); // 'analysis'
singularize("cacti"); // 'cactus'
singularize("data"); // 'datum'
// Preserves casing
singularize("Boxes"); // 'Box'
singularize("PEOPLE"); // 'PERSON'
Extracts various entities from text including emails, URLs, mentions, hashtags, phones, dates, and prices.
// Extract from mixed content
const text =
"Contact @john at john@example.com or call (555) 123-4567. Check #updates at https://example.com. Price: $99.99";
const entities = extractEntities(text);
// Returns:
// {
// emails: ['john@example.com'],
// urls: ['https://example.com'],
// mentions: ['@john'],
// hashtags: ['#updates'],
// phones: ['(555) 123-4567'],
// dates: [],
// prices: ['$99.99']
// }
// Extract from social media content
const tweet =
"Hey @alice and @bob! Check out #javascript #typescript at https://github.com/example";
const social = extractEntities(tweet);
// social.mentions: ['@alice', '@bob']
// social.hashtags: ['#javascript', '#typescript']
// social.urls: ['https://github.com/example']
// Extract contact information
const contact = "Email: support@company.com, Phone: +1-800-555-0100";
const info = extractEntities(contact);
// info.emails: ['support@company.com']
// info.phones: ['+1-800-555-0100']
// Extract dates and prices
const invoice = "Invoice Date: 2024-01-15, Due: 01/30/2024, Amount: $1,234.56";
const billing = extractEntities(invoice);
// billing.dates: ['2024-01-15', '01/30/2024']
// billing.prices: ['$1,234.56']
Intelligently splits text into sentences while properly handling abbreviations, ellipses, and decimal numbers.
// Basic sentence splitting
smartSplit("Hello world. How are you? I'm fine!");
// ['Hello world.', 'How are you?', "I'm fine!"]
// Handles abbreviations correctly
smartSplit("Dr. Smith went to the store. He bought milk.");
// ['Dr. Smith went to the store.', 'He bought milk.']
// Preserves decimal numbers
smartSplit("The price is $10.50. That's expensive!");
// ['The price is $10.50.', "That's expensive!"]
// Handles multiple abbreviations
smartSplit(
"Mr. and Mrs. Johnson live on St. Paul Ave. They moved from the U.S.A. last year."
);
// ['Mr. and Mrs. Johnson live on St. Paul Ave.', 'They moved from the U.S.A. last year.']
// Handles ellipses
smartSplit("I was thinking... Maybe we should go. What do you think?");
// ['I was thinking...', 'Maybe we should go.', 'What do you think?']
Converts an array into a grammatically correct, human-readable list with proper conjunctions and optional Oxford comma.
// Basic usage
humanizeList(["apple", "banana", "orange"]);
// 'apple, banana, and orange'
// Two items
humanizeList(["yes", "no"]);
// 'yes and no'
// With custom conjunction
humanizeList(["red", "green", "blue"], { conjunction: "or" });
// 'red, green, or blue'
// Without Oxford comma
humanizeList(["a", "b", "c"], { oxford: false });
// 'a, b and c'
// With quotes
humanizeList(["run", "jump", "swim"], { quotes: true });
// '"run", "jump", and "swim"'
// Handles mixed types and nulls
humanizeList([1, null, "text", undefined, true]);
// '1, text, and true'
// Empty arrays
humanizeList([]);
// ''
✅ Validation Functions (4 functions)
Utilities for validating string formats and content.
Validates if a string is a valid email format.
isEmail("user@example.com"); // true
isEmail("invalid.email"); // false
isEmail("test@sub.domain.com"); // true
Validates if a string is a valid URL format.
isUrl("https://example.com"); // true
isUrl("http://localhost:3000"); // true
isUrl("not a url"); // false
isUrl("ftp://files.com/file.zip"); // true
Checks if a string contains only ASCII characters (code points 0-127).
isASCII("Hello World!"); // true
isASCII("café"); // false
isASCII("👍"); // false
isASCII("abc123!@#"); // true
isASCII(""); // true
detectScript(str: string): 'latin' | 'cjk' | 'arabic' | 'cyrillic' | 'hebrew' | 'devanagari' | 'greek' | 'thai' | 'unknown'
Detects the dominant writing system (script) in a text string.
detectScript("Hello World"); // 'latin'
detectScript("你好世界"); // 'cjk'
detectScript("مرحبا بالعالم"); // 'arabic'
detectScript("Привет мир"); // 'cyrillic'
detectScript("שלום עולם"); // 'hebrew'
detectScript("नमस्ते दुनिया"); // 'devanagari'
detectScript("Γειά σου κόσμε"); // 'greek'
detectScript("สวัสดีชาวโลก"); // 'thai'
detectScript(""); // 'unknown'
Classifies text content by type (URL, email, code, JSON, markdown, HTML, question, phone, numeric, or plain text) with confidence scoring.
classifyText("https://example.com"); // { type: 'url', confidence: 1 }
classifyText("user@example.com"); // { type: 'email', confidence: 1 }
classifyText("What is TypeScript?"); // { type: 'question', confidence: 1 }
classifyText('{"key": "value"}'); // { type: 'json', confidence: 1 }
classifyText("function hello() { return 42; }"); // { type: 'code', confidence: 0.85 }
classifyText("<div>Hello</div>"); // { type: 'html', confidence: 0.9 }
classifyText("# Title\n\nText"); // { type: 'markdown', confidence: 0.7 }
classifyText("+1-555-123-4567"); // { type: 'phone', confidence: 0.95 }
classifyText("42 + 17 = 59"); // { type: 'numeric', confidence: 0.8 }
classifyText("Just plain text"); // { type: 'text', confidence: 0.7 }
🔍 String Analysis & Comparison (6 functions)
Advanced utilities for analyzing and comparing strings.
Computes a simple string diff comparison showing additions and deletions.
diff("hello world", "hello beautiful world"); // 'hello {+beautiful +}world'
diff("goodbye world", "hello world"); // '[-goodbye-]{+hello+} world'
diff("v1.0.0", "v1.1.0"); // 'v1.[-0-]{+1+}.0'
diff("debug: false", "debug: true"); // 'debug: [-fals-]{+tru+}e'
diff("user@example.com", "admin@example.com"); // '[-user-]{+admin+}@example.com'
// Form field changes
diff("John Doe", "Jane Doe"); // 'J[-ohn-]{+ane+} Doe'
// Configuration changes
diff("port: 3000", "port: 8080"); // 'port: [-300-]{+808+}0'
// File extension changes
diff("app.js", "app.ts"); // 'app.[-j-]{+t+}s'
// No changes
diff("test", "test"); // 'test'
// Complete replacement
diff("hello", "world"); // '[-hello-]{+world+}'
Uses a simple prefix/suffix algorithm optimized for readability. The output format uses:
[-text-]
for deleted text{+text+}
for added text
Calculates the Levenshtein distance (edit distance) between two strings. Optimized with space-efficient algorithm and early termination support.
levenshtein("cat", "bat"); // 1 (substitution)
levenshtein("cat", "cats"); // 1 (insertion)
levenshtein("cats", "cat"); // 1 (deletion)
levenshtein("kitten", "sitting"); // 3
levenshtein("example", "exmaple"); // 2 (transposition)
// With maxDistance for early termination
levenshtein("hello", "helicopter", 3); // Infinity (exceeds max)
levenshtein("hello", "hallo", 3); // 1 (within max)
// Unicode support
levenshtein("café", "cafe"); // 1
levenshtein("😀", "😃"); // 1
Calculates normalized Levenshtein similarity score between 0 and 1. Perfect for fuzzy matching and similarity scoring.
levenshteinNormalized("hello", "hello"); // 1 (identical)
levenshteinNormalized("cat", "bat"); // 0.667 (fairly similar)
levenshteinNormalized("hello", "world"); // 0.2 (dissimilar)
levenshteinNormalized("", "abc"); // 0 (completely different)
// Real-world typo detection
levenshteinNormalized("necessary", "neccessary"); // 0.9
levenshteinNormalized("example", "exmaple"); // 0.714
// Fuzzy matching (common threshold: 0.8)
const threshold = 0.8;
levenshteinNormalized("test", "tests") >= threshold; // true (0.8)
levenshteinNormalized("hello", "goodbye") >= threshold; // false (0.143)
Performs fuzzy string matching with a similarity score, ideal for command palettes, file finders, and search-as-you-type features.
// Basic usage
fuzzyMatch("gto", "goToLine"); // { matched: true, score: 0.546 }
fuzzyMatch("usrctrl", "userController.js"); // { matched: true, score: 0.444 }
fuzzyMatch("abc", "xyz"); // null (no match)
// Command palette style matching
fuzzyMatch("of", "openFile"); // { matched: true, score: 0.75 }
fuzzyMatch("svf", "saveFile"); // { matched: true, score: 0.619 }
// File finder matching
fuzzyMatch("index", "src/components/index.html"); // { matched: true, score: 0.262 }
fuzzyMatch("app.js", "src/app.js"); // { matched: true, score: 0.85 }
// Case sensitivity
fuzzyMatch("ABC", "abc"); // { matched: true, score: 0.95 }
fuzzyMatch("ABC", "abc", { caseSensitive: true }); // null
// Minimum score threshold
fuzzyMatch("ab", "a" + "x".repeat(50) + "b", { threshold: 0.5 }); // null (score too low)
// Acronym matching (matches at word boundaries score higher)
fuzzyMatch("uc", "UserController"); // { matched: true, score: 0.75 }
fuzzyMatch("gc", "getUserController"); // { matched: true, score: 0.75 }
Options:
caseSensitive
- Enable case-sensitive matching (default: false)threshold
- Minimum score to consider a match (default: 0)
Returns:
{ matched: true, score: number }
- When match found (score between 0-1){ matched: false, score: 0 }
- For empty querynull
- When no match found or score below threshold
Scoring algorithm prioritizes:
- Exact matches (1.0)
- Prefix matches (≥0.85)
- Consecutive character matches
- Matches at word boundaries (camelCase, snake_case, kebab-case, etc.)
- Early matches in the string
- Acronym-style matches
Highlights search terms in text by wrapping them with markers.
highlight("The quick brown fox", "quick"); // 'The <mark>quick</mark> brown fox'
highlight("Hello WORLD", "world"); // '<mark>Hello</mark> <mark>WORLD</mark>' (case-insensitive by default)
// Multiple terms
highlight("The quick brown fox", ["quick", "fox"]); // 'The <mark>quick</mark> brown <mark>fox</mark>'
// Custom wrapper
highlight("Error: Connection failed", ["error", "failed"], {
wrapper: ["**", "**"],
}); // '**Error**: Connection **failed**'
// Whole word matching
highlight("Java and JavaScript", "Java", { wholeWord: true }); // '<mark>Java</mark> and JavaScript'
// With CSS class
highlight("Hello world", "Hello", { className: "highlight" }); // '<mark class="highlight">Hello</mark> world'
// HTML escaping for security
highlight("<div>Hello</div>", "Hello", { escapeHtml: true }); // '<div><mark>Hello</mark></div>'
// Case-sensitive matching
highlight("Hello hello", "hello", { caseSensitive: true }); // 'Hello <mark>hello</mark>'
Options:
caseSensitive
- Enable case-sensitive matching (default: false)wholeWord
- Match whole words only (default: false)wrapper
- Custom wrapper tags (default: ['', ''])className
- CSS class for mark tagsescapeHtml
- Escape HTML in text before highlighting (default: false)
🌍 Unicode & International (5 functions)
Handle complex Unicode characters, emoji, and international text.
Splits a string into an array of grapheme clusters, properly handling emojis, combining characters, and complex Unicode.
graphemes("hello"); // ['h', 'e', 'l', 'l', 'o']
graphemes("👨👩👧👦🎈"); // ['👨👩👧👦', '🎈']
graphemes("café"); // ['c', 'a', 'f', 'é']
graphemes("👍🏽"); // ['👍🏽'] - emoji with skin tone
graphemes("🇺🇸"); // ['🇺🇸'] - flag emoji
graphemes("hello👋world"); // ['h', 'e', 'l', 'l', 'o', '👋', 'w', 'o', 'r', 'l', 'd']
Converts a string into an array of Unicode code points, properly handling surrogate pairs and complex characters.
codePoints("hello"); // [104, 101, 108, 108, 111]
codePoints("👍"); // [128077]
codePoints("€"); // [8364]
codePoints("Hello 👋"); // [72, 101, 108, 108, 111, 32, 128075]
codePoints("a👍b"); // [97, 128077, 98]
codePoints("👨👩👧👦"); // [128104, 8205, 128105, 8205, 128103, 8205, 128102]
🎯 Templates & Interpolation (2 functions)
Safe and flexible string template interpolation.
Interpolates variables in a template string.
template("Hello {{name}}!", { name: "World" }); // 'Hello World!'
template("{{user.name}} is {{user.age}}", {
user: { name: "Alice", age: 30 },
}); // 'Alice is 30'
template(
"Hello ${name}!",
{ name: "World" },
{
delimiters: ["${", "}"],
}
); // 'Hello World!'
Interpolates variables with HTML escaping for safe output.
templateSafe("Hello {{name}}!", {
name: '<script>alert("XSS")</script>',
}); // 'Hello <script>alert("XSS")</script>!'
⚡ Performance Utilities (1 function)
Optimize expensive string operations with caching.
Creates a memoized version of a function with LRU (Least Recently Used) cache eviction. Ideal for optimizing expensive string operations like levenshtein
, fuzzyMatch
, or diff
when processing repetitive data.
import { levenshtein, memoize } from "nano-string-utils";
// Basic usage - memoize expensive string operations
const memoizedLevenshtein = memoize(levenshtein);
// First call computes the result
memoizedLevenshtein("kitten", "sitting"); // 3 (computed)
// Subsequent calls with same arguments return cached result
memoizedLevenshtein("kitten", "sitting"); // 3 (cached - instant)
// Custom cache size (default is 100)
const limited = memoize(levenshtein, { maxSize: 50 });
// Custom key generation for complex arguments
const processUser = (user) => expensive(user);
const memoizedProcess = memoize(processUser, {
getKey: (user) => user.id, // Cache by user ID only
});
// Real-world example: Fuzzy search with caching
import { fuzzyMatch, memoize } from "nano-string-utils";
const cachedFuzzyMatch = memoize(fuzzyMatch);
const searchResults = items.map((item) => cachedFuzzyMatch(query, item.name));
// Batch processing with deduplication benefits
const words = ["hello", "world", "hello", "test", "world"];
const distances = words.map((word) => memoizedLevenshtein("example", word)); // Only computes 3 times instead of 5
Features:
- LRU cache eviction - Keeps most recently used results
- Configurable cache size - Control memory usage (default: 100 entries)
- Custom key generation - Support for complex argument types
- Type-safe - Preserves function signatures and types
- Zero dependencies - Pure JavaScript implementation
Best used with:
levenshtein()
- Expensive O(n×m) algorithmfuzzyMatch()
- Complex scoring with boundary detectiondiff()
- Character-by-character comparison- Any custom expensive string operations
Options:
maxSize
- Maximum cached results (default: 100)getKey
- Custom cache key generator function
🔧 TypeScript Utilities
Nano-string-utils provides branded types for compile-time type safety with validated strings. These types add zero runtime overhead and are fully tree-shakeable.
import { branded } from "nano-string-utils";
Type guards narrow string types to branded types:
const input: string = getUserInput();
if (branded.isValidEmail(input)) {
// input is now typed as Email
sendEmail(input);
}
if (branded.isValidUrl(input)) {
// input is now typed as URL
fetch(input);
}
if (branded.isSlug(input)) {
// input is now typed as Slug
useAsRoute(input);
}
Safely create branded types with validation:
// Returns Email | null
const email = branded.toEmail("user@example.com");
if (email) {
sendEmail(email); // email is typed as Email
}
// Returns URL | null
const url = branded.toUrl("https://example.com");
if (url) {
fetch(url); // url is typed as URL
}
// Always returns Slug (transforms input)
const slug = branded.toSlug("Hello World!"); // 'hello-world' as Slug
createRoute(slug);
// Smart slug handling
const slug2 = branded.ensureSlug("already-a-slug"); // returns as-is if valid
const slug3 = branded.ensureSlug("Not A Slug!"); // transforms to 'not-a-slug'
Assert types with runtime validation:
const input: string = getUserInput();
// Throws BrandedTypeError if invalid
branded.assertEmail(input);
// input is now typed as Email
sendEmail(input);
// Custom error messages
branded.assertUrl(input, "Invalid webhook URL");
// All assertion functions available
branded.assertEmail(str);
branded.assertUrl(str);
branded.assertSlug(str);
For trusted inputs where validation isn't needed:
// Use only when you're certain the input is valid
const trustedEmail = branded.unsafeEmail("admin@system.local");
const trustedUrl = branded.unsafeUrl("https://internal.api");
const trustedSlug = branded.unsafeSlug("already-valid-slug");
Email
- Validated email addressesURL
- Validated URLs (http/https/ftp/ftps)Slug
- URL-safe slugs (lowercase, hyphenated)Brand<T, K>
- Generic branding utility for custom types
- Zero runtime overhead - Types are erased at compilation
- Type safety - Prevent passing unvalidated strings to functions
- IntelliSense support - Full autocomplete and type hints
- Tree-shakeable - Only imported if used
- Composable - Works with existing string functions
// Example: Type-safe API
function sendNewsletter(email: branded.Email) {
// Can only be called with validated emails
api.send(email);
}
// Won't compile without validation
const userInput = "maybe@email.com";
// sendNewsletter(userInput); // ❌ Type error!
// Must validate first
const validated = branded.toEmail(userInput);
if (validated) {
sendNewsletter(validated); // ✅ Type safe!
}
Case conversion functions now provide precise type inference for literal strings at compile time. This feature enhances IDE support with exact type transformations while maintaining full backward compatibility.
import { camelCase, kebabCase, snakeCase } from "nano-string-utils";
// Literal strings get exact transformed types
const endpoint = kebabCase("getUserProfile");
// Type: "get-user-profile" (not just string!)
const column = snakeCase("firstName");
// Type: "first_name"
const methodName = camelCase("fetch-user-data");
// Type: "fetchUserData"
// Runtime strings still return regular string type
const userInput: string = getUserInput();
const result = camelCase(userInput);
// Type: string (backward compatible)
camelCase("hello-world"); // Type: "helloWorld"
kebabCase("helloWorld"); // Type: "hello-world"
snakeCase("HelloWorld"); // Type: "hello_world"
pascalCase("hello-world"); // Type: "HelloWorld"
constantCase("helloWorld"); // Type: "HELLO_WORLD"
dotCase("HelloWorld"); // Type: "hello.world"
pathCase("helloWorld"); // Type: "hello/world"
sentenceCase("hello-world"); // Type: "Hello world"
titleCase("hello-world"); // Type: "Hello World"
Transform configuration keys between naming conventions:
const config = {
"api-base-url": "https://api.example.com",
"max-retries": 3,
} as const;
// Convert keys to camelCase at type level
type ConfigCamelCase = {
[K in keyof typeof config as CamelCase<K>]: (typeof config)[K];
};
// Type: { apiBaseUrl: string; maxRetries: number; }
Create type-safe method names from API routes:
type ApiRoutes = "user-profile" | "user-settings" | "admin-panel";
type MethodNames = {
[K in ApiRoutes as `fetch${PascalCase<K>}`]: () => Promise<void>;
};
// Creates: fetchUserProfile(), fetchUserSettings(), fetchAdminPanel()
Benefits:
- ✅ Zero runtime cost - All transformations happen at compile time
- ✅ Better IDE support - Autocomplete shows exact transformed strings
- ✅ Type safety - Catch typos and incorrect transformations during development
- ✅ Backward compatible - Runtime strings work exactly as before
All functions in nano-string-utils handle null and undefined inputs gracefully:
// No more runtime errors!
slugify(null); // Returns: null
slugify(undefined); // Returns: undefined
slugify(""); // Returns: ""
// Consistent behavior across all functions
isEmail(null); // Returns: false (validation functions)
words(null); // Returns: [] (array functions)
wordCount(null); // Returns: 0 (counting functions)
// Safe to use without defensive checks
const userInput = getUserInput(); // might be null/undefined
const slug = slugify(userInput); // Won't throw!
This means:
- ✅ No TypeErrors - Functions never throw on null/undefined
- ✅ Predictable behavior - Consistent handling across all utilities
- ✅ Cleaner code - No need for defensive checks before calling functions
- ✅ Zero performance cost - Minimal overhead from null checks
Each utility is optimized to be as small as possible:
Function | Size (minified) |
---|---|
slugify | ~200 bytes |
camelCase | ~250 bytes |
snakeCase | ~220 bytes |
kebabCase | ~200 bytes |
pascalCase | ~180 bytes |
constantCase | ~230 bytes |
dotCase | ~210 bytes |
pathCase | ~210 bytes |
sentenceCase | ~280 bytes |
titleCase | ~320 bytes |
capitalize | ~100 bytes |
truncate | ~150 bytes |
stripHtml | ~120 bytes |
sanitize | ~1.2 KB |
redact | ~1.3 KB |
escapeHtml | ~180 bytes |
excerpt | ~220 bytes |
randomString | ~200 bytes |
hashString | ~150 bytes |
reverse | ~80 bytes |
deburr | ~200 bytes |
isEmail | ~180 bytes |
isUrl | ~200 bytes |
isASCII | ~100 bytes |
toASCII | ~450 bytes |
wordCount | ~100 bytes |
normalizeWhitespace | ~280 bytes |
removeNonPrintable | ~200 bytes |
template | ~350 bytes |
templateSafe | ~400 bytes |
pad | ~180 bytes |
padStart | ~150 bytes |
padEnd | ~150 bytes |
graphemes | ~250 bytes |
codePoints | ~120 bytes |
highlight | ~320 bytes |
diff | ~280 bytes |
levenshtein | ~380 bytes |
levenshteinNormalized | ~100 bytes |
fuzzyMatch | ~500 bytes |
pluralize | ~350 bytes |
singularize | ~320 bytes |
smartSplit | ~1.1KB |
humanizeList | ~850 bytes |
memoize | ~400 bytes |
extractEntities | ~1.1KB |
detectScript | ~1.1KB |
classifyText | ~2.3KB |
Total package size: < 8.5KB minified + gzipped
- Node.js >= 18
- TypeScript >= 5.0 (for TypeScript users)
# Clone the repository
git clone https://github.com/Zheruel/nano-string-utils.git
cd nano-string-utils
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build the library
npm run build
# Type check
npm run typecheck
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT © [Zheruel]
In a world of bloated dependencies, nano-string-utils
stands out by providing exactly what you need and nothing more:
- Security First: Zero dependencies means zero supply chain vulnerabilities
- Performance: Optimized for both speed and size
- 2.1-2.6x faster than lodash for truncate
- 25x faster than lodash for template
- 2.4x faster than lodash for capitalize
- Developer Experience: Full TypeScript support with comprehensive JSDoc comments
- Production Ready: 100% test coverage with extensive edge case handling
- Modern: Built for ES2022+ with full ESM support and CommonJS compatibility
We continuously benchmark nano-string-utils against popular alternatives (lodash and es-toolkit) to ensure optimal performance and bundle size.
# Run all benchmarks
npm run bench:all
# Run performance benchmarks only
npm run bench:perf
# Run bundle size analysis only
npm run bench:size
Function | nano-string-utils | lodash | es-toolkit | Winner |
---|---|---|---|---|
camelCase | 193B | 3.4KB | 269B | nano ✅ |
capitalize | 90B | 1.7KB | 99B | nano ✅ |
kebabCase | 161B | 2.8KB | 193B | nano ✅ |
truncate | 125B | 2.9KB | - | nano ✅ |
- 🏆 Smallest bundle sizes: nano-string-utils wins 10 out of 11 tested functions
- ⚡ Superior performance: 2-25x faster than lodash for key operations
- 📊 Detailed benchmarks: See benchmark-results.md for full comparison
- ⚡ Optimized performance:
- 2.25x faster than lodash for short string truncation
- Case conversions improved by 30-40% in latest optimizations
- Truncate function improved by 97.6% (42x faster!)
- 🌳 Superior tree-shaking: Each function is independently importable with minimal overhead
Library | Bundle Size | Dependencies | Tree-shakeable | TypeScript |
---|---|---|---|---|
nano-string-utils | < 7.5KB | 0 | ✅ | ✅ |
lodash | ~70KB | 0 | ✅ | |
underscore.string | ~20KB | 0 | ❌ | ❌ |
voca | ~30KB | 0 | ❌ | ✅ |