Aller au contenu

Understanding Plugin Architecture

Ce contenu n’est pas encore disponible dans votre langue.

This guide explains the internal architecture of the starlight-tags plugin. Understanding these internals helps when debugging, extending, or contributing to the plugin.

┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ tags.yml │────▶│ tags-processor │────▶│ data.ts │
└─────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌─────────────────┐
│ Validation │ │ Middleware │
│ (Zod Schema) │ │ (onRequest) │
└──────────────┘ └─────────────────┘
┌─────────────────┐
│ Astro.locals │
│ .starlightTags │
└─────────────────┘
┌────────────────────────┴────────────────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Components │ │ Generated Pages │
│ (TagBadge...) │ │ /tags/[slug]/ │
└─────────────────┘ └─────────────────┘

The plugin reads your tags.yml file at build time:

tags.yml
tags:
authentication:
label: "Authentication"
description: "Auth-related content"
color: "#3b82f6"

tags-processor.ts handles:

  • Converts YAML to JavaScript objects
  • Validates against Zod schemas
  • Scans content files for tag references
  • Creates slugs and URLs for each tag

The plugin uses route middleware to inject processed tag data into Astro.locals:

// Access via Astro.locals (automatically available)
const { tags, allTagsSorted, tagsByPage, config } = Astro.locals.starlightTags;
// Get a specific tag
const tag = tags.get('authentication');
// Get tags for current page
const pageTags = tagsByPage.get(pageSlug) ?? [];

Astro’s getStaticPaths() generates all tag pages at build time.

Tag pages are generated for every combination of:

  • Each tag defined in tags.yml
  • Each locale configured in Starlight (en, fr, es, etc.)
  • Each page number based on itemsPerPage setting
// Simplified example of generated paths
[
{ params: { tag: 'js-basics', locale: undefined, page: undefined } },
{ params: { tag: 'js-basics', locale: undefined, page: '2' } },
{ params: { tag: 'js-basics', locale: 'fr', page: undefined } },
{ params: { tag: 'js-basics', locale: 'fr', page: '2' } },
// ... for every tag × locale × page combination
]

Each generated tag page receives these props:

PropTypeDescription
tagProcessedTagTag data (label, icon, color, description, url)
pagesPageReference[]Paginated subset of pages with this tag
paginationPaginationInfoCurrent page, total pages, start/end index
interface ProcessedTag {
// Core properties
id: string; // Tag identifier (key in tags.yml)
label: string; // Display name
description?: string; // Optional description
color?: string; // Hex color code
icon?: string; // Emoji or icon
permalink?: string; // Custom URL slug override
hidden?: boolean; // Whether to hide from index
priority?: number; // Sort order (higher first, default: 0)
// Computed properties
slug: string; // URL-safe slug (from permalink or generated)
url: string; // Full URL path
count: number; // Number of pages with this tag
pages: PageReference[]; // All pages with this tag
// Educational properties (optional)
difficulty?: 'beginner' | 'intermediate' | 'advanced';
contentType?: 'lecture' | 'lab' | 'assignment' | 'project' | 'reference' | 'tutorial' | 'assessment';
prerequisites?: string[]; // Tag IDs that should be understood first
// Computed educational relationships
relatedTags?: string[]; // IDs of related tags
prerequisiteChain?: string[]; // Ordered prerequisite IDs (resolved recursively)
nextSteps?: string[]; // Suggested next topics
// Custom fields from extended schemas
[key: string]: unknown;
}
interface PageReference {
id: string; // Content collection ID
slug: string; // Page URL slug
title: string; // Page title
description?: string; // Page description
tags?: string[]; // All tags on this page
frontmatter?: Record<string, unknown>; // Full frontmatter data for advanced use
}

The “Related Tags” section on tag pages shows tags that co-occur with the current tag:

// Find other tags that appear on the same pages
const relatedTags = new Set<string>();
pages.forEach(page => {
page.tags?.forEach(tagId => {
if (tagId !== currentTag.id) {
relatedTags.add(tagId);
}
});
});
// Sort by usage count (most used first)
const sortedRelatedTags = Array.from(relatedTags)
.map(tagId => getTag(tagId))
.filter(tag => tag && !tag.hidden)
.sort((a, b) => b.count - a.count);

If the “authentication” tag appears on pages that also have:

  • “api” (5 pages)
  • “security” (3 pages)
  • “oauth” (2 pages)

The related tags section will show these three tags, sized by frequency.

Related tags are displayed with different sizes based on usage frequency:

TierCSS ClassUsage PercentileVisual Size
5tier-5Top 20%Largest
4tier-460-80%Large
3tier-340-60%Medium
2tier-220-40%Small
1tier-1Bottom 20%Smallest
function getPopularityTier(count: number): 'tier-1' | ... | 'tier-5' {
const range = maxCount - minCount;
if (range === 0) return 'tier-3'; // All same count
const normalized = (count - minCount) / range;
if (normalized >= 0.8) return 'tier-5';
if (normalized >= 0.6) return 'tier-4';
if (normalized >= 0.4) return 'tier-3';
if (normalized >= 0.2) return 'tier-2';
return 'tier-1';
}

Tag pages automatically support Starlight’s i18n configuration.

  • Root locale: /tags/authentication/
  • French: /fr/tags/authentication/
  • Spanish: /es/tags/authentication/

UI strings are loaded from the plugin’s translation files:

// Access via Astro.locals.t
const t = Astro.locals.t;
t('starlightTags.relatedTags'); // "Related Tags"
t('starlightTags.pages'); // "pages"
t('starlightTags.taggedPrefix'); // "Tagged:"

See the i18n Guide for adding custom translations.

Key files in the plugin:

packages/starlight-tags/
├── index.ts # Plugin entry point
├── middleware.ts # Route middleware for Astro.locals
├── locals.d.ts # TypeScript declarations for Astro.locals
├── src/
│ ├── components/ # Astro components
│ │ ├── TagBadge.astro
│ │ ├── PageTags.astro
│ │ └── ...
│ ├── libs/
│ │ ├── data.ts # Core data interface (StarlightTagsData)
│ │ ├── integration.ts # Astro integration setup
│ │ ├── tags-processor.ts # YAML processing & validation
│ │ ├── vite.ts # Config virtual module for runtime
│ │ ├── pagination.ts # Pagination logic
│ │ └── url.ts # URL building utilities
│ ├── pages/
│ │ ├── tags-index.astro # /tags/ index page
│ │ └── tag-page.astro # /tags/[slug]/ pages
│ ├── schemas/
│ │ ├── config.ts # Plugin config schema
│ │ ├── tags.ts # tags.yml schema
│ │ └── frontmatter.ts # Frontmatter extension
│ ├── utils.ts # Standalone utility functions
│ └── translations.ts # i18n strings

You can extend the tag schema with custom fields. See the Extending Schema Guide.

Access tag data programmatically in your components via Astro.locals:

---
import {
filterByDifficulty,
filterByContentType,
getLearningPath,
validatePrerequisites,
getPopularityTier
} from 'starlight-tags/utils';
// Core tag access - automatically available via middleware
const { tags, allTagsSorted, tagsByPage, config } = Astro.locals.starlightTags;
// Get a specific tag
const authTag = tags.get('authentication');
// Get tags for a specific page
const pageTags = tagsByPage.get('guides/authentication') ?? [];
// Filter by difficulty or content type
const beginnerTags = filterByDifficulty(allTagsSorted, 'beginner');
const tutorials = filterByContentType(allTagsSorted, 'tutorial');
// Educational features
const path = getLearningPath(tags, 'basics', 'advanced');
const { isValid, errors } = validatePrerequisites(tags);
---

See the Accessing Tag Data guide for complete documentation.