Use Hyphenopoly to insert better hyphenations in your Markdown content
Automagically insert soft hyphens into your Markdown content using Frank Liang’s LaTeX algorithm.·320 views
Update 2024-01-04: The
hyphenate-limit-chars property has ~70% global support. The easiest solution would be to add the following to your CSS:
Adding on to my previous post, Remove runts from your Astro Markdown files with ~15 lines of code, I wanted to share another quick tip for improving the layout of your Markdown prose.
We’re going to write a custom Remark plugin to suggest line breaks in our Markdown content. Doing this allows us to use the
hyphens: manual CSS property while avoiding awkward breaks in places they shouldn’t be.
CSS does have a property for breaking words and inserting hyphens —
hyphens: auto — but it’s not super granular about the words it breaks. For example, words with only 5 letters will get broken just as easily as words with 20 letters. Yuck!
There is another CSS property to control the length of words that get broken (
hyphenate-limit-chars), but it’s not yet widely supported.
This custom plugin I wrote sprinkles line break opportunities into your markup by inserting the
­ entity. The
­ entity is a soft hyphen, which means it’s a suggestion to the browser to insert hyphens there when the
hyphens: manual CSS property is used.
Under the hood, I’m using the package
hyphenopoly to insert these soft hyphens everywhere. According to the package’s documentation, hyphenopoly uses Franklin M. Liang’s hyphenation algorithm developed for TeX. If you’re curious how that algorithm works, I suggest you check out the project’s README.
Note: If you need help setting up your Astro blog for Markdown (
.mdx), I recommend following Astro’s “Build a Blog” tutorial.
This time, we’ll need two npm packages:
Now, we’ll create the JS file that will actually do the transformation. I like to store my custom plugins in a folder called
/remark-plugins at the root of my project, but you’re welcome to put them wherever.
This Remark plugin has a bit more going on than my last one. That’s because hyphenopoly must have a pattern specified to apply the soft hyphens. The package ships with a ton of built-in language support (71 by my count!), so you have to tell it which
.wasm file to load up.
In my case, I’m using the
en-us pattern. You can find the full list of patterns here.
Warning: Boring code explanation time!
loaderSyncfunction is a bit of a hack — it loads the
en-us.wasmfile from the
hyphenopolypackage directory. I’m not sure if there’s a better way to do this, but it works for now.
- We pass in our custom loader with the
.wasmpattern, and specify which language(s) we want to use.
- You can specify exceptions to the hyphenation rules, which I’ve done for the word “Houston.”
- You can specify a minimum word length for hyphenation, but I’ve left that commented out for now. Six is the default, and that was good enough for me.
- I’ve also commented out the optional
dontHyphenateClasssetting, which allows you to specify a CSS class that will prevent hyphenation from happening.
- We pass in our custom loader with the
- We’re using
unist-util-visitto traverse the Markdown AST and replace the text content of each (textual) DOM node (
node.value) with our hyphenated text.
The last thing we have to do is tell Astro to use our new plugin. We can do this by adding a
remarkPlugins array to our
To reap the full benefit of our custom plugin, we need to add at least one CSS property to our prose content:
I like the way justified text looks, but I’ve found it can look a bit wonky across browsers and screen sizes. Your mileage may vary.
Feel free to tinker with the hyphenopoly settings to your heart’s content. You can load up additional languages, or even create your own custom patterns.
I’m still not 100% happy with the way this plugin works. I’d like a better method to load in the
.wasm pattern file. My loader function seems rather brittle.
Out of the box, hyphenopoly provides orphan control (they actually mean “runts”), which I’d like to test. If it does as good a job as my Remark “deruntify” plugin, then that’s redundant code I can happily rip out.