CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typedoc-plugin-markdown

A plugin for TypeDoc that enables TypeScript API documentation to be generated in Markdown.

Pending
Overview
Eval results
Files

events-hooks.mddocs/

Events and Lifecycle Hooks

Event system for hooking into the markdown generation lifecycle, enabling customization at various rendering stages through page events, renderer events, and hook points.

Capabilities

MarkdownPageEvent Class

Event emitted before and after the markdown content of each page is rendered, providing access to page data and content modification.

/**
 * An event emitted before and after the markdown of a page is rendered.
 * Provides access to page model, content, and metadata for customization.
 */
class MarkdownPageEvent<out Model extends RouterTarget = RouterTarget> {
  /** The project the renderer is currently processing */
  project: ProjectReflection;
  
  /** The filename the page will be written to */
  filename: string;
  
  /** The URL this page will be located at */
  url: string;
  
  /** The type of page this is (index, reflection, document, etc.) */
  pageKind: PageKind;
  
  /** The model that should be rendered on this page */
  readonly model: Model;
  
  /** The final markdown content of this page */
  contents: string;
  
  /** The frontmatter of this page represented as a key-value object */
  frontmatter?: Record<string, any>;
  
  /** Page headings for table of contents generation */
  pageHeadings: PageHeading[];
  
  /** Page sections for content organization */
  pageSections: any;
  
  /** Async jobs to run before page write */
  preWriteAsyncJobs: Array<(page: MarkdownPageEvent) => Promise<void>>;
  
  /**
   * Creates a new page event
   * @param model - The model to render on this page
   */
  constructor(model: Model);
  
  /**
   * Hidden method to start a new section in the page
   */
  startNewSection(): void;
  
  /**
   * Type guard to check if this is a reflection event
   * @returns True if this event is for a reflection page
   */
  isReflectionEvent(): this is MarkdownPageEvent<Reflection>;
  
  /** Event name triggered before a document will be rendered */
  static readonly BEGIN = 'beginPage';
  
  /** Event name triggered after a document has been rendered */
  static readonly END = 'endPage';
}

Usage Example:

import { MarkdownPageEvent } from "typedoc-plugin-markdown";

// Listen for page events
renderer.on(MarkdownPageEvent.BEGIN, (page) => {
  console.log(`Starting to render: ${page.filename}`);
  
  // Modify frontmatter before rendering
  page.frontmatter = {
    ...page.frontmatter,
    author: 'Documentation Team',
    lastModified: new Date().toISOString(),
    pageType: page.pageKind
  };
});

renderer.on(MarkdownPageEvent.END, (page) => {
  console.log(`Finished rendering: ${page.filename}`);
  
  // Post-process content
  page.contents = page.contents.replace(
    /<!-- INJECT_TOC -->/g,
    generateTableOfContents(page.pageHeadings)
  );
  
  // Add custom footer
  page.contents += '\n\n---\n*Generated with TypeDoc Plugin Markdown*';
});

MarkdownRendererEvent Class

Event emitted at the beginning and end of the entire rendering process, providing access to project-wide data and navigation.

/**
 * An event emitted at the beginning and end of the rendering process.
 * Provides access to project data, output directory, and page collection.
 */
class MarkdownRendererEvent {
  /** The project the renderer is currently processing */
  readonly project: ProjectReflection;
  
  /** The path of the directory the documentation should be written to */
  readonly outputDirectory: string;
  
  /** A list of all pages that will be generated */
  pages: PageDefinition[];
  
  /** The navigation structure of the project */
  navigation?: NavigationItem[];
  
  /**
   * Creates a new renderer event
   * @param outputDirectory - Directory path for output
   * @param project - The TypeDoc project being rendered
   * @param pages - Array of page definitions to generate
   */
  constructor(
    outputDirectory: string, 
    project: ProjectReflection, 
    pages: PageDefinition[]
  );
  
  /** Event name triggered before the renderer starts rendering */
  static readonly BEGIN = 'beginRender';
  
  /** Event name triggered after the renderer has written all documents */
  static readonly END = 'endRender';
}

Usage Example:

import { MarkdownRendererEvent } from "typedoc-plugin-markdown";

// Listen for renderer events
renderer.on(MarkdownRendererEvent.BEGIN, (event) => {
  console.log(`Starting documentation generation for ${event.project.name}`);
  console.log(`Output directory: ${event.outputDirectory}`);
  console.log(`Total pages to generate: ${event.pages.length}`);
  
  // Pre-process pages list
  event.pages = event.pages.filter(page => 
    !page.filename.includes('.internal.')
  );
  
  // Generate navigation if not present
  if (!event.navigation) {
    event.navigation = generateCustomNavigation(event.project);
  }
});

renderer.on(MarkdownRendererEvent.END, (event) => {
  console.log(`Documentation generation complete`);
  
  // Generate index file
  const indexContent = generateProjectIndex(event.project, event.navigation);
  writeFileSync(
    path.join(event.outputDirectory, 'index.md'), 
    indexContent
  );
  
  // Generate sitemap
  generateSitemap(event.pages, event.outputDirectory);
});

MarkdownRenderer Interface

Extended renderer interface with custom hooks and async job support for markdown-specific functionality.

/**
 * The MarkdownRenderer extends TypeDoc's Renderer with custom hooks and async jobs
 */
interface MarkdownRenderer extends Renderer {
  /** Dedicated markdown hooks for injecting content */
  markdownHooks: EventHooks<MarkdownRendererHooks, string>;
  
  /** Pre-render async jobs that run before documentation generation */
  preRenderAsyncJobs: Array<(output: MarkdownRendererEvent) => Promise<void>>;
  
  /** Post-render async jobs that run after documentation generation */
  postRenderAsyncJobs: Array<(output: MarkdownRendererEvent) => Promise<void>>;
  
  /** Store metadata about packages for packages mode */
  packagesMeta: Record<string, { description: string; options: Options }>;
  
  /**
   * Event listener for page events
   * @param event - Page event type
   * @param callback - Callback function for page events
   */
  on(
    event: typeof MarkdownPageEvent.BEGIN | typeof MarkdownPageEvent.END,
    callback: (page: MarkdownPageEvent) => void
  ): void;
  
  /**
   * Event listener for renderer events  
   * @param event - Renderer event type
   * @param callback - Callback function for renderer events
   */
  on(
    event: typeof MarkdownRendererEvent.BEGIN | typeof MarkdownRendererEvent.END,
    callback: (event: MarkdownRendererEvent) => void
  ): void;
  
  /**
   * Define a new theme for the renderer
   * @param name - Theme name
   * @param theme - Theme constructor class
   */
  defineTheme(name: string, theme: new (renderer: Renderer) => MarkdownTheme): void;
}

MarkdownRendererHooks Interface

Describes the hooks available for injecting content at various points in the markdown rendering process.

/**
 * Describes the hooks available to inject output in the markdown theme.
 * Each hook receives a MarkdownThemeContext for accessing page data and utilities.
 */
interface MarkdownRendererHooks {
  /** Applied at the start of markdown output */
  ['page.begin']: [MarkdownThemeContext];
  
  /** Applied at the end of markdown output */
  ['page.end']: [MarkdownThemeContext];
  
  /** Applied before main markdown content is rendered */
  ['content.begin']: [MarkdownThemeContext];
  
  /** Applied at start of markdown output on index page only */
  ['index.page.begin']: [MarkdownThemeContext];
  
  /** Applied at end of markdown output on index page only */
  ['index.page.end']: [MarkdownThemeContext];
}

Hook Usage Example:

import { MarkdownThemeContext } from "typedoc-plugin-markdown";

// Register hooks for content injection
renderer.markdownHooks.on('page.begin', (context: MarkdownThemeContext) => {
  // Add custom header to all pages
  return `<!-- Generated: ${new Date().toISOString()} -->
<!-- Project: ${context.page.project.name} -->

`;
});

renderer.markdownHooks.on('page.end', (context: MarkdownThemeContext) => {
  // Add custom footer to all pages
  const relativeHome = context.relativeURL('/');
  return `

---
[Back to Home](${relativeHome}) | [View Source](${getSourceUrl(context.page.model)})
`;
});

renderer.markdownHooks.on('content.begin', (context: MarkdownThemeContext) => {
  // Add table of contents before main content
  if (context.page.pageHeadings?.length > 0) {
    return generateTOC(context.page.pageHeadings);
  }
  return '';
});

renderer.markdownHooks.on('index.page.begin', (context: MarkdownThemeContext) => {
  // Special header for index page only
  return `# ${context.page.project.name} Documentation

Welcome to the API documentation for ${context.page.project.name}.

`;
});

Async Jobs System

System for registering asynchronous jobs that run before or after the rendering process.

Pre-render Jobs:

// Register pre-render job
renderer.preRenderAsyncJobs.push(async (event: MarkdownRendererEvent) => {
  console.log('Pre-processing documentation...');
  
  // Generate additional metadata
  const metadata = await analyzeProject(event.project);
  
  // Store in renderer for use during rendering
  renderer.packagesMeta[event.project.name] = {
    description: metadata.description,
    options: event.project.options
  };
  
  // Fetch external data
  const changelog = await fetchChangelog(event.project);
  
  // Add changelog page
  event.pages.push({
    model: event.project,
    filename: 'CHANGELOG.md',
    url: 'changelog.html',
    contents: changelog
  });
});

Post-render Jobs:

// Register post-render job
renderer.postRenderAsyncJobs.push(async (event: MarkdownRendererEvent) => {
  console.log('Post-processing documentation...');
  
  // Generate search index
  const searchIndex = await generateSearchIndex(event.pages);
  await writeFile(
    path.join(event.outputDirectory, 'search-index.json'),
    JSON.stringify(searchIndex)
  );
  
  // Optimize images
  await optimizeImages(event.outputDirectory);
  
  // Generate RSS feed
  const feed = generateRSSFeed(event.project, event.pages);
  await writeFile(
    path.join(event.outputDirectory, 'feed.xml'),
    feed
  );
  
  console.log('Documentation post-processing complete');
});

Complete Event Handling Example:

import { 
  MarkdownPageEvent,
  MarkdownRendererEvent,
  MarkdownThemeContext
} from "typedoc-plugin-markdown";

class DocumentationProcessor {
  setupEventHandlers(renderer: MarkdownRenderer) {
    // Page-level event handling
    renderer.on(MarkdownPageEvent.BEGIN, this.handlePageBegin.bind(this));
    renderer.on(MarkdownPageEvent.END, this.handlePageEnd.bind(this));
    
    // Renderer-level event handling  
    renderer.on(MarkdownRendererEvent.BEGIN, this.handleRenderBegin.bind(this));
    renderer.on(MarkdownRendererEvent.END, this.handleRenderEnd.bind(this));
    
    // Content injection hooks
    renderer.markdownHooks.on('page.begin', this.injectPageHeader.bind(this));
    renderer.markdownHooks.on('page.end', this.injectPageFooter.bind(this));
    renderer.markdownHooks.on('content.begin', this.injectTOC.bind(this));
    
    // Async jobs
    renderer.preRenderAsyncJobs.push(this.preRenderSetup.bind(this));
    renderer.postRenderAsyncJobs.push(this.postRenderCleanup.bind(this));
  }
  
  private handlePageBegin(page: MarkdownPageEvent) {
    console.log(`Rendering page: ${page.filename}`);
    
    // Set up frontmatter
    page.frontmatter = {
      title: this.getPageTitle(page.model),
      type: page.pageKind,
      generated: new Date().toISOString()
    };
  }
  
  private handlePageEnd(page: MarkdownPageEvent) {
    // Validate generated content
    this.validatePageContent(page);
    
    // Add analytics tracking
    page.contents += this.getAnalyticsCode(page);
  }
  
  private async preRenderSetup(event: MarkdownRendererEvent) {
    // Initialize external services
    await this.initializeServices();
    
    // Pre-process project data
    this.processProjectMetadata(event.project);
  }
  
  private async postRenderCleanup(event: MarkdownRendererEvent) {
    // Generate additional files
    await this.generateSupportingFiles(event);
    
    // Clean up temporary resources
    await this.cleanup();
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-typedoc-plugin-markdown

docs

configuration-options.md

events-hooks.md

index.md

plugin-bootstrap.md

routing-system.md

theme-system.md

tile.json