Skip to content

fix(web-components): load both web-components.js AND account.js bundles#386

Merged
hta218 merged 1 commit into
mainfrom
fix/storefront-web-components-both-bundles
May 14, 2026
Merged

fix(web-components): load both web-components.js AND account.js bundles#386
hta218 merged 1 commit into
mainfrom
fix/storefront-web-components-both-bundles

Conversation

@paul-phan
Copy link
Copy Markdown
Member

What

Add the web-components.js script tag alongside web-components/account.js in app/root.tsx. Switch both from async to defer so they execute in document order.

+ <script
+   type="module"
+   src="https://cdn.shopify.com/storefront/web-components.js"
+   defer
+   nonce={nonce}
+ />
  <script
    type="module"
    src="https://cdn.shopify.com/storefront/web-components/account.js"
-   async
+   defer
    nonce={nonce}
  />

Why

The header's <ShopifyAccountButton> renders <shopify-account> nested inside <shopify-store>, but only the account bundle was loaded, producing this console warning on every page load and rendering the empty <span slot="signed-out-avatar"> fallback instead of the account widget:

[shopify-account] <shopify-store> custom element is not registered.
  Ensure the storefront-components bundle is loaded so
  <shopify-account> can fetch data.

Bundle analysis (verified by fetching + tracing both bundles)

customElements.define uses template-literal interpolation \shopify-${t}`, so literal-string grep for "shopify-store"misses the registrations. Tracing the registration helperI(name, ctor)` reveals:

Bundle Size Registers
cdn.shopify.com/storefront/web-components.js 92 KB shopify-store, shopify-cart, shopify-catalog, shopify-context, shopify-data, shopify-list-context, shopify-media, shopify-money, shopify-variant-selector
cdn.shopify.com/storefront/web-components/account.js 31 KB shopify-account, shopify-customer-account-data, shopify-render, shopify-element-wrapper

The account bundle DOES dynamic-import a chunked ./account/store-*.js (visible in the bundle source as the last import() call), but the chunk arrives after <shopify-account>'s connectedCallback runs, hitting a race. The connectedCallback calls customElements.get("shopify-store"), finds nothing, emits the warning, and bails.

This corrects my earlier (closed) PR #385 which claimed web-components.js was an umbrella superset \u2014 it isn't. The two bundles are disjoint feature sets, and <shopify-account> inside <shopify-store> needs both.

Why defer instead of async

Module scripts are deferred by default. Both defer and the implicit deferred behaviour of module scripts preserve document order at execution time. async does NOT preserve order \u2014 if account.js happens to finish downloading first and executes before web-components.js, the same warning fires. The explicit defer is belt-and-suspenders to encode the intent.

Behaviour change

  • Before: <shopify-account> connectedCallback warning on every page render. Login widget shows the slot-provided <UserIcon> fallback indefinitely; user can't see signed-in avatar or account dropdown.
  • After: No warning. <shopify-store> is registered first, then <shopify-account> finds its parent on connectedCallback and renders the full account widget.

Verification

  1. Visit any page on the dev server, open DevTools \u2192 Console.
  2. Filter to "shopify" \u2014 the [shopify-account] <shopify-store> custom element is not registered warning should be absent.
  3. DevTools \u2192 Network \u2192 filter to "web-components" \u2014 both bundles should load with HTTP 200.
  4. Sign in via the account widget \u2014 the widget should render the account dropdown rather than the static <UserIcon> fallback.

Net bundle cost

+92 KB gzipped (one new module script). Negligible on the critical path because both scripts are defer \u2014 they don't block HTML parsing or initial render. Net: a one-time cost for the warning to go away and the account widget to actually work.

The header's <ShopifyAccountButton> renders <shopify-account> nested
inside <shopify-store>, but only the account bundle was loaded. The
account bundle does not register <shopify-store> synchronously \u2014 it
dynamic-imports a chunked store-*.js whose execution arrives AFTER
<shopify-account>'s connectedCallback fires, emitting on every page
load:

  [shopify-account] <shopify-store> custom element is not registered.
    Ensure the storefront-components bundle is loaded so
    <shopify-account> can fetch data.

Verified bundle map by fetching each bundle and tracing the
registration helper (calls customElements.define via template literal
`shopify-${t}`, so literal-string grep misses them):

  web-components.js (92KB)
    shopify-store, shopify-cart, shopify-catalog, shopify-context,
    shopify-data, shopify-list-context, shopify-media, shopify-money,
    shopify-variant-selector

  web-components/account.js (31KB)
    shopify-account, shopify-customer-account-data, shopify-render,
    shopify-element-wrapper
    (NB: does NOT register shopify-store synchronously)

Fix: load BOTH bundles in document order. Switch from async to defer \u2014
module scripts are deferred by default and execute in declaration
order with defer, which is required (if account.js runs before
web-components.js, the same warning fires).
@hta218 hta218 self-requested a review May 14, 2026 02:30
@hta218 hta218 merged commit 300bad4 into main May 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants