# Rn Architecture Diagrams

> 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.

- Skill: `mohansaiganesh/rn-architecture-diagrams` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add mohansaiganesh/rn-architecture-diagrams`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mohansaiganesh/rn-architecture-diagrams/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: mohansaiganesh (https://skillmd.com/u/mohansaiganesh)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mohansaiganesh/rn-architecture-diagrams

---


# 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
1. Report the summary tables the script prints (class: interface/class counts; components: component/edge counts; dependency: node/edge counts).
2. 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.
3. **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.
4. 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.

