Skip to content

bug: @stencil/store stops re-rendering components when the store is created from globalScript code (since 4.33.0) #6798

Description

@CalinaCristian

Prerequisites

Stencil Version

4.43.5 (every release since 4.33.0 is affected, see below)

Current Behavior

If a project uses a globalScript and that script (or anything it imports) creates a @stencil/store store at module scope, components stop re-rendering on store changes in dist/www lazy builds. The store itself keeps working: values update, onChange/on('set') callbacks fire. Only the part that re-renders subscribed components is gone. There is no error or warning anywhere, which makes this really nasty to track down — in our app it shipped unnoticed and we only caught it because theme switching visibly broke.

What's happening, as far as I can tell:

Since 4.33.0 the client runtime imports globalStyles from @stencil/core/internal/app-globals (added in #6268, src/utils/shadow-root.ts). app-globals is the same generated module that wraps the user's globalScript, so this creates a module cycle:

client runtime -> app-globals -> globalScript -> ... -> @stencil/store -> '@stencil/core' (runtime)

Rollup resolves the cycle by emitting the runtime module last in the chunk. @stencil/store checks typeof getRenderingRef !== 'function' once, inside createStore(). When the store is created during evaluation of the globalScript import graph, getRenderingRef is still an unassigned hoisted var at that point, so the check fails and the store silently skips the renderer subscription.

In the emitted dev bundle you can see the order directly: the store package evaluates at ~2.7k into the chunk, the store is created at ~7.9k, and var getRenderingRef = () => renderingRef is only assigned at ~130k. On 4.32.0 and older the order is correct (runtime first) and everything works.

Two things that make it worse:

Expected Behavior

Components subscribed to a store re-render when the store changes, regardless of whether the store module happens to be reachable from the globalScript import graph. This worked up to and including 4.32.0.

System Info

      System: node 24.15.0
    Platform: darwin (25.5.0)
   CPU Model: Apple M4 Pro (14 cpus)
     Stencil: 4.43.5
  TypeScript: 5.8.3
      Rollup: 4.44.0
      Parse5: 7.2.1
      jQuery: 4.0.0-pre
      Terser: 5.37.0

Steps to Reproduce

The repro is ~40 lines: a store.ts with createStore at module scope (same pattern as the @stencil/store readme), a canonical default-exported globalScript that imports the store, and one component that renders store state.

  1. npm install && npm run build
  2. Serve www/ (e.g. npx serve www) and open it — the page has buttons and an expected-vs-actual explanation built in
  3. The label shows "ready", proving the globalScript ran
  4. Click "Increment store counter" a few times — the counter stays at 0
  5. Click "Force unrelated re-render (sets a prop)" — the counter jumps to the number of clicks, so the store updated the whole time and only the re-render is missing
  6. Check out the works-on-4.32 branch (identical code, @stencil/core pinned to 4.32.0), npm install && npm run build, repeat: the counter updates on every click

Code Reproduction URL

https://github.com/CalinaCristian/stencil-store-globalscript-repro (master = broken on 4.43.5, works-on-4.32 branch = same code on 4.32.0, working)

Additional Information

  • Timeline I verified against npm: internal/client has no app-globals reference up to 4.32.0, the import appears in 4.33.0 and is present in every release through 4.43.5. The 5.0.0-alpha line doesn't have it anymore (internal/app-globals is gone entirely there), so v5 seems to fix this as a side effect of the restructuring.
  • This looks like the dist-lazy sibling of bug: externalRuntime flag makes @stencil/store changes no trigger render #6040 / bug: Store changes doesn't trigger re-render of a component on dist-custom-elements build #4135, which were the same "store silently stops re-rendering" failure for dist-custom-elements, fixed in fix(dist-custom-elements): stop duplicate @stencil/core #6109.
  • Two possible fixes, either would do it:
    1. give globalStyles its own virtual module so the runtime never imports the globalScript graph (which appears to be what the v5 alphas already do), or
    2. in @stencil/store, resolve getRenderingRef/forceUpdate lazily inside the subscription handlers instead of once at createStore() time — by the time anything renders they're always assigned. (Happy to open that as a PR on stenciljs/store instead if you'd prefer the fix there.)
  • Our current workaround is a createStore wrapper that re-attaches an equivalent renderer subscription through the public store.use() API when it detects the built-in one was dropped.
  • Full disclosure: we used an AI assistant to help with the version bisecting and bundle analysis during this investigation, but the reproduction and all results above were verified by hand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bug: ValidatedThis PR or Issue is verified to be a bug within StencilStencil v5This is slated for Stencil v5 (Release date TBD)

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions