RN Architecture Diagrams
Generate reliable architecture diagrams from a React Native / Expo / React codebase in TypeScript or JavaScript (.ts/.tsx and .js/.jsx). Three passes, all emitting PlantUML rendered to SVG + PNG offline via a bundled plantuml.jar. Every pass emits both a folder-level and a file-level view (so any project — JS or TS — gets the full set with no flags):
- Class diagrams —
tplant (TypeScript compiler API) extracts interfaces / classes / type contracts. TS-oriented: empty for idiomatic JS/JSX. File level = per-module + -full (each type is a node); folder level = a -class-folder-full rollup collapsing tplant's association/inheritance arrows to folder→folder edges.
- Component diagrams — a
ts-morph extractor finds functional components, lists each one's authored props as a «component» box, and draws "renders" arrows between components (a component tree). This is what covers the functional components tplant cannot. File level = per-module + -full (each component is a node); folder level = a -component-folder-full rollup collapsing the "renders" edges to folder→folder edges.
- Dependency graphs —
dependency-cruiser's JSON is turned into a PlantUML component diagram of the import graph ("what imports what"). Emitted at two granularities by default: folder (collapsed, readable) and file (lossless — one node per file, grouped into module-folder packages). This answers a different question than the other two: architecture/wiring, not type structure. Control it with --dep-granularity folder|file|both (default both).
All three passes run by default. Note the dependency graph is not a UML diagram — it is a module-level import graph. All three folder-level views use the same folder-collapse rule, so they line up visually.
Prerequisites
- Node (present in any RN project) — runs the generator.
- Java on PATH — required only for image rendering; used by all three passes (the dependency pass renders through the same
plantuml.jar, so no Graphviz is needed). Without Java, .puml files are still produced. Do not fall back to any remote PlantUML server (keeps code local).
- First run auto-installs
tplant + ts-morph + dependency-cruiser into this skill's node_modules and downloads plantuml.jar into vendor/. All cached; later runs are offline and fast.
How to run
One command (adjust the project path; defaults to the current directory):
node "<skillDir>/generate.mjs" <projectDir> [--out <dir>] [--format svg,png]
<skillDir> is the directory containing this SKILL.md.
- Default output is
<projectDir>/docs/uml. Override with --out.
--format none writes .puml only (no images).
- Run a subset:
--no-class / --no-components / --no-deps skip one pass; --only-class / --only-components / --only-deps run exactly one. See node generate.mjs --help.
--dep-granularity folder|file|both (default both) selects the dependency-graph node granularity.
--no-html skips the interactive .html views (see Reading dense graphs below); they are written by default.
Edge readability
Every diagram colors each edge by its source node (all arrows leaving a node share one
color) and lays edges out with linetype polyline + node spacing, so a busy graph is easier
to trace in the static .svg/.png. The colors are shared with the interactive view below.
This includes the tplant class diagrams (-class-<module> / -class-full): the source
color is injected into each relation while its UML arrowhead (inheritance <|--, realization
<|.., composition *--, association -->, dependency ..>) and any cardinality labels are
preserved — so they get the same coloring and interactive .html as the other passes.
Reading dense graphs (interactive HTML)
Alongside every graph the tool writes a self-contained, offline <slug>-<name>.html
(plus one shared vis-network.min.js in the output dir). Open it in a browser (double-click —
no server): hover or click a node to isolate its edges — the node, its incident edges and
its neighbors stay lit while everything else fades, so you can follow an edge from source to
destination no matter how many edges are present. It also has a source/target highlight
toggle, node search, arrowheads for direction, and drag/zoom. This is the view to reach for
when a static image is too crowded. .html generation needs no Java (it is pure data + JS);
it runs even with --format none, and is disabled with --no-html. First run downloads and
caches vis-network.min.js into the skill's vendor/ (offline thereafter).
The script auto-detects top-level source dirs (app, src, components, lib, hooks, services, store, db, …) and produces, per module plus a -full. Every file names its diagram type:
- Class diagrams
<slug>-class-<module>.puml / <slug>-class-full.puml (loose tplant function lines stripped), plus a folder-level rollup <slug>-class-folder-full.puml.
- Component diagrams
<slug>-component-<module>.puml / <slug>-component-full.puml, plus a folder-level rollup <slug>-component-folder-full.puml.
- Dependency graphs, folder granularity:
<slug>-dependency-<module>.puml / <slug>-dependency-full.puml.
- Dependency graphs, file granularity:
<slug>-dependency-file-<module>.puml / <slug>-dependency-file-full.puml.
The class/component folder rollups are a single -folder-full graph each (a per-module folder collapse would be one node). They are pruned when empty — notably -class-folder-full for JS/JSX projects, which have no TS types.
After running
- Report the summary tables the script prints (class: interface/class counts; components: component/edge counts; dependency: node/edge counts).
- Show the user the most useful SVG(s): a type-rich class module +
-class-full, -component-full (or a screen module like component-app) for the component tree, -dependency-full for the collapsed module import graph, and -dependency-file-full for the lossless file-level import graph (plus any -dependency-file-<module> for a readable intra-module slice). For the high-level architecture (folder→folder) view, show -component-folder-full (and -class-folder-full on a TS project) — these are the folder rollups of the component/class graphs. For any graph the user finds too crowded, point them at the matching .html (e.g. -dependency-file-full.html, -component-full.html) — hovering a node isolates its edges, which is the reliable way to trace a busy graph.
- Verify reliability: spot-check one interface's members against its
.ts source, one component's props/edges against its .ts(x)/.js(x) source, and one dependency arrow against a real import in the code.
- Tell the user plainly what each view is and its limits (below). Scans
.ts/.tsx and .js/.jsx: the component and dependency passes fully support JavaScript/JSX; class diagrams are type-oriented and are typically empty for idiomatic JS/JSX (no interfaces/type-aliases; tplant does not read plain JS) — for a JS project, point the user to the component and dependency diagrams.
Limits to state to the user
- Class diagrams: interfaces / classes / type contracts only — functional components do not appear here (tplant limitation); that's what the component diagrams are for. JS/JSX projects have no TS types, so class diagrams come out empty and are pruned — expected, not an error. Per-module diagrams (
-class-<module>) are scoped to the types DECLARED in that module: borrowed types imported from elsewhere are filtered out (tplant loads the whole program and would otherwise emit every transitively-reachable type, duplicating the shared model graph across every folder). Relations to a borrowed type, and extends/implements clauses pointing at one, are dropped too; a module that declares no interface/class/enum is pruned as empty. -class-full is the unfiltered union of all project types — the one place to see everything at once. The folder rollup (-class-folder-full) collapses tplant's association/inheritance arrows to folder→folder edges; every folder that contains a type is drawn as a node (lossless), even one with no cross-folder arrow — only the edges depend on tplant emitting arrows. It is still empty (pruned) for JS/JSX (no types → no folder nodes), same as the class file-level view. Two types with the same name in different folders collapse to one folder node (rare; a heuristic-scan caveat).
- Component diagrams: props come from the component's authored type literal / named props type; inherited external base types (RN
ViewProps, TextInputProps, …) are shown as a ..TypeName.. reference line, not expanded (deliberate — avoids dozens of inherited props). Untyped JS/JSX components have no props type, so their boxes render name-only — the "renders" edges (the component tree) are the value there. Component detection and "renders" edges are syntactic heuristics (PascalCase in .tsx/.jsx/.js, JSX tag identifiers); HOCs, dynamic createElement, or components authored in .ts may be missed. The folder rollup (-component-folder-full) collapses those "renders" edges to folder→folder edges (intra-folder edges drop out) — a high-level architecture view; every folder that contains a component is drawn as a node (lossless), even one with no cross-folder edge. It inherits the same detection heuristics.
- Folder rollups (class + component): one
-folder-full graph per pass (per-module folder collapse would be a single node). Lossless: every folder that contains a type/component is a node, including folders with no cross-folder edge (matching the file-level views and the -dependency-full folder graph). Folder nodes use the same folder granularity as the dependency folder graph (module dir, splitting one level of subfolder — e.g. src/components/ui, src/components/profile), so all three folder-level views line up.
- Dependency graphs: an import graph, not UML — no class members. Two granularities (both by default;
--dep-granularity narrows):
- folder — nodes collapsed to folder granularity (the full graph to the first two path segments) to stay readable. The full folder graph is lossless: every folder that contains a file is a node, even one with no cross-folder import edge. Per-module folder graphs stay edge-derived (intra-module folder edges only) and are skipped when a module has none. This is the readable overview.
- file — lossless: one node per source file (grouped into module-folder packages, e.g.
src/components/ui), every file→file import an edge. Every in-scope source file appears as a node even when it has no qualifying edge — a file whose only imports leave its module (or that is only imported by other modules, or imports nothing at all) still renders as a standalone node, so a per-module graph shows all of that module's files, not just the connected ones. Busier (dozens of nodes on a real app); the per-module -dependency-file-<module> graphs are the readable slices. Scope matches the folder graph: only the detected module dirs are scanned, so loose entry files at the source root (e.g. src/App.jsx, src/main.jsx) are not included — same as the folder graph.
- Common to both: type-only imports (
import type { … }) are included (via --ts-pre-compilation-deps), so type/model folders show their true fan-in rather than looking orphaned. node_modules, test/spec files, core modules and unresolved imports are excluded (a couldNotResolve import is dropped, never invented). Resolved non-JS imports depcruise follows (e.g. an imported .css) do appear as nodes. Path-alias imports resolve when a tsconfig.json or jsconfig.json (JS projects) is present; without one, only relative-import edges appear. PlantUML's built-in layout engine (no Graphviz) handles both; collapse keeps the overview tidy while the file graph trades tidiness for completeness.
Troubleshooting
- PlantUML "Error line N" in a class diagram: a loose column-0 member slipped through — the
.puml should have no lines starting with +/- at column 0. The script strips these in cleanPuml; extend it if a new tplant version changes output.
- No source dirs found: run from the project root, or pass
--include <dir>.
- Empty class diagram / skipped component or dependency module: that module has no interfaces/classes, no components, or no internal imports — expected.
- Component pass error: the script prints
component pass failed: … and still completes the other passes; check that ts-morph installed under the skill's node_modules.
- Dependency pass error: the script prints
dependency pass failed: … (e.g. dependency-cruiser produced no JSON) and still completes the other passes; check that dependency-cruiser installed under the skill's node_modules and that the project's source dirs are readable. A project tsconfig that extends a base with a module/target value dependency-cruiser's bundled TypeScript can't parse (e.g. expo/tsconfig.base, TS6046) is handled automatically: the dep pass synthesizes a temporary extends-free config carrying only the project's baseUrl + paths (read from tsconfig.json, or jsconfig.json for JS projects) so @/… aliases still resolve, and falls back to the raw config, then to none, before failing. If the resulting graph has very few edges, path aliases likely could not be resolved (a missing/atypical paths map) — imports then show as unresolved and are dropped.
1---2name: rn-architecture-diagrams3description: Generate architecture diagrams for a React Native / Expo / React project in TypeScript OR JavaScript (.ts/.tsx and .js/.jsx) — class diagrams (interfaces/classes/type contracts, via tplant; TS only), functional-component diagrams (each component with its props plus "renders" arrows, via a ts-morph extractor), AND a module dependency graph (what-imports-what, via dependency-cruiser). Component and dependency passes work on JS/JSX too; class diagrams are empty for plain JS. All rendered offline to SVG/PNG through a bundled plantuml.jar (no Graphviz). Use when the user wants class diagrams, component diagrams, a component tree, a dependency/module graph, UML, or an architecture view of a RN / Expo / React JS or TS project.4---56# RN Architecture Diagrams78Generate reliable architecture diagrams from a React Native / Expo / React codebase in TypeScript or JavaScript (`.ts/.tsx` and `.js/.jsx`). Three passes, all emitting PlantUML rendered to SVG + PNG **offline** via a bundled `plantuml.jar`. **Every pass emits both a folder-level and a file-level view** (so any project — JS or TS — gets the full set with no flags):9- **Class diagrams** — `tplant` (TypeScript compiler API) extracts interfaces / classes / type contracts. TS-oriented: empty for idiomatic JS/JSX. File level = per-module + `-full` (each type is a node); folder level = a `-class-folder-full` rollup collapsing tplant's association/inheritance arrows to folder→folder edges.10- **Component diagrams** — a `ts-morph` extractor finds functional components, lists each one's authored props as a «component» box, and draws "renders" arrows between components (a component tree). This is what covers the functional components tplant cannot. File level = per-module + `-full` (each component is a node); folder level = a `-component-folder-full` rollup collapsing the "renders" edges to folder→folder edges.11- **Dependency graphs** — `dependency-cruiser`'s JSON is turned into a PlantUML component diagram of the import graph ("what imports what"). Emitted at **two granularities** by default: **folder** (collapsed, readable) and **file** (lossless — one node per file, grouped into module-folder packages). This answers a different question than the other two: architecture/wiring, not type structure. Control it with `--dep-granularity folder|file|both` (default `both`).1213All three passes run by default. Note the dependency graph is *not* a UML diagram — it is a module-level import graph. All three folder-level views use the **same** folder-collapse rule, so they line up visually.1415## Prerequisites16- **Node** (present in any RN project) — runs the generator.17- **Java on PATH** — required only for image rendering; used by all three passes (the dependency pass renders through the same `plantuml.jar`, so **no Graphviz is needed**). Without Java, `.puml` files are still produced. Do not fall back to any remote PlantUML server (keeps code local).18- First run auto-installs `tplant` + `ts-morph` + `dependency-cruiser` into this skill's `node_modules` and downloads `plantuml.jar` into `vendor/`. All cached; later runs are offline and fast.1920## How to run21One command (adjust the project path; defaults to the current directory):2223```24node "<skillDir>/generate.mjs" <projectDir> [--out <dir>] [--format svg,png]25```2627- `<skillDir>` is the directory containing this SKILL.md.28- Default output is `<projectDir>/docs/uml`. Override with `--out`.29- `--format none` writes `.puml` only (no images).30- Run a subset: `--no-class` / `--no-components` / `--no-deps` skip one pass; `--only-class` / `--only-components` / `--only-deps` run exactly one. See `node generate.mjs --help`.31- `--dep-granularity folder|file|both` (default `both`) selects the dependency-graph node granularity.32- `--no-html` skips the interactive `.html` views (see **Reading dense graphs** below); they are written by default.3334### Edge readability35Every diagram colors each edge **by its source node** (all arrows leaving a node share one36color) and lays edges out with `linetype polyline` + node spacing, so a busy graph is easier37to trace in the static `.svg`/`.png`. The colors are shared with the interactive view below.38This includes the tplant **class diagrams** (`-class-<module>` / `-class-full`): the source39color is injected into each relation while its UML arrowhead (inheritance `<|--`, realization40`<|..`, composition `*--`, association `-->`, dependency `..>`) and any cardinality labels are41preserved — so they get the same coloring and interactive `.html` as the other passes.4243### Reading dense graphs (interactive HTML)44Alongside every graph the tool writes a self-contained, **offline** `<slug>-<name>.html`45(plus one shared `vis-network.min.js` in the output dir). Open it in a browser (double-click —46no server): **hover or click a node to isolate its edges** — the node, its incident edges and47its neighbors stay lit while everything else fades, so you can follow an edge from source to48destination **no matter how many edges are present**. It also has a source/target highlight49toggle, node search, arrowheads for direction, and drag/zoom. This is the view to reach for50when a static image is too crowded. `.html` generation needs no Java (it is pure data + JS);51it runs even with `--format none`, and is disabled with `--no-html`. First run downloads and52caches `vis-network.min.js` into the skill's `vendor/` (offline thereafter).5354The script auto-detects top-level source dirs (`app`, `src`, `components`, `lib`, `hooks`, `services`, `store`, `db`, …) and produces, **per module plus a `-full`**. Every file names its diagram type:55- Class diagrams `<slug>-class-<module>.puml` / `<slug>-class-full.puml` (loose tplant function lines stripped), plus a folder-level rollup `<slug>-class-folder-full.puml`.56- Component diagrams `<slug>-component-<module>.puml` / `<slug>-component-full.puml`, plus a folder-level rollup `<slug>-component-folder-full.puml`.57- Dependency graphs, **folder** granularity: `<slug>-dependency-<module>.puml` / `<slug>-dependency-full.puml`.58- Dependency graphs, **file** granularity: `<slug>-dependency-file-<module>.puml` / `<slug>-dependency-file-full.puml`.5960The class/component folder rollups are a single `-folder-full` graph each (a per-module folder collapse would be one node). They are pruned when empty — notably `-class-folder-full` for JS/JSX projects, which have no TS types.6162## After running631. Report the summary tables the script prints (class: interface/class counts; components: component/edge counts; dependency: node/edge counts).642. Show the user the most useful SVG(s): a type-rich class module + `-class-full`, `-component-full` (or a screen module like `component-app`) for the component tree, `-dependency-full` for the collapsed module import graph, and `-dependency-file-full` for the lossless file-level import graph (plus any `-dependency-file-<module>` for a readable intra-module slice). For the **high-level architecture (folder→folder) view**, show `-component-folder-full` (and `-class-folder-full` on a TS project) — these are the folder rollups of the component/class graphs. **For any graph the user finds too crowded, point them at the matching `.html`** (e.g. `-dependency-file-full.html`, `-component-full.html`) — hovering a node isolates its edges, which is the reliable way to trace a busy graph.653. **Verify reliability**: spot-check one interface's members against its `.ts` source, one component's props/edges against its `.ts(x)`/`.js(x)` source, and one dependency arrow against a real `import` in the code.664. Tell the user plainly what each view is and its limits (below). Scans `.ts/.tsx` **and** `.js/.jsx`: the component and dependency passes fully support JavaScript/JSX; class diagrams are type-oriented and are typically **empty for idiomatic JS/JSX** (no interfaces/type-aliases; tplant does not read plain JS) — for a JS project, point the user to the component and dependency diagrams.6768## Limits to state to the user69- **Class diagrams**: interfaces / classes / type contracts only — functional components do not appear here (tplant limitation); that's what the component diagrams are for. **JS/JSX projects have no TS types, so class diagrams come out empty and are pruned** — expected, not an error. **Per-module diagrams (`-class-<module>`) are scoped to the types DECLARED in that module**: borrowed types imported from elsewhere are filtered out (tplant loads the whole program and would otherwise emit every transitively-reachable type, duplicating the shared model graph across every folder). Relations to a borrowed type, and `extends`/`implements` clauses pointing at one, are dropped too; a module that declares no interface/class/enum is pruned as empty. **`-class-full` is the unfiltered union** of all project types — the one place to see everything at once. The **folder rollup** (`-class-folder-full`) collapses tplant's association/inheritance arrows to folder→folder edges; **every folder that contains a type is drawn as a node** (lossless), even one with no cross-folder arrow — only the edges depend on tplant emitting arrows. It is still **empty (pruned) for JS/JSX** (no types → no folder nodes), same as the class file-level view. Two types with the same name in different folders collapse to one folder node (rare; a heuristic-scan caveat).70- **Component diagrams**: props come from the component's **authored** type literal / named props type; inherited external base types (RN `ViewProps`, `TextInputProps`, …) are shown as a `..TypeName..` reference line, not expanded (deliberate — avoids dozens of inherited props). **Untyped JS/JSX components have no props type, so their boxes render name-only — the "renders" edges (the component tree) are the value there.** Component detection and "renders" edges are **syntactic heuristics** (PascalCase in `.tsx`/`.jsx`/`.js`, JSX tag identifiers); HOCs, dynamic `createElement`, or components authored in `.ts` may be missed. The **folder rollup** (`-component-folder-full`) collapses those "renders" edges to folder→folder edges (intra-folder edges drop out) — a high-level architecture view; **every folder that contains a component is drawn as a node** (lossless), even one with no cross-folder edge. It inherits the same detection heuristics.71- **Folder rollups (class + component)**: one `-folder-full` graph per pass (per-module folder collapse would be a single node). **Lossless: every folder that contains a type/component is a node**, including folders with no cross-folder edge (matching the file-level views and the `-dependency-full` folder graph). Folder nodes use the **same folder granularity as the dependency folder graph** (module dir, splitting one level of subfolder — e.g. `src/components/ui`, `src/components/profile`), so all three folder-level views line up.72- **Dependency graphs**: an *import* graph, **not** UML — no class members. Two granularities (both by default; `--dep-granularity` narrows):73 - **folder** — nodes **collapsed to folder granularity** (the full graph to the first two path segments) to stay readable. The **full** folder graph is lossless: every folder that contains a file is a node, even one with no cross-folder import edge. Per-module folder graphs stay edge-derived (intra-module folder edges only) and are skipped when a module has none. This is the readable overview.74 - **file** — **lossless**: one node per source file (grouped into module-folder packages, e.g. `src/components/ui`), every file→file import an edge. Every in-scope source file appears as a node **even when it has no qualifying edge** — a file whose only imports leave its module (or that is only imported by other modules, or imports nothing at all) still renders as a standalone node, so a per-module graph shows all of that module's files, not just the connected ones. Busier (dozens of nodes on a real app); the per-module `-dependency-file-<module>` graphs are the readable slices. Scope matches the folder graph: only the detected **module dirs** are scanned, so loose entry files at the source root (e.g. `src/App.jsx`, `src/main.jsx`) are not included — same as the folder graph.75 - Common to both: **type-only imports (`import type { … }`) are included** (via `--ts-pre-compilation-deps`), so type/model folders show their true fan-in rather than looking orphaned. `node_modules`, test/spec files, core modules and unresolved imports are excluded (a `couldNotResolve` import is dropped, never invented). Resolved non-JS imports depcruise follows (e.g. an imported `.css`) do appear as nodes. Path-alias imports resolve when a `tsconfig.json` **or** `jsconfig.json` (JS projects) is present; without one, only relative-import edges appear. PlantUML's built-in layout engine (no Graphviz) handles both; collapse keeps the overview tidy while the file graph trades tidiness for completeness.7677## Troubleshooting78- **PlantUML "Error line N"** in a class diagram: a loose column-0 member slipped through — the `.puml` should have no lines starting with `+`/`-` at column 0. The script strips these in `cleanPuml`; extend it if a new tplant version changes output.79- **No source dirs found**: run from the project root, or pass `--include <dir>`.80- **Empty class diagram / skipped component or dependency module**: that module has no interfaces/classes, no components, or no internal imports — expected.81- **Component pass error**: the script prints `component pass failed: …` and still completes the other passes; check that `ts-morph` installed under the skill's `node_modules`.82- **Dependency pass error**: the script prints `dependency pass failed: …` (e.g. `dependency-cruiser produced no JSON`) and still completes the other passes; check that `dependency-cruiser` installed under the skill's `node_modules` and that the project's source dirs are readable. A project tsconfig that `extends` a base with a `module`/`target` value dependency-cruiser's bundled TypeScript can't parse (e.g. `expo/tsconfig.base`, TS6046) is handled automatically: the dep pass synthesizes a temporary `extends`-free config carrying only the project's `baseUrl` + `paths` (read from `tsconfig.json`, or `jsconfig.json` for JS projects) so `@/…` aliases still resolve, and falls back to the raw config, then to none, before failing. If the resulting graph has very few edges, path aliases likely could not be resolved (a missing/atypical `paths` map) — imports then show as unresolved and are dropped.