Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web components (custom-element) support: Conversion from .astro component to createComponent break when an anchor tag is introduced #1059

Closed
1 task done
schalkneethling opened this issue Dec 8, 2024 · 3 comments
Labels
needs triage Issue needs to be triaged

Comments

@schalkneethling
Copy link

schalkneethling commented Dec 8, 2024

Astro Info

I ran into an interesting bug (I believe it to be one) when using web components with Astro. After reducing my test case I can narrow it down to be related to custom elements.

Astro                    v5.0.3
Node                     v22.8.0
System                   macOS (arm64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

After reducing the test case to the following:

<ul class="card-list">
	{
	users.length &&
		users.map((user: User) => (
		<li>
			<article class="user-card">
			<h2 class="user-card-title">
				{user.firstName} {user.lastName}
			</h2>

			<img
				class="user-card-avatar"
				src={user.avatarURL}
				height="150"
				width="150"
				alt=""
			/>

			<span class="user-card-role">{user.role}</span>

			<span class="user-card-email">
				<span class="visually-hidden">Email:</span>
				<a href={`mailto:${user.email}`}>{user.email}</a>
			</span>
			</article>
		</li>
		))
	}
</ul>

The build works without any trouble. When I wrap this in a template element as follows, everything is still A-OK:

<template shadowrootmode="open">
  <style set:html={style}></style>
  <ul class="card-list">
    {
      users.length &&
        users.map((user: User) => (
          <li>
            // same as above - omitting for brevity
          </li>
        ))
    }
  </ul>
</template>

If I know add my wrapper custom element:

<nimbus-team>
  <template shadowrootmode="open">
    <style set:html={style}></style>
    <ul class="card-list">
      {
        users.length &&
          users.map((user: User) => (
            <li>
              // same as above - omitting for brevity
            </li>
          ))
      }
    </ul>
  </template>
</nimbus-team>

The build fails with:

Stack Trace
20:02:31 ▶ src/pages/index.astro
20:02:31   └─ /index.htmlfile:///Users/schalkneethling/dev/opensource/neo-shadow/dist/pages/index.astro.mjs?time=1733680951649:18
  return renderTemplate`${renderComponent($$result, "nimbus-team", "nimbus-team", {}, { "default": () => renderTemplate` <template shadowrootmode="open"> <style>${unescapeHTML(style)}</style> ${maybeRenderHead()}<ul class="card-list"> ${users.length && users.map((user2) => renderTemplate`<li> <article class="user-card"> <h2 class="user-card-title"> ${user2.firstName} ${user2.lastName} </h2> <img class="user-card-avatar"${addAttribute(user2.avatarURL, "src")} height="150" width="150" alt=""> <span class="user-card-role">${user2.role}</span> <span class="user-card-email"> <span class="visually-hidden">Email:</span> <a${addAttribute(`mailto:${user2.email}`, "href")}>${user2.email}</a> </span> </article> </li>`)} </ul> </template><a${addAttribute(`mailto:${user.email}`, "href")}></a>` })}<a${addAttribute(`mailto:${user.email}`, "href")}> ${renderScript($$result, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro?astro&type=script&index=0&lang.ts")}</a>`;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           ^

ReferenceError: user is not defined
    at default (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/pages/index.astro.mjs?time=1733680951649:18:764)
    at Object.render (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:788:70)
    at renderSlotToString (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:815:24)
    at file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:824:27
    at Array.map (<anonymous>)
    at renderSlots (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:823:29)
    at renderFrameworkComponent (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:1228:48)
    at renderComponent (file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/chunks/astro/server_57EaB_XO.mjs:1512:16)
    at file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/pages/index.astro.mjs?time=1733680951649:18:27
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

Node.js v22.8.0

After referring to the generated intermediary dist/pages/index.astro.mjs one can see that the conversion from the Astro component to JS is going awry:

const $$NimbusTeam = createComponent(async ($$result, $$props, $$slots) => {
  const response = await fetch(
    "https://fictionalfolks.netlify.app/.netlify/functions/users?count=2"
  );
  let users;
  if (response.ok) {
    users = await response.json();
  }
  return renderTemplate`${renderComponent($$result, "nimbus-team", "nimbus-team", {}, { "default": () => renderTemplate` <template shadowrootmode="open"> <style>${unescapeHTML(style)}</style> ${maybeRenderHead()}<ul class="card-list"> ${users.length && users.map((user2) => renderTemplate`<li> <article class="user-card"> <h2 class="user-card-title"> ${user2.firstName} ${user2.lastName} </h2> <img class="user-card-avatar"${addAttribute(user2.avatarURL, "src")} height="150" width="150" alt=""> <span class="user-card-role">${user2.role}</span> <span class="user-card-email"> <span class="visually-hidden">Email:</span> <a${addAttribute(`mailto:${user2.email}`, "href")}>${user2.email}</a> </span> </article> </li>`)} </ul> </template><a${addAttribute(`mailto:${user.email}`, "href")}></a>` })}<a${addAttribute(`mailto:${user.email}`, "href")}> ${renderScript($$result, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro?astro&type=script&index=0&lang.ts")}</a>`;
}, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro", void 0);

A few standout items for me:

return renderTemplate`${renderComponent($$result, "nimbus-team", "nimbus-team", {}, { "default": () =>

nimbus-team is repeated twice in the signature. Having not looked at the signature, it could be that this is as expected, but it reads a little curious to me.

${users.length && users.map((user2) => renderTemplate

The use of user2 here instead of user as defined in the Astro component seems odd and is the cause of the user is undefined error a bit later in this snippet.

<a${addAttribute(`mailto:${user2.email}`, "href")}>${user2.email}</a> </span> </article> </li>`)} </ul> </template>

The renderTemplate function call seems to end abruptly after the last closing list item even though it starts from <template shadowrootmode="open">.

The anchor element <a${addAttribute(mailto:${user2.email}, "href")}>${user2.email}</a> from a little earlier in the snippet is replicated twice more and in fact, the last of these wraps the script element.

<a${addAttribute(`mailto:${user.email}`, "href")}></a>` })}<a${addAttribute(`mailto:${user.email}`, "href")}> ${renderScript($$result, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro?astro&type=script&index=0&lang.ts")}</a>

What is also curious here is that:

  1. These are outside of the template _and
  2. the content ${user2.email} of the anchor element is removed.

This is truly peculiar and I would be happy to assist further in tracking down and fixing whatever is causing this. You can see the full Astro component at the following URL:

https://github.com/schalkneethling/neo-shadow/blob/main/src/components/NimbusTeam.astro

What's the expected result?

The build succeeds and produces the expected HTML.

Link to Minimal Reproducible Example

https://github.com/schalkneethling/neo-shadow/blob/main/src/components/NimbusTeam.astro

Participation

  • I am willing to submit a pull request for this issue.
@github-actions github-actions bot added the needs triage Issue needs to be triaged label Dec 8, 2024
@schalkneethling
Copy link
Author

BTW, if I do not use declarative ShadowDOM and do not nest the custom element and template element I still get the error, but the stack trace is less detailed:

Astro

<nimbus-team></nimbus-team>

<template>
  <style set:html={style}></style>
  <ul class="card-list">
    {
      users.length &&
        users.map((user: User) => (
          <li>
            <article class="user-card">
              <h2 class="user-card-title">
                {user.firstName} {user.lastName}
              </h2>

              <img
                class="user-card-avatar"
                src={user.avatarURL}
                height="150"
                width="150"
                alt=""
              />

              <span class="user-card-role">{user.role}</span>

              <span class="user-card-email">
                <span class="visually-hidden">Email:</span>
                <a href={`mailto:${user.email}`}>{user.email}</a>
              </span>
            </article>
          </li>
        ))
    }
  </ul>
</template>

Stack Trace

20:18:09 ▶ src/pages/index.astro
20:18:09   └─ /index.htmluser is not defined
  Stack trace:
    at file:///Users/schalkneethling/dev/opensource/neo-shadow/dist/pages/index.astro.mjs?time=1733681889426:18:708

createComponent for the $$NimbusTeam Astro component

const $$NimbusTeam = createComponent(async ($$result, $$props, $$slots) => {
  const response = await fetch(
    "https://fictionalfolks.netlify.app/.netlify/functions/users?count=2"
  );
  let users;
  if (response.ok) {
    users = await response.json();
  }
  return renderTemplate`${renderComponent($$result, "nimbus-team", "nimbus-team", {})} <template> <style>${unescapeHTML(style)}</style> ${maybeRenderHead()}<ul class="card-list"> ${users.length && users.map((user2) => renderTemplate`<li> <article class="user-card"> <h2 class="user-card-title"> ${user2.firstName} ${user2.lastName} </h2> <img class="user-card-avatar"${addAttribute(user2.avatarURL, "src")} height="150" width="150" alt=""> <span class="user-card-role">${user2.role}</span> <span class="user-card-email"> <span class="visually-hidden">Email:</span> <a${addAttribute(`mailto:${user2.email}`, "href")}>${user2.email}</a> </span> </article> </li>`)} </ul> </template><a${addAttribute(`mailto:${user.email}`, "href")}> ${renderScript($$result, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro?astro&type=script&index=0&lang.ts")}</a>`;
}, "/Users/schalkneethling/dev/opensource/neo-shadow/src/components/NimbusTeam.astro", void 0);

@schalkneethling
Copy link
Author

UPDATE: I was able to "fix" this by changing the data structure and looping over the social platforms to create the list items. I have "fix" in quotations because I am still unsure why having the list items hard-coded inside the template inside the custom element trips up the (I think) compiler so badly. It is most likely still worth investigating (happy to help).

Here is the part that changed:

<ul class="user-card-social">
  {Object.entries(user.social).map(([key, value]) => (
    <li>
      <a
        class={`icon icon-social-${key}`}
        href={value.url}
        target="_blank"
        rel="noopener noreferrer"
      >
        <span class="visually-hidden">
          Follow {user.firstName} on {value.name}
        </span>
      </a>
    </li>
  ))}
</ul>

The entire component can be reviewed here and the commit with the change(s) can be reviewed here.

@ascorbic
Copy link

This is a compiler bug. The most minimal repro I could do is this:

---
const users = []
---
<nimbus-team>
  <template>
      {
          users.map((user) => (
<a href={user}>{user}</a>
          ))
      }
  </template>
</nimbus-team>

That is compiled to:

import {
  Fragment,
  render as $$render,
  createAstro as $$createAstro,
  createComponent as $$createComponent,
  renderComponent as $$renderComponent,
  renderHead as $$renderHead,
  maybeRenderHead as $$maybeRenderHead,
  unescapeHTML as $$unescapeHTML,
  renderSlot as $$renderSlot,
  mergeSlots as $$mergeSlots,
  addAttribute as $$addAttribute,
  spreadAttributes as $$spreadAttributes,
  defineStyleVars as $$defineStyleVars,
  defineScriptVars as $$defineScriptVars,
  renderTransition as $$renderTransition,
  createTransitionScope as $$createTransitionScope,
  
} from "astro/runtime/server/index.js";

const $$Astro = $$createAstro();
const Astro = $$Astro;
const $$Card = $$createComponent(async ($$result, $$props, $$slots) => {
const Astro = $$result.createAstro($$Astro, $$props, $$slots);
Astro.self = $$Card;

const users = []


return $$render`${$$renderComponent($$result,'nimbus-team','nimbus-team',{},({"default": () => $$render`
  <template>
      ${
          users.map((user) => (
$$render`${$$maybeRenderHead($$result)}<a${$$addAttribute(user, "href")}>${user}</a>`
          ))
      }
  </template><a${$$addAttribute(user, "href")}>
</a>`,}))}`;
}, 'Card.astro', undefined);
export default $$Card;

Notice the extra <a${$$addAttribute(user, "href")}> outside of the template. I need to check if there's an existing issue on the compiler repo, but I'll move it over there.

@ascorbic ascorbic transferred this issue from withastro/astro Jan 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage Issue needs to be triaged
Projects
None yet
Development

No branches or pull requests

2 participants