Basic Astro Extractor¶
Extract architecture from Astro component files.
The basic-astro extractor analyzes .astro files to discover UI components, islands, and their relationships. Island architecture mapped automatically.
What It Extracts¶
From Astro components:
- 🔹 Components — Astro components as architectural units
- 🔹 Actors — Users and external systems (via JSDoc
@actor) - 🔹 Relationships — Component dependencies from imports
- 🔹 Props — Component interfaces and contracts
From frontmatter JSDoc:
- 🔹 Component metadata — Name, description, purpose
- 🔹 Actor declarations — External system interactions
- 🔹 Relationship tags — Explicit dependencies
Configuration¶
Basic Setup¶
extractors:
- use: extractors/builtin/basic-astro
inputs:
include: ['src/**/*.astro']
exclude: ['**/*.test.astro']
Advanced Setup¶
extractors:
- use: extractors/builtin/basic-astro
name: web-ui # Optional: names this container
inputs:
include:
- 'src/components/**/*.astro'
- 'src/pages/**/*.astro'
- 'src/layouts/**/*.astro'
exclude:
- '**/*.test.astro'
- '**/examples/**'
- '**/drafts/**'
Glob patterns use minimatch syntax.
Annotations Reference¶
Component Tags¶
Mark a component with metadata:
---
/**
* @component UserCard
* Displays user profile information with avatar and bio
*/
interface Props {
name: string;
avatar: string;
bio?: string;
}
const { name, avatar, bio } = Astro.props;
---
<div class="user-card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
{bio && <p>{bio}</p>}
</div>
Also supports:
@module ComponentName— Alternative annotation style- File name used if no annotation present
What gets extracted:
- Component with ID derived from file name (
user-card.astro→user-card) - Description from JSDoc comment
- Props interface as component contract
- Import relationships to other components
Actor Tags¶
Declare external interactions:
---
/**
* @component PaymentForm
* Credit card payment interface
*
* @actor Customer {Person} {in} User entering payment details
* @actor StripeAPI {System} {out} Payment processor
*/
interface Props {
amount: number;
currency: string;
}
---
<form>
<!-- Payment form implementation -->
</form>
Format:
Direction:
{in}— Actor uses this component (user, viewer){out}— This component uses actor (external API)
Relationship Tags¶
Declare dependencies explicitly:
---
/**
* @component ProductPage
* Product detail page layout
*
* @uses ProductCard Displays product information
* @uses ReviewList Shows customer reviews
* @uses AddToCartButton Purchase action
*/
import ProductCard from '../components/ProductCard.astro';
import ReviewList from '../components/ReviewList.astro';
import AddToCartButton from '../components/AddToCartButton.astro';
---
<Layout>
<ProductCard {...product} />
<ReviewList reviews={reviews} />
<AddToCartButton productId={product.id} />
</Layout>
Format:
Note: Import statements create relationships automatically. Use @uses for documentation or non-imported dependencies.
Complete Example¶
---
/**
* Shopping cart component
*
* @component ShoppingCart
* @description Displays cart items with checkout functionality
*
* @actor Customer {Person} {in} User managing cart items
* @actor PaymentAPI {System} {out} Processes checkout payments
* @actor InventoryService {System} {out} Validates item availability
*
* @uses CartItem Renders individual cart entries
* @uses CheckoutButton Initiates purchase flow
*/
import CartItem from './CartItem.astro';
import CheckoutButton from './CheckoutButton.astro';
interface Props {
items: CartItem[];
total: number;
}
const { items, total } = Astro.props;
---
<div class="shopping-cart">
<h2>Your Cart ({items.length} items)</h2>
<div class="cart-items">
{items.map(item => (
<CartItem {...item} />
))}
</div>
<div class="cart-total">
<span>Total:</span>
<span>${total.toFixed(2)}</span>
</div>
<CheckoutButton disabled={items.length === 0} />
</div>
<style>
.shopping-cart {
border: 1px solid #ccc;
padding: 1rem;
}
</style>
Extracted:
- ✅ 1 Component:
ShoppingCart - ✅ 3 Actors:
Customer,PaymentAPI,InventoryService - ✅ 2 Import relationships: ShoppingCart → CartItem, CheckoutButton
- ✅ Props interface:
items,total - ✅ 6 Actor relationships (bidirectional)
Component ID Generation¶
Component IDs derive from file names:
| File Path | Component ID |
|---|---|
components/UserCard.astro |
usercard |
components/user-card.astro |
user-card |
layouts/BaseLayout.astro |
baselayout |
pages/products/[id].astro |
products-[id] |
components/ui/Button.astro |
ui-button |
Rules:
- Lowercase
- Remove
.astroextension - Preserve path structure in component name
- Keep hyphens and brackets
Import Detection¶
Automatically extracts relationships from imports:
---
// ✅ Relative imports detected
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
// ✅ Local component imports detected
import Button from './Button.astro';
// ⚠️ External package imports ignored (not architectural)
import { Icon } from 'astro-icon';
// ✅ Framework components detected
import ReactCounter from '../components/Counter.tsx';
---
<Layout>
<Header />
<main>
<Button />
<ReactCounter client:load />
</main>
<Footer />
</Layout>
Creates relationships:
- Layout → Header
- Layout → Footer
- Layout → Button
- Layout → ReactCounter
Island Architecture¶
Client directives indicate interactivity:
---
import InteractiveMap from '../components/Map.tsx';
import SearchBar from '../components/SearchBar.tsx';
import Analytics from '../components/Analytics.tsx';
---
<!-- Static content (no island) -->
<header>
<h1>Location Finder</h1>
</header>
<!-- Islands (hydrated on client) -->
<SearchBar client:load />
<InteractiveMap client:visible />
<Analytics client:idle />
Extracted:
- Static shell: header content
- 3 Islands: SearchBar (immediate), InteractiveMap (lazy), Analytics (idle)
- All mapped as component relationships
Props as Contracts¶
Props interfaces define component APIs:
---
/**
* @component ProductCard
*/
interface Props {
id: string; // Required
name: string; // Required
price: number; // Required
image?: string; // Optional
discount?: number; // Optional
}
const { id, name, price, image, discount } = Astro.props;
---
<article data-product={id}>
{image && <img src={image} alt={name} />}
<h3>{name}</h3>
<p class="price">
${discount ? price * (1 - discount) : price}
</p>
</article>
Props captured in IR:
{
"componentId": "productcard",
"props": {
"id": { "type": "string", "required": true },
"name": { "type": "string", "required": true },
"price": { "type": "number", "required": true },
"image": { "type": "string", "required": false },
"discount": { "type": "number", "required": false }
}
}
Layout Components¶
Layouts are components with special semantics:
---
/**
* @component BaseLayout
* Base page layout with header, footer, and SEO
*/
interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
Pages using this layout create relationships:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Home" description="Welcome">
<h1>Home Page</h1>
</BaseLayout>
Extracted: HomePage → BaseLayout relationship
Framework Integration¶
Astro supports React, Vue, Svelte, etc.:
---
/**
* @component Dashboard
* Main dashboard with mixed frameworks
*/
import ReactChart from '../components/Chart.tsx'; // React
import VueTable from '../components/Table.vue'; // Vue
import SvelteWidget from '../components/Widget.svelte'; // Svelte
---
<div class="dashboard">
<ReactChart client:load data={chartData} />
<VueTable client:visible items={tableData} />
<SvelteWidget client:idle config={widgetConfig} />
</div>
All framework components treated equally:
- Dashboard → Chart (React)
- Dashboard → Table (Vue)
- Dashboard → Widget (Svelte)
Monorepo Configuration¶
Extract UI separately from other layers:
extractors:
# Astro UI Components
- use: extractors/builtin/basic-astro
name: web-ui
inputs:
include: ['ui/src/**/*.astro']
# TypeScript Business Logic
- use: extractors/builtin/basic-node
name: api-layer
inputs:
include: ['api/src/**/*.ts']
# Cloudflare Workers
- use: extractors/builtin/basic-wrangler
inputs:
include: ['iac/workers/**/*.toml']
Three containers: UI, API, Infrastructure
Best Practices¶
✅ Do¶
- Annotate complex components — Use JSDoc for components with business logic
- Document actors — Clarify user roles and external APIs
- Use consistent naming — Kebab-case file names (
user-card.astro) - Organize by feature — Group related components in directories
- Declare props — TypeScript interfaces document contracts
❌ Don't¶
- Over-nest directories — Keep paths shallow for readability
- Mix conventions — Choose PascalCase or kebab-case, stay consistent
- Forget client directives — Document which islands are interactive
- Ignore imports — Import relationships are architectural
Troubleshooting¶
Components Not Showing Up¶
Check:
- File has
.astroextension - File matches
includepatterns - File doesn't match
excludepatterns - Frontmatter section exists (even if empty)
Relationships Missing¶
Check:
- Import statements use relative paths (
./or../) - Imported file has
.astro(or framework) extension - Target component exists in included files
- Import path is correct (typos break extraction)
Props Not Captured¶
Check:
interface Propsdefined in frontmatter- Using TypeScript syntax (not JSDoc types)
- Props destructured from
Astro.props
Actor Tags Not Working¶
Check:
- JSDoc comment is in frontmatter, not template section
- Format matches:
@actor Name {Type} {Direction} Description - Braces
{}are present around type and direction
What's Next?¶
Combine with other extractors:
- Basic Node — Add backend TypeScript/JavaScript
- Basic Wrangler — Add Cloudflare deployment topology
Learn more:
- Annotations Reference — All supported JSDoc tags
- Configuration Guide — Advanced extractor options
Extend Archlette:
- Plugin Development — Build custom extractors
Astro-Specific Features¶
Content Collections¶
Astro content collections can be documented:
---
/**
* @component BlogPost
* Individual blog post layout
*
* @uses ContentCollection Loads post data
*/
import { getEntry } from 'astro:content';
const post = await getEntry('blog', Astro.params.slug);
---
<article>
<h1>{post.data.title}</h1>
<time>{post.data.date}</time>
<div>{post.body}</div>
</article>
Dynamic Routes¶
Dynamic route files are components:
pages/
products/
[id].astro → Component: products-[id]
[...slug].astro → Component: products-[...slug]
View Transitions¶
View transition relationships:
---
/**
* @component TransitionPage
* Page with view transitions enabled
*/
---
<ViewTransitions />
<main transition:animate="slide">
<h1>Content with transitions</h1>
</main>
Note: View transition analysis is experimental.