npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

anyword

v2.0.1

Published

Micro text segmenter — locale-correct words, graphemes and sentences, safe counting and truncation, one native Intl wrapper.

Downloads

427

Readme


One export. Correct boundaries. Any locale. Zero dependencies.

Naive JS quietly gets text wrong: .length miscounts emoji and accents, .split(" ") finds no words in Chinese or Thai, [...str] rips 👨‍👩‍👧‍👦 into pieces. The browser already knows where the real boundaries are — anyword is the thin wrapper. No rule tables, no locale files, no config.

import { anyword } from "anyword";

anyword("don't stop 世界");                          // ["don't", "stop", "世界"]
anyword("👨‍👩‍👧 hi", { by: "grapheme" });               // ["👨‍👩‍👧", " ", "h", "i"]
anyword.count("世界 test");                          // 2
anyword.count("👨‍👩‍👧", { by: "grapheme" });            // 1   — "👨‍👩‍👧".length is 8
anyword.truncate("héllo 👨‍👩‍👧", 5, { ellipsis: "…" }); // "héllo…"

install

npm install anyword

usage

anyword(text);
anyword(text, options);

Returns the segments as plain strings, in order. Everything else hangs off the same name:

anyword.parts(text, options?);          // { segment, index, isWordLike? }[]
anyword.count(text, options?);          // number
anyword.truncate(text, limit, options?); // string
anyword.supported;                       // boolean

anyword.parts() carries offsets into the original text, so you can highlight or slice without searching again.

anyword.parts("世界 test");
// [{ segment: "世界", index: 0, isWordLike: true }, { segment: "test", index: 3, isWordLike: true }]

anyword.parts(text, { raw: true }).map((p, i) =>
  p.segment === query ? <mark key={i}>{p.segment}</mark> : p.segment,
);

recipes

Copy, paste, move on.

// Word counter
anyword.count(post.body);
// 412

// Character counter users agree with (👨‍👩‍👧 counts as 1, not 8)
anyword.count(input, { by: "grapheme" });

// Safe preview / char-limit cut
anyword.truncate(bio, 140, { ellipsis: "…" });

// Word-limited excerpt
anyword.truncate(article, 30, { by: "word", ellipsis: " …" });

// Per-character animation, emoji intact
anyword(title, { by: "grapheme" }).map((c, i) => <span key={i}>{c}</span>);

// Safe reverse
anyword(text, { by: "grapheme" }).reverse().join("");

// Initials
anyword(fullName).slice(0, 2).map((w) => anyword(w, { by: "grapheme" })[0]).join("");

// Split into sentences
anyword(text, { by: "sentence" });

anyword is pure and synchronous — no clock, no state — so it renders the same on server and client. Pass an explicit locale to keep it that way.


granularity

by maps straight to Intl.Segmenter:

| by | Unit | Example | | --- | --- | --- | | "word" | words (default) | "don't stop 世界"["don't", "stop", "世界"] | | "grapheme" | user-perceived characters | "👨‍👩‍👧 hi"["👨‍👩‍👧", " ", "h", "i"] | | "sentence" | sentences | "Hi. Go now!"["Hi. ", "Go now!"] |

Word mode drops the segments between words — spaces and punctuation. Set raw: true to keep them, and the pieces join back into the original string.

anyword("hi, there!");                 // ["hi", "there"]
anyword("hi, there!", { raw: true });  // ["hi", ",", " ", "there", "!"]

Grapheme and sentence modes never drop anything, so raw does nothing there.

Every granularity in detail


options

| Option | Type | Default | Notes | | --- | --- | --- | --- | | by | "word" \| "grapheme" \| "sentence" | "word" | truncate defaults to "grapheme" | | locale | string \| string[] | runtime locale | BCP 47 tag or fallback array | | raw | boolean | false | word mode only — keep spaces and punctuation | | ellipsis | string | "" | truncate only — appended when text was cut |

truncate cuts on a segment boundary, so an emoji or an accented letter is never split. The ellipsis does not count toward the limit, and short input comes back untouched.

What each option does, with examples


locales

Any valid BCP 47 tag, and fallback arrays. The locale matters most for word breaking in scripts without spaces. When omitted, native Intl uses the runtime locale.

anyword("これは日本語です", { locale: "ja" }); // ["これ", "は", "日本語", "です"]
anyword("สวัสดีชาวโลก", { locale: "th" });   // ["สวัสดี", "ชาว", "โลก"] — no spaces needed
anyword("don't stop", { locale: "en" });     // ["don't", "stop"]

vs the alternatives

| | anyword | grapheme-splitter | words-count + lodash | | --- | :---: | :---: | :---: | | gzip | < 1kb | ~10kb | ~25kb | | unicode data bundled | no | yes | yes | | boundary rules | native Intl | bundled tables | regex | | word / sentence mode | yes | grapheme only | spaces only | | dependencies | 0 | 0 | 1+ |

anyword is not an NLP toolkit — it does one thing. Reach for a tokenizer or a full i18n framework when you need stemming, stop words or message catalogs.


stability

anyword follows semver. The public API is a single export — anyword, with parts, count, truncate and supported on it — plus AnywordOptions, Granularity and the exported types. It only changes shape in a major release.

Segment lists come from the runtime's ICU data and may vary between Node versions, browsers and OSes — especially for CJK and Thai. Test behaviour, not exact arrays.

migrating from 1.x

2.0 removed the separate anywordParts, anywordCount, anywordTruncate and supported exports. All four are the same values, now reached through the one name the package exports:

- import { anyword, anywordCount, anywordTruncate, supported } from "anyword";
+ import { anyword } from "anyword";

- anywordCount(text);
+ anyword.count(text);

- anywordTruncate(text, 20);
+ anyword.truncate(text, 20);

- supported ? anyword(text) : text.split(/\s+/);
+ anyword.supported ? anyword(text) : text.split(/\s+/);

Arguments, return values and throwing behaviour are unchanged. Every any* package follows this shape from 2.0 on: the bare call does the job, everything else hangs off the same name.


compatibility

Intl.Segmenter landed late — Firefox 125, Safari 14.1. On engines without it every call throws; branch on anyword.supported if you target them.

Node.js 18+ · Chrome 87+ · Firefox 125+ · Safari 14.1+ · Edge Runtime · Cloudflare Workers · Deno

CI runs the full suite on Node 20, 22 and 24.


the any family

anyword is part of any family — tiny, zero-dependency wrappers over native Intl, one API per package.

| | | | | --- | --- | --- | | anywhen | dates & relative time | Intl.DateTimeFormat | | anyamount | numbers, currency, units | Intl.NumberFormat | | anymany | lists | Intl.ListFormat | | anyaround | names & flags | Intl.DisplayNames | | anylong | durations | Intl.DurationFormat | | anyplural | plurals | Intl.PluralRules | | anyword | words & graphemes | Intl.Segmenter |

Want all of them? anyfamily is one install for the lot, and anyfamily-react wraps each as a hook with a shared locale provider.

npm install anyfamily

MIT © kirilinsky