11ty / eleventy-plugin-webc

Adds support for WebC *.webc files to Eleventy
https://www.11ty.dev/docs/languages/webc/
119 stars 10 forks source link

Stale data from cascade in webc:setup #69

Open darthmall opened 1 year ago

darthmall commented 1 year ago

I can access the full data cascade from inside <script webc:setup>, but page-level data is not updated. My guess is that webc:setup is run once per-component the first time it’s loaded and then never again. Inside of Eleventy, this is the first page that’s rendered with that component. This seems reasonable to me, except that you can access page-level data from webc:setup, but that data never changes. I would expect that either the $data is kept up-to-date for each page, or that access to data is somehow restricted to just global data in Eleventy.

Example

Here’s a simple example of how this can be a little weird.

Input Files

my-title.webc

<script webc:setup>
  const myTitle = $data.title
</script>
<dl>
  <dt>Title</dt>
  <dd @text="$data.title"></dd>
  <dt>My Title</dt>
  <dd @text="myTitle"></dd>
</dl>

a.webc

---
title: Page A
---
<my-title></my-title>

b.webc

---
title: Page B
---
<my-title></my-title>

Output

a/index.html

<dl>
  <dt>Title</dt>
  <dd>Page A</dd>
  <dt>My Title</dt>
  <dd>Page A</dd>
</dl>

b/index.html

<dl>
  <dt>Title</dt>
  <dd>Page B</dd>
  <dt>My Title</dt>
  <dd>Page A</dd>
</dl>

Expected Output

I would expect that b/index.html has “Page B” for both the $data.title and the myTitle variables. Or, I think it would be best if trying to access data that changes from page-to-page in webc:setup throws an error so people know they can’t do something like this.

Motivation

The actual use that motivated this was me trying to dynamically set a <title> attribute for a site.

site-meta.webc

<script webc:setup>
  const metaTitle = $data.title
    ? `${$data.title} - ${$data.site.title}`
    : $data.site.title;
</script>

<title @text="metaTitle"></title>
unonweb commented 1 year ago

I'm having the same issue. This is why I dropped .webc as layout format again and I got back to .11ty.js. In opposition to .webc the 11ty.js layouts are evaluated for every template using them.

CanIGetaPR commented 1 year ago

Yes this is a bug. I was about to create an issue named

Functions in webc:setup are given the incorrect page context

I have a footer components which lives on every page. The problem is the footer always thinks it's on one particular page for all instances.

It doesn't matter if I use this.page or $data.page or just page, it's always the same.

My footer-bar.webc component looks like this:

<script webc:setup>
    function getGitLabLink() {
        console.log("Get gitlab link function called on page " + page.inputPath);
        let repo_url = "https://gitlab.com/-/tree/master";
        let relativeFilePath = page.inputPath;
        relativeFilePath.startsWith(".") ? relativeFilePath.slice(1) : relativeFilePath;
        return repo_url + relativeFilePath;
    }
</script>

<p id="contribute-link">Contribute to this page on <a :href="getGitLabLink()">GitLab</a></p>

The console output looks like this:

Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc
Get gitlab link function called on page ./src/pawn.webc

It looks like the function responsible for providing the page context is here.

https://github.com/11ty/eleventy-plugin-webc/blob/cde696d83046ca23258b862ab268b3cc64e19a19/src/eleventyWebcTemplate.js#L20

Perhaps one of us can figure out how to solve this issue as it's a pretty serious flaw preventing web components from being dynamic.

CanIGetaPR commented 1 year ago

I made a pull request to fix:

https://github.com/11ty/webc/pull/176

hasanhaja commented 1 year ago

I think I have a better solution! I think this staleness issue can also be avoided if you use pure functions in webc:setup and you pass in the content to the function in the markup.

<script webc:setup>
  // This goes stale and doesn't recompute every time
  const impure = () => this.content.toUpperCase();

  // Since this is a pure function, it gets called with the right data every time
  const pure = (str) => str.toUpperCase();
</script>

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <meta name="generator" :content="eleventy.generator">
    <title @html="this.title ? `@hasanhaja | ${this.title}` : '@hasanhaja'"></title>

    <style @raw="getBundle('css')" webc:keep></style>
    <script @raw="getBundle('js')" webc:keep></script>
  </head>
  <body>
    <main>
      <section>
        <h2>Impure</h2>
        <p @html="impure()"></p>
      </section>
      <section>
        <h2>Pure</h2>
        <p @html="pure(this.content)"></p>
      </section>
    </main>
    <footer>
      <p>
        Copyright @hasanhaja 2023
      </p>
    </footer>
  </body>
</html>

I need to try this with my project still where I also had this issue, but here's the repo to the code snippet if you want to play with it: https://github.com/hasanhaja/webc-data-cascade

Edit: It works :star_struck: