ReactWire
See the wiring of a React app inside VS Code: where React is mounted, which component renders which,
and where a prop or a context travels. Click any node to open the component.
ReactWire analyses the source statically with the TypeScript compiler (through ts-morph). No running
app, no backend, nothing leaves your machine.
What you get
- Component tree growing from the mount point (the
createRoot(...).render(<App />) call), laid
out in straight levels like a house of cards: each level is one centred row, every component sits
at its distance from the mount, and children line up under their parent regardless of the folder
they live in. Bottom-to-top by default; switch to top-down or left-to-right in the toolbar. Node
size grows with the number of components underneath it.
- Only components and render edges. Props and contexts are not drawn; they are the job of the
search box.
- Two searches: the first box finds components by name, the second every component that
touches a prop, context or hook with that name. Matches light up in lilac, a colour that is
never one of the tags, and everything else fades, edges included, until you select a match:
then its edges light up and its parents and children get a dashed outline, so
only the matched components and the edges between them stay visible. In the prop search the
name carries a mark:
(in) the component receives it (own prop, consumed context), (out) it
passes it down (prop on a child, provided context), (in+out) both. The match is exact and case-insensitive;
* is a wildcard (App*, *Provider, *price*) and the Aa button makes both
case-sensitive. The chips in the details panel fill the prop search with one click.
- Colour tags. Select components (shift-click adds to the selection) and pick one of five
colours in the Colour row right under the name in the details panel, or press
1–5 (0
puts them back to blue). The node and its edges take that colour. The Colours tab of the
side panel lists the default blue and the five colours, each with a name you can edit (pages,
to refactor…) and its toggles: Show off draws its components disabled in grey, Only
keeps just the pressed colours on screen (press several to combine, press again to release),
Remove takes a colour off every component, back to blue. Focus shown colours keeps the
components of the visible colours with their ancestors and descendants, like Focus does for a
selection, and stays lit until you press it again. Tags are remembered per folder and survive a
refresh: a tagged component that moved to another file keeps its colour as long as its name is
unique.
- Providers are dimmed by default. Components that provide a context or are named
…Provider
only carry data, so they start disabled; tick the Providers box to bring them back, drawn as
green hexagons so they never pass for regular components. The details panel lists them apart
from regular components, in green.
- Your own base colours.
react-wire.colors sets component, provider, root, selected
and edge. Any CSS colour; an empty value keeps the theme colour. Changes apply to
the open graph without re-analysing.
- The viewport stays put. Muting, hiding or focusing never changes your zoom; the selected
component (or the mount point) keeps its place on screen. Fit re-centres on demand.
- Details panel for the selected node: props received and props passed down, contexts (with the
hook that reads them), who renders it, what it renders, with providers listed apart. Every name
is a link that selects that node. Select several nodes (shift-click) and the panel shows one tab
per node. Drag the left edge of the side panel to resize it; the width is remembered.
- Unconnected toggle to include components that no path from the mount point reaches (rendered
through a variable, compound components, dead code…). They stay out of the way otherwise.
- Double-click a node or click its path to open the file at the declaration line.
- Every on/off setting is a toggle button that stays lit while active. Parents and
Children, both on by default, decide which side of the selection is highlighted: turn one off
to see only who renders the selected component, or only what it renders, when the arrows alone do
not make the direction obvious. Whole chain follows those parents all the way up to the mount
point and those children all the way down to the leaves, instead of the direct ones only.
Focus hides everything that is not highlighted and stays lit until you press it again.
Fit re-centres, Refresh re-analyses. Shift-click, or cmd-click on a Mac, adds a component
to the selection and removes it again.
- Routes. When a component is declared in a router table (
element: <Page /> or a lazy
route), the path it is mounted on, nested paths included, shows in the details panel as a purple
chip: a Routes section on the component itself, and next to each child in Renders that is
reached through a route. The AI guide carries the same information.
Usage
- Open a React project (a folder with a
tsconfig.json, or set react-wire.sourceGlobs).
- Click the ReactWire icon in the activity bar. The Analysis view shows which folder and
branch are analysed and a summary of the result. By default it is the workspace folder, exactly as
it sits on disk (checked-out branch, uncommitted changes included).
- To analyse a worktree or any other checkout, use Open Folder… in the view title (or click
the folder entry). The choice is remembered for this workspace; open the workspace folder again
to go back to the default.
- Open Graph in the view title, the summary entry, or ReactWire: Open Graph from the command
palette, opens the interactive graph. Refresh re-analyses.
- The last entry is the config. By default there is none and the state lives only in VS
Code. Click it to create one: a JSON file under
.react-wire/ in the analysed folder holding
your layers and the diagram they draw on. A layer is the whole state of the graph, the
colours assigned and their names, both searches, the selected components, the focus, the
direction and the toggles, so it can be committed, shared, and reopened exactly where you left
it. With a config active, a Save button appears in the view title and the entry says when
there are unsaved changes. Everything about configs starts from that entry (or Open Config…
in the … menu): open one of the project's configs, create a new one, import a config file
from anywhere, or go back to no config. Save Config As… in the same menu writes the current
state wherever you choose. Both dialogs open in ~/.react-wire/, a folder in your home shared by
every project, and remember the last folder you used. Ids in these files are relative to the
analysed folder, so a config committed by a teammate works on your machine. The folder is
watched: a config written by hand, by git or by an AI shows up in the list, and the active one
reloads on its own.
- The Layers group in the graph toolbar moves between Code and the layers of the config,
names them, adds an empty one, duplicates the current one or removes it. Code is always first
and is the diagram itself, without colours: the first colour, name or Show you change while on
Code creates a new layer at the end and moves you there. Searching, changing the direction,
selecting or focusing on Code does not create anything.
Layers and the stored diagram
A config saves the diagram as it was analysed: components, who renders whom, mount points and
providers. Nothing else, so it stays small and does not change every time a prop moves. Opening a
config draws that diagram even if the checked-out branch no longer matches it: components that
exist in the code get their props, contexts and location from the live analysis, and the rest are
drawn with a dotted border and a not in code badge. Refresh replaces the stored diagram with
the analysed code, and Save writes it back. With no config there is nothing stored and the graph
always shows the analysed code.
A layer can also draw planned components that do not exist yet (dashed border, planned
badge), regular or providers, with the edges to the real or planned components around them. There
is no button for that: it is meant for an AI assistant explaining a refactor step by step, one
layer per step, with colours for what goes away (a colour with Show off) and what appears. Since
the stored diagram is in the file, an assistant can also compare it with the code after a pull and
write a layer with what changed.
Working with an AI assistant
Write AI Guide in the … menu of the view asks for a folder, ~/.react-wire/ by default, and
writes two files there: <project>-config.json, a real export of the current layers without the
stored diagram, and <project>-AI-GUIDE.md, a short guide for the assistant: where that example
lives, the format of a config, layers and planned components, how component ids are built, which
colours exist and where to write the result so the extension picks it up. Then ask, for instance,
Claude Code: "read ~/.react-wire/zenit-AI-GUIDE.md and write a config marking in red every
component that touches the price prop and in green the pages that render them", or "…and
explain in layers how you would split the checkout: what you would delete, what you would create".
The file appears in Open Config…, and if it is the active one the graph reloads by itself.
A layer only stores what you decided: colours, names, searches, selection, focus and view options,
plus the planned components it draws. It never stores positions, because the layout is computed
from the graph and comes out the same every time. The diagram is written by the extension alone,
and an assistant only reads it to compare, so writing a config costs a few hundred tokens per layer.
Settings:
| Setting |
Default |
Purpose |
react-wire.ignoreGlobs |
tests, specs, stories, __tests__, __mocks__, .storybook, e2e |
Files left out of the analysis. Bare names match anywhere |
react-wire.colors |
{} (theme colours, important in red) |
Colour per kind of node and edge, see above |
react-wire.tsconfigPath |
"" (auto-detect at the root) |
tsconfig used to load the project, relative to the root |
react-wire.sourceGlobs |
["src/**/*.{ts,tsx,js,jsx}"] |
Files analysed when no tsconfig is found |
Changing any setting re-runs the analysis.
What it understands
- Function components: declarations, arrow functions,
memo, forwardRef, observer wrappers,
default exports, export default Name, and const Fast = memo(Slow) aliases.
- Props passed as JSX attributes and spreads. Forwarding is detected for destructured props, a
props bag and a ...rest spread.
createContext declarations, <Ctx.Provider> and React 19 <Ctx> providers, useContext(Ctx),
use(Ctx) and hooks that call them one level down.
- Mount points:
createRoot(el).render(...), a root kept in a variable, hydrateRoot, legacy
ReactDOM.render / render / hydrate from react-dom. Wrappers in the mount JSX
(<Providers><App /></Providers>) become the first levels of the tree.
- JSX built outside components: route builders and similar helpers (
buildRouter, a lazyMap
object with dynamic imports) are credited to the components that use them, transitively.
React.lazy(() => import('./Page')), with or without .then(m => ({ default: m.Page })), and
dynamic imports of components written directly in a component or route helper.
- Dynamic imports with a computed path (
import(`./${version}/index.ts`)): every module whose
path fits the template counts as rendered, so all the variants hang from the component that picks
one of them.
- Compound components:
<Card.Header /> resolves through Card.Header = HeaderSlot, a property in
an object ({ Header: HeaderSlot }) or a shorthand.
- Components picked at runtime:
const Tag = variants[kind], cond ? A : B, custom ?? Default
and React.lazy variables all resolve to every component they can be, so the graph connects to
all the alternatives.
- Route tables reached through helpers that build nothing themselves:
RouterProvider fed by
router = createBrowserRouter(routes) or by a hook called with { routes } still leads to the
pages, including lazy routes that destructure the import (const { Page } = await import(…)).
- Imports resolved by TypeScript, so
paths aliases, package.json imports (#app/*), barrels and
re-exports work.
Not covered yet: class components, hooks nested more than one level, and anything outside React
(API calls, events, cookies).
Development
npm install
npm run verify # typecheck + tests + build
npm run watch # rebuild on change
Press F5 in VS Code to launch an Extension Development Host with the extension loaded.
Installing in your own VS Code
npm run install:local # verify, package react-wire-<version>.vsix and install it
npm run package only builds the .vsix; install it by hand with
code --install-extension react-wire-<version>.vsix --force, or through
Extensions → … → Install from VSIX. Reinstall after each change; bump version in
package.json so VS Code picks up the new build.
Layout:
src/core pure analysis: ts-morph project → WireGraph (no VS Code, fully unit-tested)
src/extension VS Code glue: analysis view, folder choice, commands, settings, shared analysis service, webview panel
src/webview the graph UI: Cytoscape rendering, filters, details panel
src/shared the message protocol between extension and webview
The core is the only place that reasons about code. The extension never inspects source files and
the webview never knows where the graph came from.
| |