Skip to Content
How it works

How it works

Snapds is built around a small, fast pipeline that runs entirely inside the extension host — no runtime dependencies are added to your project.

Resolution

To read a package’s types, Snapds first has to find it on disk. It uses a multi-strategy resolver so it works in flat installs, nested node_modules, and monorepos alike:

Upward node_modules walk

Starting at the workspace root, it checks {dir}/node_modules/{pkg} and walks up parent directories to the filesystem root.

require.resolve

Falls back to Node’s resolver (require.resolve('{pkg}', { paths: [root] })).

Searches across the workspace for hoisted or nested deps.

Local workspace package

Finally scans every package.json (excluding node_modules) and matches by name, so locally-built workspace packages resolve too.

Local component sources. In-repo design systems (shadcn or your own folder) skip node_modules entirely: Snapds reads the folder’s source .tsx directly, resolves the folder from a components.json alias (or a folder you pick) via tsconfig paths, and injects each component from its per-file path alias (e.g. @/components/ui/button) rather than a package name. Their cache is keyed by a content signature (file mtimes), and a file watcher re-indexes on edit.

In a monorepo, every sub-app’s components.json is detected independently, so the same @/* alias resolves to each app’s own folder — sources stay distinct (keyed by their workspace-relative folder) and never collide.

Monorepo version resolution

In monorepos where the same package is installed at different versions across different apps, Snapds resolves the right version for the file you’re editing rather than always using the hoisted root copy.

On startup Snapds discovers every installation of each registered package across the workspace (via findFiles) and indexes them by version, sorted highest-semver-first.

When the active editor changes, Snapds walks up from the focused file’s directory to find the nearest node_modules/{pkg} directory. That installation — and its cached component schema — is shown in the Component Properties panel.

If no installation is found along the file’s path (e.g. a shared config file at the repo root), Snapds falls back to the highest semver found in the workspace. The props panel header shows an auto badge when the version was inferred this way, and a version dropdown when more than one installation exists so you can pin a specific version manually.

When the package is not listed in the focused file’s nearest package.json, an Add to this app button appears. Clicking it writes the dependency into that package.json (under dependencies) and shows a reminder to run pnpm install.

Introspection

Once resolved, Snapds parses the package’s typings entry with react-docgen-typescript plus the TypeScript compiler API. For each component it extracts:

  • Props — name, type (enum, string, boolean, number, function, ReactNode), the raw TS type, whether it’s required, the default value, and the JSDoc description.
  • Enum values — so the props panel can render selects instead of free text.
  • Polymorphic components — a compiler pass enumerates exported value components that docgen alone misses (e.g. as-style components).
  • forwardRef components — for components typed as ForwardRefExoticComponent<Omit<Props, "ref">> (common in libraries like Radix UI), a compiler pass reads the Props interface directly. Props a component inherits from sibling packages are kept — only standard DOM/React attributes, and props declared in interfaces JSDoc-tagged @private/ @internal, are filtered out as noise.

Components are filtered to those whose display name starts with an uppercase letter and are not tagged @internal or listed in your config’s ignore.

Caching

Introspection is cached in the extension’s global state under a key of the form:

ds.cache.v{schemaVersion}.{name}@{version}[@{configMtimeMs}]

Key points:

  • version is read live from node_modules/{pkg}/package.json at cache time, not from your saved settings. This means running pnpm update invalidates the cache automatically — no manual cache clear needed.
  • Each installed version has its own cache entry. In a monorepo with v1.2.0 in one app and v2.1.0 in another, both entries coexist. Switching the version selector in the props panel hits a warm cache immediately after startup.
  • configMtimeMs is appended when a snapds.config.json or .snapds.json exists. Editing it invalidates all entries for that workspace.
  • User overrides are stored separately and applied after the cache, so tweaking them never forces a re-parse.

Re-opening the gallery is instant; the cache only rebuilds when one of those inputs actually changes.

Code generation

Dropping a component produces a bare-specifier import and JSX:

import { Button } from '@acme/ui';

New imports are merged into an existing statement for the same package when one exists — including multi-line, Prettier-formatted imports — and otherwise inserted after the last import in the file, on its own line.

Webviews

The gallery, props panel, and settings are native VS Code webviews, so the UI matches your editor theme and stays fast even on large monorepos thanks to differential updates.

Last updated on