Computing data without render templates
bennypowers opened this issue · 6 comments
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>
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>
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)
@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>
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>
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.
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!