How Lodash Words Segments Compound Emoji Characters

The Lodash _.words utility accurately isolates words and complex characters—including compound emojis—by combining Unicode feature detection with an elaborate, purpose-built regular expression. When processing strings, Lodash avoids standard naive character splitting, instead recognizing multi-codepoint sequences such as Zero-Width Joiners, variation selectors, and skin tone modifiers. This article explains how Lodash inspects input text, routes it to a specialized Unicode word tokenizer, and preserves the integrity of compound emojis.

The Challenge of Compound Emojis in JavaScript

JavaScript represents strings using UTF-16 code units. While standard characters fit into a single 16-bit code unit, modern emojis often exist outside the Basic Multilingual Plane (BMP) as surrogate pairs (two 16-bit units).

Compound emojis increase this complexity by combining multiple individual emojis into a single visual glyph. These include:

Standard JavaScript methods like String.prototype.split('') or simplistic regular expressions split these sequences at the surrogate or joiner boundary, causing data corruption and rendering broken emoji fragments.

Step 1: Unicode Detection Routing

When _.words(string, pattern) is invoked without a custom pattern argument, Lodash determines whether the input string contains complex Unicode characters before running its tokenization logic.

It checks the string against an internal regular expression—commonly referenced as hasUnicodeWord in the Lodash source. This check scans for code points outside the ASCII range, specifically looking for astral symbols, variation selectors, diacritics, and the zero-width joiner (\u200D).

Step 2: The Structure of the Unicode Word Regex

The unicodeWords function relies on a compiled regular expression composed of modular Unicode ranges. To handle compound emojis alongside written scripts, Lodash constructs regex components that explicitly match compound emoji mechanics:

  1. Surrogate Pair Matching: High and low surrogate ranges are explicitly grouped to treat 32-bit astral symbols as atomic units rather than two separate characters.
  2. Fitzpatrick Modifiers: The regex appends optional modifiers ([\uD83C[\uDFFB-\uDFFF]]) immediately following eligible person or hand glyphs to ensure tone adjustments remain attached to the base glyph.
  3. Variation Selectors: Patterns include optional matches for \uFE0E (text presentation) and \uFE0F (emoji presentation), preventing selector bytes from detaching.
  4. ZWJ Recursion Simulation: The regular expression uses non-capturing groups structured around \u200D. It defines an emoji pattern followed by (?:\u200D(?:pattern))*, allowing the regex engine to match indefinitely chained sequences (such as family or profession combinations) as a single token.
  5. Flag Sequences: Pairs of Regional Indicator characters ([\uD83C\uDDE6-\uD83C\uDDFF]) are matched specifically in couples, ensuring flags are not split into isolated letters.

Step 3: Extraction and Output

Once the appropriate regular expression matches the text, _.words uses String.prototype.match against the compiled pattern. Because the emoji definitions within the pattern have higher structural precedence and greedily consume modifiers and ZWJ-linked sequences, the entire composite glyph is captured as an unbroken string element.

The resulting array safely isolates standard language words alongside fully qualified compound emojis, maintaining the visual and technical integrity of the multi-byte characters.