bug: @stencil/store stops re-rendering components when the store is created from globalScript code (since 4.33.0)
Prerequisites
- I have read the Contributing Guidelines.
- I agree to follow the Code of Conduct.
- I have searched for existing issues that already report this problem, without success.
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:
- It happens even with no
globalStyleconfigured.BUILDflags are runtime values in the emitted chunk, so the import isn't tree-shaken. extras: { addGlobalStyleToComponents: false }(#6292) doesn't help — it empties the styles string but the import edge stays.
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.0Steps 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.
npm install && npm run build- Serve
www/(e.g.npx serve www) and open it — the page has buttons and an expected-vs-actual explanation built in - The label shows "ready", proving the globalScript ran
- Click "Increment store counter" a few times — the counter stays at 0
- 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
- Check out the
works-on-4.32branch (identical code,@stencil/corepinned 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/clienthas noapp-globalsreference 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-globalsis gone entirely there), so v5 seems to fix this as a side effect of the restructuring. - This looks like the
dist-lazysibling of #6040 / #4135, which were the same "store silently stops re-rendering" failure fordist-custom-elements, fixed in #6109. - Two possible fixes, either would do it:
- give
globalStylesits own virtual module so the runtime never imports the globalScript graph (which appears to be what the v5 alphas already do), or - in
@stencil/store, resolvegetRenderingRef/forceUpdatelazily inside the subscription handlers instead of once atcreateStore()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.)
- give
- Our current workaround is a
createStorewrapper that re-attaches an equivalent renderer subscription through the publicstore.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.
Source: stenciljs/core