trim-repeated
trim-repeated is a tiny JavaScript string utility that reduces consecutive copies of an exact substring to one copy. Give it an input such as `foo---bar` and the target `-`, and it returns `foo-bar`; targets may also contain several characters, punctuation, backslashes, or emoji. Version 2 exposes one default function as an ES module and supports Node.js 12 or newer. It is literal text cleanup, not whitespace normalization, fuzzy matching, or a general parser.
A correct, readable one-function package for collapsing an exact repeated delimiter, particularly when the delimiter contains regex syntax. Do not add it for whitespace-only cleanup, synchronous CommonJS, or strict TypeScript without planning for its missing declarations and quiet maintenance history.
Use it if
- You need to collapse consecutive copies of one known delimiter while leaving every other character untouched
- Your target can contain regular-expression characters and you want the package to escape them correctly
- You need multi-character targets such as `@#`, which are more awkward to express with a quick character-class regex
- You already ship ES modules and value a named, tested operation over repeating a custom regex across the codebase
- You only need to normalize whitespace: the README points to condense-whitespace, which also trims leading and trailing whitespace and communicates that narrower job more clearly
- You run CommonJS with synchronous require(): version 2 declares `type: module`, exports only `./index.js`, and supplies no CommonJS entry point
- You need TypeScript declarations: version 2 publishes no `types` field and the repository contains no index.d.ts, so strict projects must add their own declaration or accept an untyped import
- You need overlapping or approximate matching: the tests explicitly preserve `foo#@#@bar` when the target is `@#`, because only adjacent complete target copies are collapsed
- You are avoiding dormant dependencies: 2.0.0 was published in April 2021 and the repository's last push was in July 2022, even though the repository is not archived and currently has no open issues or pull requests
Setup reality
Installation is one command, `npm install trim-repeated`, and there are no peer dependencies, credentials, native extensions, or configuration files. The important compatibility detail is packaging: version 2 is ESM-only, requires Node.js 12 or newer, and exposes a default export. An ES module can use `import trimRepeated from 'trim-repeated'`; older CommonJS code cannot use a synchronous `require('trim-repeated')` and must either switch module formats, use dynamic `import()`, or stay on an older line with its own maintenance tradeoffs. The package has one runtime dependency, `escape-string-regexp`, because it builds a global regular expression for the supplied target. That escaping means targets such as `.`, `\`, `)`, or `@#` are treated literally. Both arguments must be strings or the function throws `TypeError: Expected a string`; it does not coerce numbers, buffers, null, or arrays. There are no published TypeScript declarations in version 2, so TypeScript users may see a missing-declaration error under strict settings and need a local `declare module` shim. The function creates a new regular expression on every call, so code processing a very hot loop with the same delimiter may be better served by a precompiled local regex. It only collapses adjacent, exact, case-sensitive copies. It does not trim the target from either end, normalize Unicode, remove isolated targets, or understand delimiter structure such as the `//` in a URL. Validate untrusted targets before calling it, especially an empty string, because the public contract merely says the target is a required string and does not promise useful empty-target behavior.
Patterns
Collapse repeated hyphenscollapse-hyphens
import trimRepeated from 'trim-repeated';
const value = trimRepeated('foo--bar---baz', '-');
// 'foo-bar-baz'Every run of two or more adjacent hyphens becomes one; isolated hyphens remain unchanged.
Collapse a repeated multi-character delimitercollapse-multi-character-target
import trimRepeated from 'trim-repeated';
const value = trimRepeated('foo@#@#@#baz', '@#');
// 'foo@#baz'The target is a complete substring, not a character class, so each adjacent `@#` pair must line up exactly.
Treat regular-expression syntax literallycollapse-regex-symbol
const value = trimRepeated('part....next', '.');
// 'part.next'The dependency escape-string-regexp escapes the target before the package constructs its global regular expression.
Collapse repeated backslashescollapse-backslashes
const value = trimRepeated(String.raw`foo\\bar`, '\');
// 'foo\bar'JavaScript string escaping still applies; String.raw makes the input easier to read when backslashes are the data.
Collapse an exact repeated emojicollapse-emoji
const value = trimRepeated('ready🐴🐴🐴done', '🐴');
// 'ready🐴done'The repository test suite covers this exact kind of emoji target, though the package performs no broader Unicode normalization.
Leave nonconsecutive targets alonepreserve-separated-targets
const value = trimRepeated('one--two-x--three', '--');
// 'one--two-x--three'A target must appear twice back-to-back before anything changes; a single occurrence is preserved.
Clean every repeated run in one callcollapse-throughout-string
const value = trimRepeated('a:::b:::::c:::d', ':');
// 'a:b:c:d'The implementation uses a global regular expression, so it replaces every qualifying run rather than only the first.
Validate an externally supplied targetvalidate-external-input
function collapse(input, target) {
if (typeof input !== 'string' || typeof target !== 'string' || target.length === 0) {
throw new TypeError('input and non-empty target must be strings');
}
return trimRepeated(input, target);
}The package checks only that both values are strings; add the non-empty rule when callers can choose the target.
Load version 2 from CommonJSuse-from-commonjs
async function clean(value) {
const { default: trimRepeated } = await import('trim-repeated');
return trimRepeated(value, '_');
}
clean('a___b').then(console.log);Version 2 is ESM-only, so CommonJS must use asynchronous dynamic import; synchronous require() is not supported.
Add a local TypeScript declarationadd-typescript-shim
// types/trim-repeated.d.ts
declare module 'trim-repeated' {
export default function trimRepeated(input: string, target: string): string;
}Version 2 does not publish declarations. Include the folder in tsconfig if TypeScript does not discover the shim automatically.
Collapse a generated slug delimiternormalize-generated-slug
const rough = 'open---source--guide';
const slug = trimRepeated(rough, '-').replace(/^-|-$/g, '');
// 'open-source-guide'The package collapses repeats but does not remove leading or trailing delimiters, so edge trimming remains a separate step.
Limit slash cleanup to a URL pathavoid-url-corruption
const url = new URL('https://example.com//docs///api');
url.pathname = trimRepeated(url.pathname, '/');
// https://example.com/docs/apiDo not run slash collapsing over a complete URL string, because it would also reduce the `//` after the protocol.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| condense-whitespace | npm | Choose it when the actual job is trimming and collapsing whitespace rather than an arbitrary substring |
| collapse-white-space | npm | Choose it for a focused whitespace collapse helper that works on text with mixed whitespace characters |
| lodash | npm | Choose it only when Lodash is already in the application and a local replace expression avoids another package |