jan 06, 2025
🔗 Serve static files with Eleventy
Eleventy is a static site generator that can be configured to do everything that you can imagine. A nice addition for a technical blog can be to serve static files. This is useful when you want to include files like PDFs, or downloadable code snippets in your project. This was a research needed to make the code-sandbox web component fetch files correctly.
The first tool to reach to in this case, is Passthrough File Copy, the Eleventy utility that allows you to copy files from the input directory to the output directory, otherwise Eleventy will ignore all non-templates files.
eleventyConfig.addPassthroughCopy("content/posts/*/**/*.{js,pdf}");
This command allow you to put the files directly in your posts directory and Eleventy will copy them to the output directory while preserving the same directory structure.
🔗 Bundling with Vite
Vite is the most recommended bundling tool for Eleventy and this blog too has been originally setup to use Vite to run after the Eleventy build, using the official Eleventy plugin eleventy-plugin-vite.
This works very well for quickly setting up a feature-rich development environment, because its default configuration provides a lot of features out-of-the-box, like asset hashing, tree-shaking, and of course, bundling and minification of JavaScript and CSS files.
However, in case you want to serve static files the Vite step can silently work against you, since Vite will remove all files which are not linked in the outputted html files or hash and move, usually in the /asset
folder, the rest of the files during in the production build, and this can break the links to the files in your posts.
After a tedious debug to figure out which was the issue, a possible solution is funnily another Vite unsolicited feature, the public directory. Maybe it's not widely known that Vite by default will copy the files in the public directory to the output directory without touching them, and it will also flat the /public
directory structure in the project root.
public
├── assets
│ └── pdf.pdf
└── og-image.png
The above will be transformed to this:
dist
├── assets
│ └── pdf.pdf
└── og-image.png
To take advantage of this feature, you can copy all the static files to the /public/asset
directory, which Vite will then move to the root output directory leaving only the existing /asset
folder.
eleventyConfig.addPassthroughCopy({
"content/posts/*/**/*.{js,pdf}": "public/assets",
});
I can then link the files in the markdown files like this:
[Download PDF](/assets/demo.pdf)
It would be even better if addPassthroughCopy
could be configured to move the files to the /public
directory and at the same time keep the existing file structure, this way the relative link typed in markdown would still be valid.
[Download PDF](/demo.pdf)
But setting complex rules in the addPassthroughCopy
's options
object is not so straightforward. Eleventy under the hood uses recursive-copy for this feature, but tweaking the documentation example didn't work for me.
// Not working
eleventyConfig.addPassthroughCopy("content/posts/*/**/*.{html,css,js}", {
rename: function (filePath) {
return filePath.replace("content/", "public/content/");
},
});
# CSS Icon
To display a nice little icon in front of downloadable files, I added a CSS down arrow to the markdown.css
, like this demo.pdf
a[href$=".pdf"]::before {
content: "";
display: inline-block;
background-color: currentColor;
mask-image: url("data:image/svg+xml,%3Csvg ... %3E");
mask-repeat: no-repeat;
mask-size: contain;
}
# Markdown-it plugin
This works already but the link is not yet a download link, it's just a link to the file. To make it a direct download link, there is a handy markdown-it plugin crookedneighbor/markdown-it-link-attributes to add the download
attribute to the link.
// Add download attributes to file links
mdLib.use(markdownItLinkAttrs, {
matcher(href, config) {
return href.endsWith(".pdf");
},
attrs: {
target: "_blank",
download: "download",
},
});