11ty / webc

Single File Web Components
MIT License
1.33k stars 38 forks source link

Computing data without render templates #164

Open bennypowers opened 1 year ago

bennypowers commented 1 year ago

Instigating nunjucks example:

{% set thingies = someComponentVar | expensivelyGenerateImages %}
<ul>{% for thingy in thingies %}
  <li data-index="{{thingies.getIndex(thingy)}}">
    <img alt="{{thingy.alt}}" src="{{thingy.src}}">
  </li>{% endfor %}
</ul>

If I have an expensive-but-private computation to do within (not without) a component, I'd like to do it in a script block. I don't want to use a render template though, because I prefer to use one templating language wherever possible

straw example:

<script type="module" webc:data>
export const thingies = await expensivelyGenerateImages(someCompVar);
</script>

<ul>
  <li webc:for="thingy of thingies" :data-index="thingies.getIndex(thingy)">
    <img :alt="thingy.alt" :src="thingy.src">
  </li>
</ul>

In the straw example, the webc:data script has access to and runs in the component scope, but you can easily imagine a case where you'd want to reference the computed private prop multiple times, and memoizing the calc function is just cumbersome.

<script type="module" webc:data>
export const likes = await getWebmentionLikes(page.url);
export const many = likes.length > 12;
</script>

<template webc:nokeep webc:if="likes.length">

<h2>Likes</h2>

<ul :class="`webmentions likes ${many ? 'many' : ''}`">
  <li webc:for="mention of likes" class="webmention like">
    <a target="_blank" rel="noopener" :href="mention.author.url">
      <img :src="mention.author.photo" :title="mention.author.name">
    </a>
  </li>
</ul>

</template>
steffans commented 1 year ago

Good idea, i was also looking for a way to compute data in a component. An other way would be to allow to access the component properties in the setup script scope, maybe using $props or webc.props. Similar to Astro.props

<script webc:setup>

const myComputed = $props.num + 1;

</script>
bennypowers commented 1 year ago

making the setup script (and everything else) a module would enable tla as well, and would cut down on magic / nonstandard behaviour (globals getting scoped to a single component, for example)

jaredcwhite commented 1 year ago

@steffans @bennypowers I was able to get something like this working, but it feels like a bit of a hack. Given input arr prop of [1,2,3]:

<script webc:type="js">
  globalThis.arrStr = `${arr.join(", ")}`
  "" // return a blank string
</script>

<p>Values: <span @text="arrStr">...</span></p>

renders:

<p>Values: <span>[1, 2, 3]</span></p>

You could also render inline with a script tag which is sort of weird but cool!

<p>Values: <span><script webc:type="js">`${arr.join(", ")}`</script></span></p>
jaredcwhite commented 1 year ago

OK, after poking around some more, maybe this is a more canonical approach…at least it feels less hacky:

<script webc:setup>
  function joinedArr(input) { 
    return input.join(", ");
  }
</script>

<p>Values: <span @text="joinedArr(arr)">...</span></p>
bennypowers commented 1 year ago

What about when joinedArray is an expensive async function that calls to network and disk resources?

You can make a new component, because props are lifted into promise space, but that seems overkill.

agarzola commented 1 year ago

I just ran into the issue of webc:setup missing access to attributes and props passed to the component. @jaredcwhite’s most recent solution (set up a function in webc:setup and call it from the component) is a reasonable workaround for our use case. Thanks for sharing that!