Developing against a local checkout #
When you want to hack on solid-stage and your app at the same time — try a
fix in the library, see it in your app, repeat — point your app at a local build
instead of the published package. This page covers that link: workflow and its
gotchas.
Everything here is link-only. Each step is a workaround for the fact that your app and the library are two separate installs. A normal consumer on the published package needs none of it — so when you switch back to the registry dependency, undo all of it (the dedupe, the cast). Each one is flagged below with when to remove it.
1. Point your app at the local package #
Use the link: protocol in your app's package.json, pointing at the package
directory (the thing that gets published — it resolves through the same
exports map and dist/ as the real package):
// your-app/package.json
{
"dependencies": {
"@dolphinspired/solid-stage": "link:../solid-stage/packages/solid-stage"
}
}
Then pnpm install. (If the two live in one monorepo/workspace, use
"workspace:*" instead — same effect, no relative path.)
2. Rebuild the library after every change #
The linked package serves its built dist/, not src/ — byte-for-byte what
npm would publish. So source edits don't reach your app until you rebuild:
cd ../solid-stage
pnpm --filter @dolphinspired/solid-stage build # vite build + tsc (.d.ts)
This is the trap that bites most often: a change to a .tsx or to the CSS /
tokens is invisible to your app until you rebuild, because the app consumes
dist/style.css and dist/*.js, never the source. If you're iterating fast,
pnpm --filter @dolphinspired/solid-stage exec vite build --watch rebuilds the
JS + CSS on save — but it skips the .d.ts step, so run the full build
again before you trust types.
3. Dedupe Solid in your app's Vite config #
The linked package resolves its own copy of solid-js. Two Solid instances
means broken reactivity — signals created in one don't track in the other, and
useContext comes back undefined. Force a single copy:
// vite.config.ts
export default defineConfig({
// link-only — drop it on the published dependency.
resolve: { dedupe: ['solid-js'] },
});
Your test runner needs the same dedupe for the same reason — see Testing a consumer.
4. If you use the icon preset, cast stageIcons() #
stageIcons() returns a Vite Plugin. Under a link there are
two vite copies (your app's, plus the one solid-stage uses to build
itself), so the preset's Plugin type and your config's are distinct — their
union in plugins[] overflows TypeScript's comparison depth
(TS2321: Excessive stack depth comparing types). Cast it to your local
Plugin:
import type { Plugin } from 'vite';
export default defineConfig({
plugins: [solid(), stageIcons() as Plugin], // link-only cast
});
A published consumer has a single vite copy and writes plain stageIcons().
Cheat sheet #
| Workaround | Why it's needed under a link | Remove when |
|---|---|---|
link: (or workspace:*) dependency |
point the app at the local build | back on the published dependency |
rebuild dist after every change |
the linked package serves dist/, not src/ |
always (inherent to linking) |
resolve.dedupe: ['solid-js'] |
one Solid runtime, not two | published dependency (single copy) |
stageIcons() as Plugin |
two vite copies → clashing Plugin types |
published dependency (single copy) |
When you publish or switch back to the registry dependency, delete the dedupe and the cast — on a single install they're no-ops at best and misleading at worst.