Prerequisites
Stencil Version
5.0.0-beta.7
Current Behavior
We have a component library that uses signals and has @stencil/core as a peer dependency. When we link it into an app to work on the two together, components in the app render once with the right data and then never update again. Nothing throws and nothing is logged.
Linking is what breaks it. Installing the same library from npm works, so this passes CI and works in production, and only the people developing the library see it.
The cause is that linking leaves two copies of @preact/signals-core in the project. @stencil/core/signals does not implement the signal functions, it re-exports them:
import { batch, computed, effect, signal, untracked } from "@preact/signals-core";
The linked library resolves @stencil/core from its own node_modules, and that copy uses its own @preact/signals-core. The app uses the copy in its own tree. A computed made by one copy cannot see a signal made by the other.
npm run test:issue4 shows it with no components involved:
✓ the two copies are separate
✓ a computed sees a signal from its own copy
× a computed does not see a signal from the other copy
✓ the signal still notifies its own subscribers
The failing test sets the source to 5 and the computed still reads 2.
That last passing test is why this took us so long to find. The signal keeps working. It notifies its subscribers, and every value we inspected in the running app was correct. Only the screen was stale, so we spent a long time looking for a bug in our own code.
Expected Behavior
Two copies should either share one set of signals, or there should be a way to stop the duplication happening.
dist/signals/index.js exports a symbol that nothing reads:
const STENCIL_SIGNALS_SYMBOL = Symbol.for("stencil.signals");
Symbol.for is the usual way to let two copies of a library share one instance, so this looks meant for the problem. Using it would fix this without any app having to change.
Failing that, the Stencil config could expose rolldown's resolve.symlinks. Rolldown's own documentation describes the case:
Whether to resolve symlinks to their symlinked location. When enabled, symlinked resources are resolved to their real path, not their symlinked location. Note that this may cause module resolution to fail when using tools that symlink packages (like npm link). Default true
System Info
System: node 24.18.1
Platform: darwin (25.6.0)
CPU Model: Apple M4 (10 cpus)
Compiler: node_modules/@stencil/core/dist/compiler/index.mjs
Build: 1788951557
Stencil: 5.0.0-beta.7
TypeScript: 5.9.3
Rolldown: 1.2.8
Terser: 5.51.2
Steps to Reproduce
npm install
npm run test:issue4 — 1 of 4 tests fails
The second copy is installed under an alias so that one npm install is enough:
"@preact/signals-core-copy": "npm:@preact/signals-core@1.14.4"
It is imported by file path, because the alias keeps its original name in package.json and importing it by name lets the bundler merge the two back into one.
Installing a second @stencil/core does not reproduce this. npm then installs only one @preact/signals-core and both copies share it.
Code Reproduction URL
https://github.com/jgroth/stencil-5-signals/tree/main/src/issue4-duplicate-copies
Additional Information
No response
Prerequisites
Stencil Version
5.0.0-beta.7
Current Behavior
We have a component library that uses signals and has
@stencil/coreas a peer dependency. When we link it into an app to work on the two together, components in the app render once with the right data and then never update again. Nothing throws and nothing is logged.Linking is what breaks it. Installing the same library from npm works, so this passes CI and works in production, and only the people developing the library see it.
The cause is that linking leaves two copies of
@preact/signals-corein the project.@stencil/core/signalsdoes not implement the signal functions, it re-exports them:The linked library resolves
@stencil/corefrom its ownnode_modules, and that copy uses its own@preact/signals-core. The app uses the copy in its own tree. Acomputedmade by one copy cannot see a signal made by the other.npm run test:issue4shows it with no components involved:The failing test sets the source to
5and the computed still reads2.That last passing test is why this took us so long to find. The signal keeps working. It notifies its subscribers, and every value we inspected in the running app was correct. Only the screen was stale, so we spent a long time looking for a bug in our own code.
Expected Behavior
Two copies should either share one set of signals, or there should be a way to stop the duplication happening.
dist/signals/index.jsexports a symbol that nothing reads:Symbol.foris the usual way to let two copies of a library share one instance, so this looks meant for the problem. Using it would fix this without any app having to change.Failing that, the Stencil config could expose rolldown's
resolve.symlinks. Rolldown's own documentation describes the case:System Info
System: node 24.18.1 Platform: darwin (25.6.0) CPU Model: Apple M4 (10 cpus) Compiler: node_modules/@stencil/core/dist/compiler/index.mjs Build: 1788951557 Stencil: 5.0.0-beta.7 TypeScript: 5.9.3 Rolldown: 1.2.8 Terser: 5.51.2Steps to Reproduce
npm installnpm run test:issue4— 1 of 4 tests failsThe second copy is installed under an alias so that one
npm installis enough:It is imported by file path, because the alias keeps its original name in
package.jsonand importing it by name lets the bundler merge the two back into one.Installing a second
@stencil/coredoes not reproduce this. npm then installs only one@preact/signals-coreand both copies share it.Code Reproduction URL
https://github.com/jgroth/stencil-5-signals/tree/main/src/issue4-duplicate-copies
Additional Information
No response