Skip to main content

Linting an Eleventy site with Vale

I came across Vale, an open-source command-line tool that checks prose style. It’s like a linter or traditional style checker, but instead of code, it’s for text. I’ve been looking for something like this for a while!

Vale understands a bunch of different files, like Markdown and HTML, and seems to be able to ignore markup pretty well.

Once you’ve got it set up, there’s a few ways to integrate it into your work. You can put a Vale step in your review process or build pipeline. For folks on GitHub, there’s a Vale GitHub Action, and for other folks, it shouldn’t be tricky to integrate. (It exits non-zero when it finds issues, and it can print findings in multiple formats.) There’s also a Vale Language Server for editors that use Language Server Protocol things, and of course, you can run Vale on the command line.

I had some confusion and trouble setting it up. I had to review the entirety of the documentation a few times to get a general idea of how things go together.

I’m still no expert, but here’s how I set it up:

  1. I installed Vale.
  2. I created a .vale.ini file in the root of the project. I started with the Config Generator. After reading the documentation and scratching my head, I ended up with something like:
# See https://vale.sh/docs/topics/config/
# and https://vale.sh/generator/
.
StylesPath = .vale

# Hide some alerts based on severity. Set to suggestion, warning, or error.
MinAlertLevel = suggestion

# Make a directory at config/vocabularies/<thisvalue>/ in the StylesPath.
# Vale will look there for an accept.txt and reject.txt, which should
# consist of one word/phrase (or regular expression) per line.
# The accept lines will be accepted, and the reject line will be rejected.
#
# See https://vale.sh/docs/topics/vocab/
Vocab = atomwolf.org 

[*]
BasedOnStyles = Vale
  1. I ran Vale at the command line, telling it to check every file ending in .md in the content directory with vale content/**/*.md.
  2. I reviewed the output. It flagged a lot of spelling errors, which were mostly legitimate words it didn’t recognize. It did flag a legitimate misspelling. I fixed the misspelling.
  3. I created an atomwolf.org vocabulary file (.vale/config/vocabularies/atomwolf.org/accept.txt) for the words that Vale flagged that I wanted to keep. I put the words and phrases, one per line, in the accept.txt file. I didn’t have any phrases or words to always reject, so I didn’t make a reject.txt.
  4. I reran Vale to make sure the vocabulary files worked as expected. Great!

Ignoring Nunjucks directives

Nunjucks is a template engine, “essentially a port of Jinja2”. Nunjucks uses brackets to delimit filters, comments, and other directives. Filters are surrounded by {{ and }}, comments are surrounded by {# and #}, and other directives are surrounded with {% and %}. One of Vale’s strengths is that it’s supposed to understand markup well enough to work around it, but Vale doesn’t understand Nunjucks yet.

We can tell Vale to ignore these directives using the .vale.ini configuration file. I added:


[*.md]
# Ignore Nunjucks directives, {{ foo }} {% foo %} or {# foo #}
BlockIgnores = (?s){{.*?}}|{%.*?%}|{#.*?#}

BlockIgnores tells Vale to ignore everything the regular expression matches—anything inside {% and }%, {{ and }}, and {# and #} in Markdown files. It’s pretty good, but it might not be perfect. If our directives add text to our output files, that text is going to bypass our Vale checks.

Input vs Output

This site is made with Eleventy. Eleventy combines Markdown content files with templates and creates HTML files. If we want to be really pedantic, we need to check our HTML files, not just our Markdown input files, to check our prose after our templating and any other transformations.

You can check Markdown files and HTML files at the same time, like vale content/**/*.md _site/**/*.html, but I prefer to check them separately. I check all the Markdown input files first, and only check and the HTML files if the Markdown files pass. If I misspell something in a Markdown file, it’s more helpful to flag that error and stop. Showing me the error in the HTML files dilutes the output. Because Vale exits non-zero when it finds problems, I can run Markdown checks first and HTML checks only if the Markdown checks pass with:

vale content/**/*.md && vale _site/**/*.html

Ignoring a directory

I have some files that I don’t want to check in content/articles/style/. (They’re used for automated testing.) I used find to give Vale the list of files I wanted it to check, and excluded the ones that should be skipped, with:

find content -type f -name \*.md ! -path content/articles/style/\* | xargs -0 vale

find finds every file in the content directory that has a name that ends in .md and isn’t inside content/articles/style/. It gives those filenames to xargs, which uses those filenames as arguments and runs vale.

Vale Styles

The built-in Vale style is pretty sparse—just spelling with that accepts.txt and rejects.txt. The Vale folks have created style packages for other style guides, like the Google Developer Documentation Style Guide and the Microsoft Writing Style Guide, and they’ve created packages that have the equivalent (or as close as you can get, I guess) of other prose style checkers and linters, like write-good and proselint. I found these at the “Vale Package Hub”.

Commented sample configuration

After playing around with Vale, I made a sample .vale.ini config file that may be helpful.


# See https://vale.sh/docs/topics/config/
# and https://vale.sh/generator/

StylesPath = .vale

# Hide some alerts based on severity. Set to suggestion, warning, or error.
MinAlertLevel = suggestion

# Make a directory at config/vocabularies/<thisvalue>/ in the StylesPath.
# Vale will look there for an accept.txt and reject.txt, which should
# consist of one word/phrase (or regular expression) per line.
# The accept lines will be accepted, and the reject line will be rejected.
#
# See https://vale.sh/docs/topics/vocab/
Vocab = atomwolf.org 

# Each package is grabbed from an online repository when you run
# `vale sync` and placed into the StylesPath.
#
# See https://vale.sh/docs/topics/packages/
# and https://vale.sh/hub/

Packages = Google, write-good

[*]
# This configuration applies to all files.
# Apply the rules from these styles
BasedOnStyles = Vale, Google, write-good

[*.md]
# This configuration applies to all .md files.
# Ignore Nunjucks directives, {{ foo }} {% foo %} or {# foo #}
BlockIgnores = (?s){{.*?}}|{%.*?%}|{#.*?#}

Next Steps

I looked through the Google and Microsoft style guides. I don’t want to use them as-is—this site has a different voice than the technical documentation they’re intended for.

I’d like to look through the style sets the Vale folks have made to mimic other tools, like proselint and write-good, and pull in the rules I like.

What I am really excited about, though, is making my own style guide, especially for different types of pages. In just a few minutes, I used Vale Studio to make a few helpful rules, like requiring particular headers in the dotted metadata block at the top of this page, and requiring that articles begin with an “Introduction” section.

Have a suggestion? An important correction? Let me know!

If this was helpful or enjoyable, please share it! To get new posts, subscribe to the newsletter or the RSS/Atom feed. If you have comments, questions, or feedback, please email me.