eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

page object is not available in the JavaScript front matter

Open hidegh opened this issue 4 years ago • 13 comments

Describe the bug Was experimenting with the possibility to use JS in the front matter (to call some globally defined function. While using the computedData with plain (yaml) front matter, the page.url was working, the same did not worked with JS frontmatter.

Might relate to:

  • wrong this binding
  • possibly a missing extra parent object under the page is accessible in JS - if so, then also missing documentation: https://www.11ty.dev/docs/data-frontmatter/#javascript-front-matter

Also note:

JS front matter seems to be a powerful tool, adding documentation on how to create and nicely inject JS functions (to be able to call them in the JS font-matter) would be welcome.

Currently I just created a plain main = function(p) { return p; } inside the main .eleventy.js - probably to limit pollution I'd rather add a fnc = require('my-functions') to import multiple functions safely. This might not be the ideal solution, just the one I was figuring out while playing around.


So details on the described BUG:

---
layout: default
permalink: test/
eleventyComputed:
    title: 'url = {{ page.url }}'
---

---js
{   
    layout: "default",
    permalink: 'test/',
    data: main('1..2..3'),
    eleventyComputed: {
        title: `url = ${ page.url }`
    } 
}
---

Failed with error:

`SyntaxError` was thrown:
    SyntaxError: ReferenceError: page is not defined
        at Object.parse (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\node_modules\gray-matter\lib\engines.js:48:13)
        at module.exports (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\node_modules\gray-matter\lib\parse.js:12:17)
        at parseMatter (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\node_modules\gray-matter\index.js:109:17)
        at matter (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\node_modules\gray-matter\index.js:50:10)
        at Template.read (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\src\TemplateContent.js:82:16)
        at async Template.getFrontMatterData (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\src\TemplateContent.js:141:7)
        at async Template.getData (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\src\Template.js:235:29)
        at async Template.getTemplateMapEntries (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\src\Template.js:744:16)
        at async TemplateMap.add (C:\Users\Balazs\AppData\Roaming\npm\node_modules\@11ty\eleventy\src\TemplateMap.js:32:21)
        at async Promise.all (index 4)

To Reproduce Create any test.njk, use the 2 provided front matters to test (actually remove the data: main('1..2..3') from the JS).

With JS frontmatter the exception with the stack-trace above will occur.

Expected behavior When processing JS front-matter and using computedData same objects should be avail. as in case of YAML.

Screenshots n/a

Environment:

  • OS and Version: Win 10
  • Eleventy Version: 0.11.1

Additional context n/a

hidegh avatar Feb 20 '21 18:02 hidegh

Spent a few minutes looking into this and following the data through @11ty/eleventy into gray-matter to figure out what's different between the JS and YAML frontmatter parsing. Turns out there's not much difference! There are no eleventy variables available in the parse step for either format and the behaviour there is actually a perfect 1:1 match between the two.

For both formats, you have to use the same {{ page.url }} syntax to access any eleventy data. If you change your code to look like this, so that your JS frontmatter title passes the exact same title as the YAML one, it'll work the way you want it to and you can access the page object in the JS frontmatter!

---js
{
    eleventyComputed: {
        title: "url = {{ page.url }}"
    }
}
---

Cool question! Figuring this out was a fun way to spend 15 minutes and I learned a couple of things about how Eleventy works in the process. I can totally understand the potential for confusion with this and I definitely agree that it could be a good thing to document!

I don’t think you’re gonna be able to call functions from the JS frontmatter though. And it looks to be by design: frontmatter parsing happens way before the resulting object gets turned into page data. Seems like quite a nice design choice to me too as it gives parity between the different frontmatter formats.

henrycatalinismith avatar Apr 01 '21 18:04 henrycatalinismith

I’ve been stuck on this problem for days!

This definitely needs to be added to the documentation, because it’s not at all obvious to keep the same non-JS interpolation syntax in the front matter.

Zearin avatar Jul 31 '22 17:07 Zearin

…Actually, I think I’m still stuck on this. My setup is a little different:

Front Matter

---js
{
    layout: "default",
    pagination: {
        data:   calendar.index,
        size:   1,
        alias:  calDate,
    },
    permalink: "calendar/{{ calDate.year }}-{{ calDate.month }}/"
}
---

This front matter is my first attempt at following the example from the 11ty docs “Create page from data” article. It’s nearly identical, except for some different variable names.

Data

Also, the relevant data in my global data directory is stored in 📁_src/_data/calendar/index.js. (I checked for errors in the data file. Nothing’s wrong.)

The Error

So the error that I get regarding the above front matter is:

[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] 1. Having trouble reading front matter from template ./_src/calendar.pug (via TemplateContentFrontMatterError)
[11ty] 2. ReferenceError: calendar is not defined (via SyntaxError)
[11ty] 
[11ty] Original error stack trace: SyntaxError: ReferenceError: calendar is not defined
…

This is my first attempt at creating pages from data, so I’m not sure how to solve this…

Zearin avatar Jul 31 '22 17:07 Zearin

ReferenceError: calendar is not defined (via SyntaxError)

I'd have to try recreating this locally, but I think since you're using JavaScript front-matter (and not YAML), you'd need to maybe quote the values:

{
    layout: "default",
    pagination: {
-       data:   calendar.index,
+       data:   "calendar.index",
        size:   1,
-       alias:  calDate,
+       alias: "calDate",
    },
    permalink: "calendar/{{ calDate.year }}-{{ calDate.month }}/"
}

~~If that doesn't work, it might need "calendar/index" instead of period separated. 🤷~~

UPDATE: This worked for me locally:

---js
{
    pagination: {
        data:   "calendar.index",
        size:   1,
        alias:  "calDate",
    },
    permalink: "calendar/{{ calDate.year }}-{{ calDate.month }}/"
}
---

<pre><code>{{ calDate | dump | safe }}</code></pre>

Where my src/_data/calendar/index.js file looks like this:

module.exports = [
  {
    year: "2014",
    month: "09",
    posts: [
      {"title": "Lorem Ipsum"}
    ]
  },
  {
    year: "2021",
    month: "07",
    posts: [
      {title: "more stuff"}
    ]
  }
];

pdehaan avatar Jul 31 '22 20:07 pdehaan

And while I'm here, I guess one solution to the original question (trying to get the page scope in eleventyComputed when using ---js frontmatter, if I'm skimming correctly), is using the function form of eleventyComputed properties and then accessing it via data.page.url, like so:

---js
{
  pagination: {
    data: "calendar.index",
    size: 1,
    alias: "calDate",
  },
  eleventyComputed: {
    permalink: (data) => `calendar/${ data.calDate.year }-${ data.calDate.month}/`,
    title: (data) => `url = ${data.page.url}`
  }
}
---

<header>
  <h1>{{ title }}</h1>
</header>

LOG

[11ty] Writing www/calendar/2014-09/index.html from ./src/index.njk
[11ty] Writing www/calendar/2021-07/index.html from ./src/index.njk
[11ty] Wrote 2 files in 0.03 seconds (v1.0.1)

www/calendar/2014-09/index.html

<header>
  <h1>url = /calendar/2014-09/</h1>
</header>

www/calendar/2021-07/index.html

<header>
  <h1>url = /calendar/2021-07/</h1>
</header>

pdehaan avatar Jul 31 '22 23:07 pdehaan

@pdehaan Thanks for testing my problem! Unfortunately, I can’t replicate your success. :-(

I’m not sure, but I think it might have something to do with my using Pug as preferred template language. (The error stack trace seems to indicate so, anyway.)

Up till now, I’ve never had a problem with Pug. But this is also the first time I’m using this special “data from pages” functionality in Eleventy with Pug.

It’s clear from the docs that Nunjucks is the preferred template language of Eleventy, and that’s fine. I can give Nunjucks a try to get past this particular problem.

But it would be excellent if it were possible to get this to work in Pug instead of just using a workaround.

…Do you think you could get this to work with Pug, @pdehaan?

Zearin avatar Aug 01 '22 15:08 Zearin

Waitasec! I spoke too soon. I am getting successful compilation* with Pug using the following:

---js
{
    layout: "default",
    pagination: {
        data:   "calendar.index",
        size:   1,
        alias:  "calDate"
    },
    eleventyComputed: {
        permalink:  (data) => `calendar/${ data.calDate.year }-${ data.calDate.month}/`,
        title:      (data) => `url = ${data.page.url}`
    }
}
---

However, it is not generating all the pages like it’s supposed to. It is generating a single page at _site/calendar/index.html.

Zearin avatar Aug 01 '22 15:08 Zearin

I've never used Pug before (I prefer LiquidJS, Nunjucks, or 11ty templating), but I renamed index.njk to index.pug and it seemed to work in my simple testcase: https://github.com/pdehaan/11ty-1649


UPDATE: Actually, I see what you're saying. After renaming the template to .pug, it writes two files as www/index.html versus www/calendar/yyyy-mm/index.html when using .njk:

# ./src/index.pug writes two files named www/index.html
npm run build

> [email protected] build
> eleventy

[11ty] Writing www/index.html from ./src/index.pug
[11ty] Writing www/index.html from ./src/index.pug
[11ty] Wrote 2 files in 0.08 seconds (v1.0.1)
# ./src/index.njk writes two files with the correct permalink.
mv src/index.pug src/index.njk
npm run build

> [email protected] build
> eleventy

[11ty] Writing www/calendar/2014-09/index.html from ./src/index.njk
[11ty] Writing www/calendar/2021-07/index.html from ./src/index.njk
[11ty] Wrote 2 files in 0.04 seconds (v1.0.1)

pdehaan avatar Aug 01 '22 15:08 pdehaan

https://github.com/11ty/eleventy/issues/286 implies that I need to set the permalink in the front matter using something like this:

permalink: "| calendar/#{ calDate.year }-#{ calDate.month }/",
npm run build 

> [email protected] build
> eleventy

[11ty] Writing www/calendar/2014-09/index.html from ./src/index.pug
[11ty] Writing www/calendar/2021-07/index.html from ./src/index.pug
[11ty] Wrote 2 files in 0.14 seconds (v1.0.1)

pdehaan avatar Aug 01 '22 15:08 pdehaan

Okay. It’s iterating through the data, but it just keeps overwriting the same page. I got the following output from some of Eleventy’s variables....

pagination content

{
  data: 'calendar.index',
  size: 1,
  alias: 'calDate',
  items: [ mr [Temporal.PlainDate] {} ],
  pages: [
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {},
    mr [Temporal.PlainDate] {}
  ],
  page: {
    previous: mr [Temporal.PlainDate] {},
    next: mr [Temporal.PlainDate] {},
    first: mr [Temporal.PlainDate] {},
    last: mr [Temporal.PlainDate] {}
  },
  pageNumber: 18,
  previousPageLink: '/calendar/index.html',
  previous: '/calendar/index.html',
  nextPageLink: '/calendar/index.html',
  next: '/calendar/index.html',
  firstPageLink: '/calendar/index.html',
  lastPageLink: '/calendar/index.html',
  links: [
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html'
  ],
  pageLinks: [
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html',
    '/calendar/index.html', '/calendar/index.html'
  ],
  previousPageHref: '/calendar/',
  nextPageHref: '/calendar/',
  firstPageHref: '/calendar/',
  lastPageHref: '/calendar/',
  hrefs: [
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/',
    '/calendar/', '/calendar/'
  ],
  href: {
    previous: '/calendar/',
    next: '/calendar/',
    first: '/calendar/',
    last: '/calendar/'
  }
}

## `pageObj` content
```js
{
  date: 2022-07-28T17:57:58.529Z,
  inputPath: './_src/calendar.pug',
  fileSlug: 'calendar',
  filePathStem: '/calendar',
  outputFileExtension: 'html',
  url: '/<calendar/><2022-07/>',
  outputPath: '_site/calendar/index.html'
}

Zearin avatar Aug 01 '22 15:08 Zearin

Forgot to show the front matter that yielded the results from previous post…

Here it is:

---js
{
    layout: "default",
    pagination: {
        data:   "calendar.index",
        size:   1,
        alias:  "calDate"
    },
    eleventyComputed: {
        testOutput: (data) => `${ data.calDate }`,
        permalink:  (data) => `calendar/${ Temporal.PlainYearMonth.from( data.calDate ) }/`,
        pageObj:    (data) => `${ inspect(data.page) }`,
        pgn:        (data) => `${ inspect(data.pagination) }`
    }
}
---

The inspect() function is Node.js’ util.inspect().

Zearin avatar Aug 01 '22 16:08 Zearin

Solved! 🥳

For anyone else struggling with Pug and pagination, here is what worked for me:

---js
{
    layout: "default",
    pagination: {
        //    An array of `Temporal.PlainDate` instances
        //    (but you can use `Date` or anything else you want)
        data:   "calendar.index",
        size:   1,
        alias:  "calDate"
    },
    //    Apparently you can use the functional syntax outside of `eleventyComputed`
    //    if your front-matter is js (!!!)
    permalink:  (data) => `calendar/${ Temporal.PlainYearMonth.from( data.calDate ) }/`,
    eleventyComputed: {
        //    For debugging
        //   `inspect()` is alias of Node.js `util.inspect()`
        pageObj:    (data) => `${ inspect(data.page) }`,
        pgn:        (data) => `${ inspect(data.pagination) }`
    }
}
---

Zearin avatar Aug 01 '22 16:08 Zearin

@pdehaan Thank you! 🙏🏽 I was so discouraged before your help got me to build without errors again. I might have given up if not for your help, because it gave me hope.

@zachleat: I think this thread shows some issues that could be used to improve the official docs. Although I can’t take the lead on that, I am happy to contribute if you can tell me what to do.

Zearin avatar Aug 01 '22 16:08 Zearin