visulima string
A robust string manipulation library providing utilities for common string operations with support for multiple languages.
Daniel Bannert's open source work is supported by the community on GitHub Sponsors
Features
Case Conversion
- Multiple Case Styles:
camelCase
: Convert to camelCase style
PascalCase
: Convert to PascalCase style
snake_case
: Convert to snake_case style
kebab-case
: Convert to kebab-case style
CONSTANT_CASE
: Convert to CONSTANT_CASE style
dot.case
: Convert to dot.case style
path/case
: Convert to path/case style
Sentence case
: Convert to Sentence case style
Title Case
: Convert to Title Case with smart minor word handling
String Manipulation
- Smart String Splitting:
- Split by case transitions (camelCase → ["camel", "Case"])
- Split by script boundaries (日本語Text → ["日本語", "Text"])
- Split by separators (foo-bar → ["foo", "bar"])
- Preserve known acronyms (XMLHttpRequest → ["XML", "Http", "Request"])
- Text Indentation:
outdent
: Remove leading indentation while preserving relative indentation
- Handles template literals and string inputs
- Normalizes newlines across platforms
- Configurable trimming behavior
- String Width Calculation:
getStringWidth
: Calculate visual width of strings with Unicode support
getStringTruncatedWidth
: Width calculation with smart truncation
- Handles CJK characters, emojis, ANSI codes, and more
- Configurable character width settings
- Support for zero-width and combining characters
- Customizable truncation with ellipsis
Multi-Script Support
- CJK Scripts:
- Japanese (Hiragana, Katakana, Kanji)
- Korean (Hangul)
- Chinese (Han characters)
- European Scripts:
- Cyrillic (Russian, Ukrainian, etc.)
- Greek
- Latin (with extended characters)
- RTL Scripts:
- Indic Scripts:
- Devanagari
- Bengali
- Tamil
- And more...
- Southeast Asian:
Enhanced Type Safety
- Native String Type Extensions:
- Type-safe string operations
- Compile-time type checking
- Improved IDE support
- Generic Type Parameters:
- Flexible type constraints
- Template literal type support
- Conditional type inference
Performance Features
- Optimized Processing:
- Precompiled regex patterns
- Efficient string manipulation
- Caching Mechanisms:
- Smart caching for repeated operations
- WeakMap-based caching for template literals
- Configurable cache options
- Fast paths for common cases
- Memory Efficiency:
- Minimal string allocations
- Efficient string splitting
- Optimized concatenation
Developer Experience
- Comprehensive API:
- Consistent method signatures
- Chainable operations
- Flexible configuration options
- Robust Error Handling:
- Graceful handling of edge cases
- Clear error messages
- Type-safe error prevention
- Full TypeScript Support:
- Complete type definitions
- IntelliSense support
- Type inference
Install
npm install @visulima/string
yarn add @visulima/string
pnpm add @visulima/string
Usage
Outdent (Remove Indentation)
The outdent
function removes leading indentation from multi-line strings while preserving the relative indentation structure.
import { outdent } from '@visulima/string';
const text = outdent`
This text will have its indentation removed
while preserving relative indentation.
This line will still be indented relative to the others.
`;
const result = outdent.string('
Hello
World
');
const customOutdent = outdent({
trimLeadingNewline: false,
trimTrailingNewline: false,
newline: '\r\n',
cache: true
});
const name = 'World';
const greeting = outdent`
Hello ${name}!
Welcome to outdent.
`;
Performance Optimization with Caching
The outdent
function supports caching to improve performance when the same template is used multiple times:
const dedent = outdent();
const noCacheDedent = outdent({ cache: false });
const customCache = new WeakMap();
const customCacheDedent = outdent({ cacheStore: customCache });
Word Wrapping
The wordWrap
function provides flexible text wrapping with support for ANSI color codes and Unicode.
import { wordWrap, WrapMode } from "@visulima/string";
const wrapped = wordWrap("This is a long text that will be wrapped to fit within the specified width limit.");
const narrowWrapped = wordWrap("This text will be wrapped to fit within a 40-character width.", { width: 40 });
const preserveWords = wordWrap("Long words will stay intact but may exceed the width limit.", {
width: 20,
wrapMode: WrapMode.PRESERVE_WORDS,
});
const breakAtCharacters = wordWrap("Words will be broken at character boundaries to fit width.", {
width: 20,
wrapMode: WrapMode.BREAK_AT_CHARACTERS,
});
const strictWidth = wordWrap("Text will be broken exactly at the width limit.", {
width: 20,
wrapMode: WrapMode.STRICT_WIDTH,
});
const coloredText = "\u001B[31mThis red text\u001B[0m will be wrapped while preserving the color codes.";
const wrappedColored = wordWrap(coloredText, { width: 20 });
const customWrapped = wordWrap("Text with\u200Bzero-width characters and\u200Btrailing spaces.", {
width: 30,
trim: false,
removeZeroWidthCharacters: false,
});
String Replacement with Ignore Ranges
The replaceString
function provides advanced string replacement capabilities, allowing multiple search/replace operations (using strings or RegExps) while respecting specified index ranges that should be ignored. It also handles match precedence correctly (earlier start index wins, then longer match wins if starts are identical) and ensures only the highest-priority, non-overlapping, non-ignored match is applied in any given segment.
Note: Use this function when you need fine-grained control over multiple replacements, especially when needing to ignore specific index ranges or handle complex overlapping matches with defined precedence rules. For simple, non-overlapping replacements without ignore ranges, native String.prototype.replaceAll
might be sufficient.
import replaceString from "@visulima/string/replace-string";
import type { IntervalArray, OptionReplaceArray } from "@visulima/string";
const source1 = "Replace AB and CD";
const searches1: OptionReplaceArray = [
["AB", "ab"],
["CD", "cd"],
];
const result1 = replaceString(source1, searches1, []);
const source2 = "Replace AB and ignore CD and replace XY";
const searches2: OptionReplaceArray = [
["AB", "ab"],
["CD", "cd"],
["XY", "xy"],
];
const ignoreRanges2: IntervalArray = [[19, 20]];
const result2 = replaceString(source2, searches2, ignoreRanges2);
const source3 = "abcde";
const searches3: OptionReplaceArray = [
["abc", "123"],
["abcde", "54321"],
];
const result3 = replaceString(source3, searches3, []);
const source4 = "ababab";
const searches4: OptionReplaceArray = [
["aba", "X"],
["bab", "Y"],
];
const result4 = replaceString(source4, searches4, []);
const source5 = "abc";
const searches5: OptionReplaceArray = [[/(?=.)/g, "^"]];
const result5 = replaceString(source5, searches5, []);
const source6 = "abc";
const searches6: OptionReplaceArray = [[/$/g, "$"]];
const result6 = replaceString(source6, searches6, []);
const source7 = "Firstname Lastname";
const searches7: OptionReplaceArray = [[/(\w+)\s+(\w+)/, "$2, $1 ($& - Group 1: $1)"]];
const result7 = replaceString(source7, searches7, []);
String Splitting
The splitByCase
function is a powerful utility that splits strings based on various patterns:
import { splitByCase } from "@visulima/string";
splitByCase("camelCase");
splitByCase("PascalCase");
splitByCase("snake_case");
splitByCase("kebab-case");
splitByCase("XMLHttpRequest");
splitByCase("iOS8");
splitByCase("IPv6Address");
splitByCase("ひらがなカタカナABC", { locale: "ja" });
splitByCase("한글Text", { locale: "ko" });
splitByCase("中文Text", { locale: "zh" });
splitByCase("русскийText", { locale: "ru" });
splitByCase("ελληνικάText", { locale: "el" });
splitByCase("MyXMLParser", {
knownAcronyms: ["XML"],
normalize: true,
locale: "en",
});
splitByCase("🎉HappyBirthday🎂", {
handleEmoji: true,
});
Case Conversion Functions
camelCase
Converts a string to camelCase.
camelCase("foo bar");
camelCase("foo-bar");
camelCase("foo_bar");
camelCase("XMLHttpRequest");
camelCase("AJAXRequest");
camelCase("QueryXML123String");
pascalCase
Converts a string to PascalCase.
pascalCase("foo bar");
pascalCase("foo-bar");
pascalCase("foo_bar");
pascalCase("XMLHttpRequest");
pascalCase("AJAXRequest");
pascalCase("QueryXML123String");
snakeCase
Converts a string to snake_case.
snakeCase("fooBar");
snakeCase("foo bar");
snakeCase("foo-bar");
snakeCase("XMLHttpRequest");
snakeCase("AJAXRequest");
snakeCase("QueryXML123String");
kebabCase
Converts a string to kebab-case.
kebabCase("fooBar");
kebabCase("foo bar");
kebabCase("foo_bar");
kebabCase("XMLHttpRequest");
kebabCase("AJAXRequest");
kebabCase("QueryXML123String");
titleCase
Converts a string to Title Case, with smart handling of minor words.
titleCase("this-IS-aTitle");
titleCase("XMLHttpRequest");
titleCase("AJAXRequest");
titleCase("QueryXML123String");
pathCase
Converts a string to path/case.
pathCase("foo bar");
pathCase("foo-bar");
pathCase("foo_bar");
pathCase("XMLHttpRequest");
pathCase("AJAXRequest");
pathCase("QueryXML123String");
dotCase
Converts a string to dot.case.
dotCase("foo bar");
dotCase("foo-bar");
dotCase("foo_bar");
dotCase("XMLHttpRequest");
dotCase("AJAXRequest");
dotCase("QueryXML123String");
constantCase
Converts a string to CONSTANT_CASE.
constantCase("foo bar");
constantCase("foo-bar");
constantCase("foo_bar");
constantCase("XMLHttpRequest");
constantCase("AJAXRequest");
constantCase("QueryXML123String");
sentenceCase
Converts a string to Sentence case.
sentenceCase("foo bar");
sentenceCase("foo-bar");
sentenceCase("foo_bar");
sentenceCase("XMLHttpRequest");
sentenceCase("AJAXRequest");
sentenceCase("QueryXML123String");
String Width Calculation
The package provides two functions for calculating string widths: getStringWidth
for basic width calculation and getStringTruncatedWidth
for width calculation with truncation support.
Basic Width Calculation
The getStringWidth
function calculates the visual width of strings, taking into account various Unicode characters, emojis, ANSI escape codes, and more:
import { getStringWidth } from "@visulima/string";
getStringWidth("hello");
getStringWidth("👋 hello");
getStringWidth("あいう");
getStringWidth("hello", { regularWidth: 2 });
getStringWidth("あいう", { ambiguousIsNarrow: true });
getStringWidth("\u001B[31mRed\u001B[39m");
getStringWidth("\u001B[31mRed\u001B[39m", { countAnsiEscapeCodes: true });
getStringWidth("한글");
getStringWidth("你好");
getStringWidth("👨👩👧👦");
Configuration Options
interface StringWidthOptions {
ambiguousIsNarrow?: boolean;
ansiWidth?: number;
controlWidth?: number;
countAnsiEscapeCodes?: boolean;
emojiWidth?: number;
fullWidth?: number;
regularWidth?: number;
tabWidth?: number;
wideWidth?: number;
}
Width Calculation with Truncation
The getStringTruncatedWidth
function extends the basic width calculation with truncation support:
import { getStringTruncatedWidth } from "@visulima/string";
getStringTruncatedWidth("hello world", {
limit: 8,
ellipsis: "...",
});
getStringTruncatedWidth("あいうえお", {
limit: 6,
ellipsis: "...",
fullWidth: 2,
});
getStringTruncatedWidth("\u001B[31mRed Text\u001B[39m", {
limit: 5,
ellipsis: "...",
countAnsiEscapeCodes: true,
});
getStringTruncatedWidth("👨👩👧👦 Family", {
limit: 7,
ellipsis: "...",
});
String Truncation
The truncate
function provides a convenient way to truncate strings with support for different positions, Unicode characters, ANSI escape codes, and more.
import { truncate } from "@visulima/string";
truncate("unicorn", 4);
truncate("unicorn", 4, { position: "end" });
truncate("unicorn", 5, { position: "start" });
truncate("unicorn", 5, { position: "middle" });
truncate("unicorns", 5, { ellipsis: "." });
truncate("unicorns", 5, { ellipsis: " ." });
truncate("dragons are awesome", 15, { position: "end", preferTruncationOnSpace: true });
truncate("unicorns rainbow dragons", 20, { position: "middle", preferTruncationOnSpace: true });
truncate("\u001B[31municorn\u001B[39m", 4);
truncate("안녕하세요", 3, { width: { fullWidth: 2 } });
Truncation Options
interface TruncateOptions {
ellipsis?: string;
ellipsisWidth?: number;
position?: "end" | "middle" | "start";
preferTruncationOnSpace?: boolean;
width?: Omit<StringTruncatedWidthOptions, "ellipsis" | "ellipsisWidth" | "limit">;
}
interface StringTruncatedWidthOptions extends StringWidthOptions {
ellipsis?: string;
ellipsisWidth?: number;
limit?: number;
}
interface StringTruncatedWidthResult {
width: number;
truncated: boolean;
ellipsed: boolean;
index: number;
}
Common Options
All case conversion functions accept these common options:
interface CaseOptions {
cache?: boolean;
cacheMaxSize?: number;
cacheStore?: Map<string, string>;
knownAcronyms?: ReadonlyArray<string>;
locale?: string;
}
String Splitting
The splitByCase
function accepts these configuration options:
interface SplitOptions {
locale?: string;
knownAcronyms?: ReadonlyArray<string>;
handleAnsi?: boolean;
handleEmoji?: boolean;
normalize?: boolean;
separators?: ReadonlyArray<string> | RegExp;
stripAnsi?: boolean;
stripEmoji?: boolean;
}
Supported Scripts
The library provides comprehensive support for various scripts and writing systems:
- Latin: Standard ASCII and extended Latin characters
- CJK:
- Japanese (Hiragana, Katakana, Kanji)
- Korean (Hangul)
- Chinese (Han characters)
- Cyrillic: Russian, Ukrainian, Bulgarian, etc.
- Greek: Modern Greek script
- RTL Scripts: Arabic, Hebrew
- Indic Scripts: Devanagari, Bengali, Tamil, etc.
- Southeast Asian: Thai, Lao, Khmer
Performance Optimization
The library includes several optimizations:
- Precompiled regex patterns for script detection
- Fast paths for single-script strings
- Efficient handling of case transitions
- Smart caching of character type checks
Error Handling
The library handles various edge cases gracefully:
splitByCase("");
splitByCase(null);
splitByCase(undefined);
splitByCase("A");
splitByCase("URL", { knownAcronyms: ["URL"] });
Native String Types
The library provides enhanced TypeScript type definitions for native string methods. These types provide better type inference and compile-time checks.
Configuration
Configure your tsconfig.json
file to include the types:
{
"compilerOptions": {
"types": ["@visulima/string/native-string-types"]
}
}
Alternatively, you can add a triple-slash reference in your TypeScript files:
Usage Examples
const str = "Hello, World!";
str.charAt<typeof str, 0>();
str.charAt<typeof str, 1>();
str.concat<typeof str, "Hi">();
str.endsWith<typeof str, "World!">();
str.endsWith<typeof str, "Hello">();
str.includes<typeof str, "World", 0>();
str.includes<typeof str, "World", 7>();
type Length = (typeof str)["length"];
str.padStart<typeof str, 15, "_">();
str.padEnd<typeof str, 15, "_">();
str.replace<typeof str, "World", "TypeScript">();
str.replaceAll<typeof str, "l", "L">();
str.slice<typeof str, 0, 5>();
str.split<typeof str, ", ">();
str.startsWith<typeof str, "Hello">();
str.toLowerCase<typeof str>();
str.toUpperCase<typeof str>();
const paddedStr = " hello ";
paddedStr.trim<typeof paddedStr>();
paddedStr.trimStart<typeof paddedStr>();
paddedStr.trimEnd<typeof paddedStr>();
These enhanced types provide several benefits:
const result = "Hello, World!".toLowerCase<string>().replace<string, "hello", "hi">().split<string, " ">().join("-");
Testing Utilities
The package includes specialized utilities for testing ANSI colored strings, making it easier to write tests for terminal output and colored text.
ANSI String Formatting and Comparison
The formatAnsiString
function helps format ANSI strings for test output, providing multiple representations:
import { formatAnsiString } from "@visulima/string/test/utils";
import { red } from "@visulima/colorize";
const coloredText = red("Error message");
const formatted = formatAnsiString(coloredText);
Comparing ANSI Strings
The compareAnsiStrings
function provides detailed comparison between two ANSI strings:
import { compareAnsiStrings } from "@visulima/string/test/utils";
import { red, blue } from "@visulima/colorize";
const string1 = red("Error");
const string2 = blue("Error");
const result = compareAnsiStrings(string1, string2);
Vitest Integration
The package includes a custom matcher for Vitest that makes testing ANSI strings straightforward:
import { expect, describe, it } from "vitest";
import { toEqualAnsi } from "@visulima/string/test/vitest";
import { red, green } from "@visulima/colorize";
expect.extend({ toEqualAnsi });
describe("colored output tests", () => {
it("should display the correct error message", () => {
const actual = getErrorMessage();
const expected = red("Error: ") + green("File not found");
expect(actual).toEqualAnsi(expected);
});
});
The custom matcher provides detailed error messages when tests fail, showing:
- The visible content of both strings
- The ANSI escape codes in both strings
- Whether the visible content matches but the colors differ
- Length information for both strings
replaceString(source, searches, ignoreRanges?)
Replaces occurrences of search patterns within a string, respecting ignored ranges.
This function is designed to handle overlapping matches and ignore ranges correctly.
It prioritizes matches that start earlier and, for matches starting at the same position,
prefers longer matches. Replacements within ignored ranges are skipped.
Parameters:
source
: The input string.
searches
: An array of search pairs. Each pair can be:
[string | RegExp, string]
: A literal string or regex to search for, and its replacement string.
Regex flags like g
(global) are respected.
ignoreRanges?
: Optional. An array of [start, end]
index pairs (inclusive) specifying ranges within the
source
string that should be ignored during replacement.
Returns:
string
: The string with replacements applied, respecting ignore ranges.
Usage:
import { replaceString } from "@visulima/string";
const text = "abc abc abc";
const searches = [
[/a/g, "X"],
["abc", "YYY"],
];
const ignoreRanges = [[4, 6]];
const result = replaceString(text, searches, ignoreRanges);
transliterate(source, options?)
Performs transliteration of characters in a string based on an extensive character map and provided options. This function is useful for converting characters from one script to another (e.g., Latin with diacritics to basic Latin, Cyrillic to Latin) or for custom character replacements.
Parameters:
source: string
: The input string to transliterate.
options?
: Optional OptionsTransliterate
object:
fixChineseSpacing?: boolean
: If true
, adds a space between transliterated Chinese Pinyin syllables. (Default: true
).
ignore?: string[]
: An array of strings or characters to ignore during transliteration. These segments will be preserved in their original form. (Default: []
).
replaceBefore?: Array<[string | RegExp, string]> | Record<string, string>
: Custom replacement rules to apply before the main character map transliteration. (Default: []
).
replaceAfter?: Array<[string | RegExp, string]> | Record<string, string>
: Custom replacement rules to apply after the main character map transliteration. (Default: []
).
trim?: boolean
: If true
, trims whitespace from the beginning and end of the result. (Default: false
).
unknown?: string
: The character or string to use for characters that are not found in the character map and are not covered by other rules. (Default: ""
- removes unknown characters).
Returns:
string
: The transliterated string.
Usage:
import { transliterate } from "@visulima/string";
transliterate("Crème brûlée");
transliterate("你好世界");
transliterate("你好世界", { fixChineseSpacing: false });
transliterate("Don't change THIS, but change that.", { ignore: ["THIS"] });
transliterate("Replace C++ before map.", { replaceBefore: { "C++": "cpp" } });
transliterate("café", { replaceAfter: { e: "E" } });
transliterate("a🚀b", { unknown: "[?]" });
slugify(input, options?)
Converts a string into a URL-friendly slug.
It transliterates non-ASCII characters using the transliterate
function (if enabled), optionally converts case, removes disallowed characters (replacing with separator), and collapses separators.
Parameters:
input
: The string to convert.
options?
: Optional SlugifyOptions
object:
allowedChars?: string
: Characters allowed in the slug. Others are replaced by separator
. (Default: "a-zA-Z0-9-_.~"
)
fixChineseSpacing?: boolean
: Passed to transliterate
. Determines if a space is added between transliterated Chinese characters (default: true
).
ignore?: string[]
: Passed to transliterate
. Characters/strings to ignore during the initial transliteration phase (default: []
).
lowercase?: boolean
: Convert to lowercase. (Default: true
). Cannot be true if uppercase
is true.
replaceAfter?: OptionReplaceCombined
: Passed to transliterate
. Search/replace pairs to apply after the character map transliteration but before slugification logic (default: []
).
replaceBefore?: OptionReplaceCombined
: Passed to transliterate
. Search/replace pairs to apply before any transliteration (default: []
).
separator?: string
: Custom separator. (Default: "-"
).
transliterate?: boolean
: Whether to perform the initial transliteration of non-ASCII characters. If false
, only case conversion and character filtering/replacement are performed on the input string. (Default: true
).
unknown?: string
: Passed to transliterate
. Character to use for unknown characters during transliteration (default: ""
).
uppercase?: boolean
: Convert to uppercase. (Default: false
). Cannot be true if lowercase
is true.
Returns:
string
: The generated slug.
Usage:
import { slugify } from "@visulima/string";
slugify("你好 World!");
slugify("你好World!", { fixChineseSpacing: false });
slugify("Crème Brûlée");
slugify("foo & bar * baz");
slugify("FOO BAR", { lowercase: false, uppercase: true });
slugify("foo bar baz", { separator: "_", allowedChars: "a-z_" });
slugify("Keep C++", { replaceBefore: { "C++": "cpp" } });
slugify("Keep !@#$", { allowedChars: "a-z!@$" });
Text Alignment
The alignText
function aligns text (including multi-line strings and strings with ANSI escape codes) to the left, center, or right. It can handle both single strings (which can be split into lines based on the split
option) and arrays of strings.
import { alignText } from "@visulima/string";
const text1 = "First line\nSecond, much longer line";
const alignedText1 = alignText(text1, { align: "right" });
const textArray = ["Short", "Medium length", "A very very long line indeed"];
const alignedArray = alignText(textArray, { align: "center" });
const emojiText = "Line1😊*WiderLine😊😊";
const alignedEmojiText = alignText(emojiText, {
align: "center",
split: "*",
pad: "-",
stringWidthOptions: { emojiWidth: 2 }
});
Alignment Options
The alignText
function accepts an options
object of type AlignTextOptions
with the following properties:
align?: "center" | "left" | "right"
: Specifies the alignment direction. Defaults to "center"
. Note: left
alignment primarily ensures line splitting if text
is a single string; it doesn't typically add padding on the left unless the string was not pre-split.
pad?: string
: The character or string to use for padding. Defaults to " "
.
split?: string
: The character or string used to split the input text
into multiple lines if it's provided as a single string. Defaults to "\n"
.
stringWidthOptions?: StringWidthOptions
: Options passed to an internal string width calculation function (similar to getStringWidth
) for determining the visual width of each line. This is important for accurately handling ANSI escape codes, CJK characters, emojis, etc. Refer to the getStringWidth
documentation for details on StringWidthOptions
.
Related
Supported Node.js Versions
Libraries in this ecosystem make the best effort to track Node.js' release schedule.
Here's a post on why we think this is important.
Contributing
If you would like to help, take a look at the list of issues and check our Contributing guidelines.
Note: please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
Credits
License
The visulima string is open-sourced software licensed under the MIT