eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

Objects are initially perceived to be empty in eleventyComputed during the first pass through

Open AleksandrHovhannisyan opened this issue 3 years ago • 7 comments
trafficstars

Describe the bug

In an eleventyComputed context, object data is initially seen as empty ({}) until another pass over the data occurs, at which point it is non-empty. I'm not sure if this is a bug or working as intended, but I would expect that a hard-coded object in front matter would initially be seen as-is since it doesn't depend on any other data in the cascade.

To Reproduce

See the repro sandbox

Create a very simple template like this:

src/_pages/index.html

---
object:
    key: value
---

Create a corresponding 11ty data file for it:

src/_pages/index.11tydata.js

module.exports = {
  eleventyComputed: {
    async: async (data) => {
      console.log(`async data.object`, data.object);
    },
    sync: (data) => {
      console.log(`sync data.object`, data.object);
    },
  },
};

Start your server. Observe the following logs:

async data.object {}
sync data.object {}
async data.object { key: 'value' }
sync data.object { key: 'value' }

(Tested both synchronous and asynchronous computed data to rule that out. Observe that even for synchronous computed data, the existing data.object is perceived to be an empty object, even though it's not.)

Expected behavior

Object data should not be initially empty in eleventyComputed.

Environment:

  • OS and Version: N/A
  • Eleventy Version: 1.0.0

Additional context

This is only reproducible with objects. Plain key-value data in front matter is perceived as-such by eleventyComputed.

AleksandrHovhannisyan avatar Feb 21 '22 15:02 AleksandrHovhannisyan

I'm having the same issue. When I remove all the frontmatter from my individual .md files and place a .11tydata.js local data file in the directory, the build process throws the error:

> Having trouble writing template: build/components/button/index.html

`TemplateWriterWriteError` was thrown
> (./src/_includes/partials/content.njk)
  Error: Node does not exist: button

Logged debug output:

data.page : {}
data.page : {
  date: 2022-01-27T08:48:57.686Z,
  inputPath: './src/pages/components/button.md',
  fileSlug: 'button',
  filePathStem: '/pages/components/button',
  url: '/components/button/',
  outputPath: 'build/components/button/index.html'
}
# repeats for all other .md files in the dir

The local data file:

// directory is named 'components', filename is components.11tydata.js
const path = require('path');
const cwd = path.basename(__dirname);

module.exports = {
  layout: 'partials/content.njk',
  eleventyComputed: {
    tags: () => [cwd],
    title: (data) => {
      if (!data.title) {
        return data.page.fileSlug;
      }
    },
    permalink: (data) => {
      if (!data.permalink) {
        return `${cwd}/${data.page.fileSlug}/`;
      } else {
        return data.permalink;
      }
    },
    eleventyNavigation: {
      key: data => data.title,
      // INFO: case sensitive! The parent has to match the key exactly.
      parent: () => cwd.trim().replace(/^\w/, (c) => c.toUpperCase())
    },
    test: (data) => {
      // debug log posted above
      console.log('data.page :', data.page);
    }
  }
}

davidjost avatar Feb 22 '22 10:02 davidjost

@davidjost Interesting! Not to get too far off topic for this thread, but… do you have a public repo with this [to save me trying to recreate everything from scratch]? I don't think I've seen errors like that when migrating from front matter to data files. And I can't immediately understand what the error message means:

Error: Node does not exist: button

Button is the component/file name, but not sure what the "Node" is in this context.

Wonder if it's eleventyNavigation related. I think there might be a subtle bug in your title computed function where it isn't returning a title.

    title: (data) => {
      if (!data.title) {
        return data.page.fileSlug;
      }
    },

You say if data.title does NOT exist, return data.page.fileSlug, but there isn't an else or other return statement, so if I'm reading that correctly, if data.title is defined, that function will return undefined (which made me suspicious of eleventyNavigation since it's expecting [the maybe undefined] data.title property as a key).

I'm honestly a little curious what the tags and cwd is in this context too. My guts tell me it might be "components", and not something more specific like "button", but that's just a wild guess. And if I recall correctly, dynamically setting the tags property like that works-kinda. It will set the tags, but I don't think it will auto-create collections like it does if you set it directly in front matter.

pdehaan avatar Feb 22 '22 18:02 pdehaan

@pdehaan You are right, I was missing the else and the return of the title if one is set, thanks for the tip. I corrected it, but that wasn't the cause. I have forked a comparable project here: https://stackblitz.com/edit/github-exz3x9-nojyxl?file=posts/posts.11tydata.js In the console you see the double logs and empty objects. In the preview it should list the posts, but it lists NaN instead. However this does not throw an error like my project does, I might have another mistake somewhere I can't pinpoint at the moment.

tags is supposed to get the current directory (components in my case) via the node function path.basename(__dirname), which returns the current path and extracts the last directory into a string. My intent is to set the tags for all pages in components in one place instead of many.

davidjost avatar Feb 23 '22 11:02 davidjost

Not sure if your repo or the stackblitz is newer, but here's my diff:

// index.njk
{% set maxPosts = collections.posts | length | min(3) %}
<h1>Latest {% if maxPosts == 1 %}Post{% else %}{{ maxPosts }} Posts{% endif %}</h1>

Moved the tags and layout properties out of the eleventyComputed block since they didn't need to be computed. Plus by dynamically setting the eleventyComputed.tags property, it doesn't auto-create collections, which is I think why your collections.posts was empty and not paginating on your index page.

// posts/posts.11tydata.js
module.exports = {
  tags: [cwd],
  layout: 'layouts/post.njk',
  eleventyComputed: {
    title: (data) => {
      return data.title || data.page.fileSlug;
    },
    permalink: (data) => {
      return data.permalink || `${cwd}/${data.page.fileSlug}/`;
    },

You'll still see the double output in the console, but I don't think anything is wrong/broken and think it's unrelated to your other issues so you can mostly ignore it;

data.page : {}
data.title : 
data.page : {
  date: 2022-02-23T15:14:34.921Z,
  inputPath: './posts/thirdpost.md',
  fileSlug: 'thirdpost',
  filePathStem: '/posts/thirdpost',
  outputFileExtension: 'html',
  url: '/posts/thirdpost/',
  outputPath: '_site/posts/thirdpost/index.html'
}
data.title : thirdpost

pdehaan avatar Feb 23 '22 15:02 pdehaan

Just wanted to bump this thread to note that I'm still seeing this issue. Not sure why data is nullish in 11tydata files on the first pass.

AleksandrHovhannisyan avatar Jul 26 '22 22:07 AleksandrHovhannisyan

I also stumbled across this, just sharing the fix that is working for me. Might not work in all cases! As per example in OP:

module.exports = {
  eleventyComputed: {
    async: async (data) => {
      # `eleventyComputed` is executed twice (the first time with empty data), so bail early.
      if (Object.keys(data).length === 0) return;
      console.log(`async data.object`, data.object);
    },
    sync: (data) => {
      # `eleventyComputed` is executed twice (the first time with empty data), so bail early.
      if (Object.keys(data).length === 0) return; 
      console.log(`sync data.object`, data.object);
    },
  },
};

It was a while back, but my code comments refer to https://github.com/11ty/eleventy/issues/1278#issuecomment-894374284

d3v1an7 avatar Dec 04 '23 22:12 d3v1an7