Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow renderFile or equivalent to fail silently if file does not exist #3051

Open
thedamon opened this issue Sep 16, 2023 · 5 comments
Open

Comments

@thedamon
Copy link

Is your feature request related to a problem? Please describe.

I want to pull in files dynamically, associating markdown descriptions of taxonomies and other things like so:

{%- set desc = "./_meta/tags/" + tag + ".md" -%}
<div>{% renderFile desc %}</div>

In this scenario, though, if the tag does not exist eleventy will fail to build.

Describe the solution you'd like

It seems potentially useful to be able to opt not to fail if the file is not there today. Probably best would be an additional shortcode that has a try/catch in it:

{% renderFileIfExists desc %}

Nunjucks as an option in include: {% include "missing.html" ignore missing %}

Describe alternatives you've considered

A simple way to check the existence of the file in another variable or maybe more helpfully overall adding a custom

{% try %} block to supported templating languages then I could instead do

{% try %} <div>{% renderFile desc %}</div> {% endtry %}

Additional context

No response

@uncenter
Copy link
Contributor

Good suggestion! How about renderFileSafe as the name of the shortcode?

@pdehaan
Copy link
Contributor

pdehaan commented Sep 16, 2023

eleventy.config.js

const fs = require("node:fs");
const path = require("node:path");

const { EleventyRenderPlugin } = require("@11ty/eleventy");

/**
 * @param {import("@11ty/eleventy/src/UserConfig")} eleventyConfig
 * @returns {ReturnType<import("@11ty/eleventy/src/defaultConfig")>}
 */
module.exports = function (eleventyConfig) {
  eleventyConfig.addPlugin(EleventyRenderPlugin);

  eleventyConfig.addAsyncShortcode("renderFileIfExists", async function (filename, data={}) {
    filename = path.join("./src/_includes", filename);
    if (fs.existsSync(filename)) {
      return eleventyConfig.javascriptFunctions.renderFile(filename, data);
    }
    return "";
  });

  return {
    dir: {
      input: "src",
      output: "www",
    }
  };
};

src/cats.njk

---
title: Cats
---

{%- set desc = "tags/" + page.fileSlug + ".md" -%}
{%- renderFileIfExists desc %}

Cat things here.

src/dogs.njk

---
title: Dogs
---

{%- set desc = page.fileSlug + ".md" -%}
{% renderFileIfExists desc %}

Dog things here.

src/_includes/tags/cats.md

# CATS LIVE HERE
They _sometimes_ catch mice.

OUTPUT

www/cats/index.html

<h1>CATS LIVE HERE</h1>
<p>They <em>sometimes</em> catch mice.</p>

Cat things here.

www/dogs/index.html

Dog things here.

DIRS

tree -A --gitignore
.
├── eleventy.config.js
├── package-lock.json
├── package.json
├── src
│   ├── _includes
│   │   └── tags
│   │       └── cats.md
│   ├── cats.njk
│   └── dogs.njk
└── www
    ├── cats
    │   └── index.html
    └── dogs
        └── index.html

6 directories, 8 files

@pdehaan
Copy link
Contributor

pdehaan commented Sep 16, 2023

If you want a more .addFilter() approach:

eleventy.config.js (snippet)

const fs = require("node:fs");


  eleventyConfig.addFilter("fsExists", function (filename) {
    return fs.existsSync(filename);
  });

src/cats.njk

{%- set desc = "src/_includes/tags/" + page.fileSlug + ".md" -%}
{% if desc | fsExists %}
  {% renderFile desc %}
{% endif %}

A bit more verbose, but possibly more flexible.

@thedamon
Copy link
Author

thedamon commented Sep 17, 2023 via email

@pdehaan
Copy link
Contributor

pdehaan commented Sep 17, 2023

@thedamon You could do some hybrid solution that uses the shortcode but still lets you use the filter for other things (like checking for global data files or whatever):

  eleventyConfig.addFilter("file_exists", function (filename, throwOnMissing=false) {
    const exists = fs.existsSync(filename);
    if (throwOnMissing && !exists) {
      throw new Error(`Missing file: "${filename}"`);
    }
    return exists ? filename : false;
  });

  eleventyConfig.addAsyncShortcode("renderFileIgnoreMissing", async function (filename, data={}) {
    const fileExists = eleventyConfig.getFilter("file_exists");
    if (!!fileExists(filename)) {
      return eleventyConfig.javascriptFunctions.renderFile(filename, data);
    }
    return "";
  });

USAGE

A:
{%- set descA = ("src/_includes/tags/" + page.fileSlug + ".md") | file_exists -%}
{%- renderFileIgnoreMissing descA %}

---

B:
{%- set descB = ("src/_includes/tags/" + page.fileSlug + ".md") | file_exists() -%}
{% if descB %}
  {% renderFile descB %}
{% endif %}

---

C:
{%- set descC = ("src/_includes/tags/" + page.fileSlug + ".md") | file_exists(true) -%}
{% renderFile descC %}

Both example A and B should work since we're using our custom renderFileIgnoreMissing shortcode or wrapping the native renderFile in a conditional check.
Example C should fail loud since we're setting the throwOnMissing argument which throws a build error.


UPDATE: Sorry, I re-read this and it's a bit of a mess. Probably worth explaining this filter:

  eleventyConfig.addFilter("file_exists", function (filename, throwOnMissing=false) {
    const exists = fs.existsSync(filename);
    if (throwOnMissing && !exists) {
      throw new Error(`Missing file: "${filename}"`);
    }
    return exists ? filename : false;
  });

Basically the probably-confusingly-named file_exists filter no longer returns a Boolean, but either the input filename or false (or throws a hard error if throwOnMissing is truthy).
Also confusing is that now the string concatenation needs to be wrapped in parens, or apparently the | file_exists filter was only applying to that last segment (if I remember correctly -- so it was trying to do ".md" | file_exists and then prefix that result with `"src/_includes/tags")).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants