When I checked the word count of my first post, I noticed that the number of words in the content was not the same as was output from the plugins recommended by Astro.
The Astro docs suggest installing two
plugins to calculate the read time/word count- mdast-util-to-string
and reading-time
.
If you’re using MDX, and importing or exporting custom components, the mdast-util-to-string
plugin
includes the import and export statements which is something you probably don’t want as part of the read
time.
To remove the ES Module imports and exports, we can filter those nodes in the MDAST(Markdown Abstract Syntax Tree) before converting it to a string.
Moreover, the plugin merges elements together without any spaces. This means that a word at the end of one paragraph gets combined with the first word of the next paragraph, reducing the overall word count by the number of top-level blocks in your MDX file.
To fix this issue, you can iterate over the last child of each top-level element and manually add a space in the cloned markdown syntax tree.
Note- Do not change the original tree or do a shallow copy as the changes are performed in-place. Astro won’t be able to recognize your imports/exports and the spaces will be added to the DOM nodes.
import getReadingTime from "reading-time";
import { toString } from "mdast-util-to-string";
export function remarkReadingTime() {
return function (tree, { data }) {
// Create a deep clone
const readingTree = structuredClone(tree);
// Remove ESM statements
readingTree.children = readingTree.children.filter(
(node) => node.type !== "mdxjsEsm"
);
readingTree.children.forEach((node) => {
// Add a space if it's a value node
if ("value" in node) {
node.value += " ";
} else if ("children" in node) {
// Traverse to the last child and add a space
let parent = node;
let child = node.children.at(-1);
while (child) {
parent = child;
child = child.children?.at(-1);
}
if (parent && "value" in parent) {
parent.value += " ";
}
}
});
const textOnPage = toString(readingTree);
const readingTime = getReadingTime(textOnPage);
data.astro.frontmatter.minutesRead = readingTime.text;
};
}