# Hyphenopoly, hyphenation for the browser in WebAssembly with a Node module

> mnater/Hyphenopoly is a JavaScript polyfill for CSS hyphenation that falls back to its own WebAssembly implementation when the browser does not support the language you need, and it is also a Node.js module for hyphenating plain strings. The language patterns ship as small binary files rather than JavaScript.

**mnater/Hyphenopoly** — Hyphenation for node and Polyfill for client-side hyphenation.

- Repository: https://github.com/mnater/Hyphenopoly
- Website: http://mnater.github.io/Hyphenopoly/
- Stars: 735 · Forks: 50
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/mnater-hyphenopoly

## What the project is and how it is put together

Hyphenopoly is a JavaScript polyfill for hyphenation in HTML. It hyphenates text if the user agent does not support CSS hyphenation at all, or does not support it for the required languages, and it is also a Node.js module. The package description is hyphenation for node and polyfill for client-side hyphenation.

The README describes the package as consisting of several parts with specific sizes, and those numbers are the design in miniature.

Hyphenopoly_Loader.js is about 11 KB unpacked and about 2 KB minified and compressed, and it feature-checks the client and loads other resources if necessary. Hyphenopoly.js is about 36 KB unpacked and about 5 KB minified and compressed, and it does the DOM work and wraps the WebAssembly. The WebAssembly modules carry the core hyphenation functions and the hyphenation patterns in a space-saving binary format, which also includes the pattern licence, and the sizes differ per language, with en-us at around 21 KB uncompressed and 15 KB compressed. Finally there is hyphenopoly.module.js, which is the Node module for hyphenating plain text strings.

That last detail is the interesting one. The hyphenation patterns for a language are a large body of data, and the usual way to ship them is as a JavaScript object, which is bulky and slow to parse. Putting them in a binary file that the WebAssembly module reads directly is what makes the whole approach viable, and the note that the pattern licence travels with the binary data is a careful detail, because the patterns for many languages are licensed separately from the code.

The licence is MIT, the package name is hyphenopoly, the version is 6.1.0, the main entry point is hyphenopoly.module.js and the type field is module, so it is an ES module package. The engines field requires Node 22 or newer, which is a fairly demanding floor and reflects the age of the current major release rather than a technical need of the algorithm.

The author is Mathias Nater, the homepage is mnater.github.io/Hyphenopoly, and the releases are v5.3.0 in November 2023, v6.0.0 in June 2024 and v6.1.0 on the first of January 2026, with the last push on 2026-04-30.

## The browser usage and the feature check that drives it

The browser usage has a specific order that matters for performance, and the README explains why.

You place all the Hyphenopoly code at the top of the header, immediately after the title tag, to ensure the resources are loaded as early as possible. Then you insert two script blocks. In the first you load Hyphenopoly_Loader.js as an external script. In the second you provide the initial configuration for the loader as an inline script, which also triggers all the further steps. And you have to enable CSS hyphenation.

The configuration object is passed to Hyphenopoly.config() and stored in a globally accessible object called window.Hyphenopoly, and the loader and the main script add their methods and properties only to that object, so there are no other global variables or functions beyond it.

The require property is the heart of it. The configuration object must have exactly one property called require, which is an object containing at least one name-value pair, where the name is a language code string and the value is a long word in that language, preferably more than 12 characters long. The README is specific that some languages are region specific and that the patterns directory lists the supported ones, so using en will not work and you need en-us or en-gb.

The long word is a probe, and the reason is worth understanding. The loader tests whether the client supports CSS hyphenation for the languages given in require, by measuring that word. If every language is supported natively, the loader deletes the Hyphenopoly object and stops, so the cost of the whole thing on a modern browser with the right languages is one script and one measurement. If a language is not supported, the loader automatically hides the document contents and loads Hyphenopoly.js and the necessary WebAssembly modules, which then hyphenate the elements according to the settings and unhide the document when it is done.

That hiding is why the loader has to be in the header. The document is invisible while the polyfill works, so a slow load means a blank page, and a failure means a permanently hidden document. The README covers that: if something goes wrong and Hyphenopoly.js cannot unhide the document, the loader has a timeout that kicks in after some time, 1000 milliseconds by default, unhides the document and writes a message to the console.

Hyphenopoly by default hyphenates elements and their children with the class name .hyphenate, and the README repeats the reminder not to forget to enable CSS hyphenation for the classes eventually handled by the polyfill. In other words, the polyfill inserts soft hyphens and the browser still does the line breaking, which is why the CSS property is required even when the polyfill is active.

## Using it from Node and the loader abstraction

The Node side installs the same package and uses a different entry point:

```shell
npm i hyphenopoly
```

and then configures a hyphenator with a loader callback:

```javascript
import hyphenopoly from "hyphenopoly";

const hyphenator = hyphenopoly.config({
    "exceptions": {
        "en-us": "en-han-ces"
    },
    "hyphen": "•",
    "loader": async (file, patDir) => {
        const {readFile} = await import("node:fs/promises");
        return readFile(new URL(file, patDir));
    },
    "require": ["de", "en-us"]
});

async function hyphenateEn(text) {
    const hyphenateText = await hyphenator.get("en-us");
    console.log(hyphenateText(text));
}

async function hyphenateDe(text) {
    const hyphenateText = await hyphenator.get("de");
    console.log(hyphenateText(text));
}

hyphenateEn("hyphenation enhances justification.");
hyphenateDe("Silbentrennung verbessert den Blocksatz.");
```

The loader callback is the design point that makes the module usable outside a browser. Rather than the package deciding how to read the pattern files, you supply a function that takes a file name and a pattern directory and returns the bytes. The example reads from the filesystem with the node:fs/promises module and resolves the file against the pattern directory as a URL. In a browser the same hook would fetch the file over the network, and in a bundler you could inline it, which is why the example directory includes a webpack example and a Deno example.

The exceptions option is a map of language code to hyphenation exceptions, with en-us mapped to en-han-ces in the example, which is the standard exception list preventing incorrect breaks in words like hyphenation itself. The hyphen option sets the character used for the break, and the bullet is chosen in the example so the effect is visible in the console.

The require option is a list of language codes, and the get method is asynchronous because the pattern data has to be fetched and compiled before a language can be used. The two functions in the example fetch the hyphenator for their language and then apply it to a string.

The pattern directory naming convention in the repository, with a lang/ directory and a patterns/ directory, matches what the loader callback receives, and the published package includes the patterns/ directory and the min/ variants, so a Node consumer gets the pattern data rather than having to source it separately.

## The project layout, the toolchain and where it fits

The repository layout shows a project with more going on than the two files the package ships.

There is src/ for the source and the built Hyphenopoly.js and Hyphenopoly_Loader.js at the top level, alongside hyphenopoly.module.js for Node. There is a patterns/ directory for the hyphenation patterns, a lang/ directory, and a min/ directory in the published package for the minified variants. There is an examples/ directory with a Deno example, a plain HTML example, a JavaScript example, a synchronous JavaScript example and a webpack directory, and a test/ directory plus a testsuite/ directory. There is a docs/ directory, a tools/ directory, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md and a CHANGELOG.md, and an .all-contributorsrc for the contributors list.

The toolchain is worth noting because it explains the design. The WebAssembly is written in AssemblyScript, with the assemblyscript dependency at version 0.28, and terser does the minification. The test runner is tap with c8 for coverage, and the type checking is TypeScript with typescript-eslint. The linting stack is unusually broad: eslint 10 with the stylistic plugin, a security plugin, an html plugin for the example files and a jsdoc plugin, and the markdown is linted with remark and the recommended preset. There is a yaml dependency and a remark configuration, and the c8 coverage reporters are configured in the package metadata.

The fact that the source of the hyphenation core is AssemblyScript rather than hand-written WebAssembly text is the reason the pattern format is a space-saving binary one. Compiling to a small binary that reads structured pattern data is a much better fit for a library that has to fit in a page, and the README's size breakdown is the evidence.

On where it fits, the honest answer is that the browser polyfill is a targeted fix rather than a general tool. Native CSS hyphenation is well supported in current browsers for the languages they ship patterns for, and where it is available it is faster and requires no JavaScript at all, which is why the loader's feature check is the first thing it does. What Hyphenopoly adds is coverage of the gap, and the gap is real: some languages are supported in one browser and not another, and Firefox and Chromium do not always agree on which. The Node module is the more broadly useful half, because hyphenating text for PDF generation or print layout on the server is a task with no native answer, and doing it correctly with the same patterns the browser uses keeps the two consistent.

For the patterns themselves, the licensing point deserves repeating. The pattern files carry their own licence, recorded inside the binary format, and many languages are patterned by organisations with their own terms. That is why the pattern directory is a first-class part of the package rather than something fetched from elsewhere.

## Conclusion

Adopt Hyphenopoly if you are typesetting justified text in a browser and need hyphenation in languages the browser does not support, or you need hyphenation in a Node pipeline such as PDF or print generation. Skip it if you are on a modern browser with the languages you need, since native CSS hyphenation is faster and needs no JavaScript. Verify first by checking the patterns directory for your language and region, because the codes are region specific and a plain en will not work.

## FAQ

### What is Hyphenopoly and what does it do?

It is a JavaScript polyfill for hyphenation in HTML that hyphenates text when the user agent does not support CSS hyphenation at all or not for the required languages, and it is also a Node.js module for hyphenating plain text strings. The core hyphenation functions and patterns run as WebAssembly.

### How does Hyphenopoly decide whether to use its own implementation?

The loader tests whether the browser supports CSS hyphenation for the languages given in the require configuration, using a long word in each language as the probe. If all languages are supported it stops immediately, and if one is not it hides the document, loads the polyfill and the WebAssembly modules, then unhides the document when it is done. A timeout of 1000 milliseconds by default unhides the document if something goes wrong.

### How do I use Hyphenopoly in Node?

Install it with npm i hyphenopoly, then call the config function with a require list of language codes, an exceptions map, a hyphen character and a loader callback that reads the pattern files. The hyphenator's get method returns the hyphenator for a language asynchronously, and you apply it to a string.

### Why do I need a region specific language code?

Some languages are region specific, and the README says plainly that just using en will not work, you need en-us or en-gb. The patterns directory lists the supported languages, and the configuration uses a long word in that language to test whether the browser supports it.

### What licence is Hyphenopoly released under?

MIT for the code, and the hyphenation patterns carry their own separate licence, which the README notes is included inside the space-saving binary format of the WebAssembly modules. The package is on npm as hyphenopoly at version 6.1.0 and requires Node 22 or newer.

## Sources

- [License: MIT](https://github.com/mnater/Hyphenopoly/blob/master/LICENSE)
- [mnater/Hyphenopoly on GitHub](https://github.com/mnater/Hyphenopoly)
- [Project website](http://mnater.github.io/Hyphenopoly/)
- [README](https://github.com/mnater/Hyphenopoly/blob/master/README.md)
- [Releases](https://github.com/mnater/Hyphenopoly/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mnater-hyphenopoly
