How Lodash Truncate Handles Zero-Width Joiners
When truncating strings containing Zero-Width Joiner (ZWJ) characters
(\u200D), the Lodash JavaScript library prevents visual
corruption by treating entire emoji and character sequences as single,
indivisible grapheme clusters. Rather than relying on native JavaScript
string methods that operate on UTF-16 code units,
_.truncate relies on specialized internal Unicode detection
and segmentation regular expressions. This ensures composite
characters—such as complex multi-person emojis or modified glyphs—are
measured accurately and are not cut in half during truncation.
The Challenge with Native JavaScript Truncation
In standard JavaScript, strings are indexed by 16-bit code units.
Complex Unicode sequences, such as the sequence for the technologist
emoji (👨💻), consist of multiple code points: the base man emoji
(\uD83D\uDC68), a zero-width joiner (\u200D),
and a personal computer (\uD83D\uDCBB). If native
String.prototype.slice() or .length is used to
truncate after a fixed number of units, the slice might land directly on
or after the ZWJ character, severing the sequence and rendering broken
or unintended glyphs.
Lodash's Unicode Detection
Before slicing, _.truncate determines whether the input
string contains complex Unicode structures. It checks the string against
an internal regular expression pattern (reHasUnicode).
This regular expression specifically checks for the presence of:
- Surrogate pairs
- Combining diacritical marks
- Variation selectors
- Zero-width joiners (
\u200d)
If reHasUnicode.test(string) returns false,
Lodash defaults to standard string slicing for performance. If it
returns true, Lodash routes the string through a
specialized Unicode parsing workflow.
Grapheme Segmentation
via unicodeToArray
When a ZWJ is present, Lodash converts the string into an array of
visual graphemes using unicodeToArray (or
stringToArray). This function matches the string against a
comprehensive Unicode regular expression (reUnicode).
Within reUnicode, Lodash explicitly defines patterns
that combine base characters, variation selectors, and modifiers with
ZWJ characters:
// Conceptual representation of Lodash's ZWJ matching logic
const reOptMod = `(?:${rsModifier})?`;
const reOptVar = `[${rsVarRange}]?`;
const reOptJoin = `(?:${rsZWJ}(?:${[rsNonAstral, rsRegional, rsSurrPair].join('|')})${reOptVar}${reOptMod})*`;This pattern matches any base symbol and consumes subsequent
\u200D characters alongside their associated join targets
repeatedly. As a result:
- Atomic Grouping: The entire chain (e.g.,
👨+ZWJ+💻) is matched as a single array element instead of three separate parts. - Length Measurement: Lodash calculates the string's length based on the number of visual symbols, meaning a multi-part ZWJ sequence counts toward the truncation limit as one character.
Truncation and Reassembly
Once split into an array of grapheme clusters,
_.truncate extracts the desired number of visual elements
using array slicing (Array.prototype.slice). Because the
ZWJ sequences are preserved inside individual array elements, the slice
cannot sever a joiner from its linked symbols.
Finally, Lodash joins the sliced array back into a string and appends
the configured omission string (defaulting to '...'),
ensuring the truncated output remains visually coherent and free of
broken Unicode artifacts.