# 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): ```jsonc // 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: ```sh 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: ```ts // 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](./testing.md). ## 4. If you use the icon preset, cast `stageIcons()` [`stageIcons()`](./icons.md) 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`: ```ts 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.