0
# Events and Lifecycle Hooks
1
2
Event system for hooking into the markdown generation lifecycle, enabling customization at various rendering stages through page events, renderer events, and hook points.
3
4
## Capabilities
5
6
### MarkdownPageEvent Class
7
8
Event emitted before and after the markdown content of each page is rendered, providing access to page data and content modification.
9
10
```typescript { .api }
11
/**
12
* An event emitted before and after the markdown of a page is rendered.
13
* Provides access to page model, content, and metadata for customization.
14
*/
15
class MarkdownPageEvent<out Model extends RouterTarget = RouterTarget> {
16
/** The project the renderer is currently processing */
17
project: ProjectReflection;
18
19
/** The filename the page will be written to */
20
filename: string;
21
22
/** The URL this page will be located at */
23
url: string;
24
25
/** The type of page this is (index, reflection, document, etc.) */
26
pageKind: PageKind;
27
28
/** The model that should be rendered on this page */
29
readonly model: Model;
30
31
/** The final markdown content of this page */
32
contents: string;
33
34
/** The frontmatter of this page represented as a key-value object */
35
frontmatter?: Record<string, any>;
36
37
/** Page headings for table of contents generation */
38
pageHeadings: PageHeading[];
39
40
/** Page sections for content organization */
41
pageSections: any;
42
43
/** Async jobs to run before page write */
44
preWriteAsyncJobs: Array<(page: MarkdownPageEvent) => Promise<void>>;
45
46
/**
47
* Creates a new page event
48
* @param model - The model to render on this page
49
*/
50
constructor(model: Model);
51
52
/**
53
* Hidden method to start a new section in the page
54
*/
55
startNewSection(): void;
56
57
/**
58
* Type guard to check if this is a reflection event
59
* @returns True if this event is for a reflection page
60
*/
61
isReflectionEvent(): this is MarkdownPageEvent<Reflection>;
62
63
/** Event name triggered before a document will be rendered */
64
static readonly BEGIN = 'beginPage';
65
66
/** Event name triggered after a document has been rendered */
67
static readonly END = 'endPage';
68
}
69
```
70
71
**Usage Example:**
72
73
```typescript
74
import { MarkdownPageEvent } from "typedoc-plugin-markdown";
75
76
// Listen for page events
77
renderer.on(MarkdownPageEvent.BEGIN, (page) => {
78
console.log(`Starting to render: ${page.filename}`);
79
80
// Modify frontmatter before rendering
81
page.frontmatter = {
82
...page.frontmatter,
83
author: 'Documentation Team',
84
lastModified: new Date().toISOString(),
85
pageType: page.pageKind
86
};
87
});
88
89
renderer.on(MarkdownPageEvent.END, (page) => {
90
console.log(`Finished rendering: ${page.filename}`);
91
92
// Post-process content
93
page.contents = page.contents.replace(
94
/<!-- INJECT_TOC -->/g,
95
generateTableOfContents(page.pageHeadings)
96
);
97
98
// Add custom footer
99
page.contents += '\n\n---\n*Generated with TypeDoc Plugin Markdown*';
100
});
101
```
102
103
### MarkdownRendererEvent Class
104
105
Event emitted at the beginning and end of the entire rendering process, providing access to project-wide data and navigation.
106
107
```typescript { .api }
108
/**
109
* An event emitted at the beginning and end of the rendering process.
110
* Provides access to project data, output directory, and page collection.
111
*/
112
class MarkdownRendererEvent {
113
/** The project the renderer is currently processing */
114
readonly project: ProjectReflection;
115
116
/** The path of the directory the documentation should be written to */
117
readonly outputDirectory: string;
118
119
/** A list of all pages that will be generated */
120
pages: PageDefinition[];
121
122
/** The navigation structure of the project */
123
navigation?: NavigationItem[];
124
125
/**
126
* Creates a new renderer event
127
* @param outputDirectory - Directory path for output
128
* @param project - The TypeDoc project being rendered
129
* @param pages - Array of page definitions to generate
130
*/
131
constructor(
132
outputDirectory: string,
133
project: ProjectReflection,
134
pages: PageDefinition[]
135
);
136
137
/** Event name triggered before the renderer starts rendering */
138
static readonly BEGIN = 'beginRender';
139
140
/** Event name triggered after the renderer has written all documents */
141
static readonly END = 'endRender';
142
}
143
```
144
145
**Usage Example:**
146
147
```typescript
148
import { MarkdownRendererEvent } from "typedoc-plugin-markdown";
149
150
// Listen for renderer events
151
renderer.on(MarkdownRendererEvent.BEGIN, (event) => {
152
console.log(`Starting documentation generation for ${event.project.name}`);
153
console.log(`Output directory: ${event.outputDirectory}`);
154
console.log(`Total pages to generate: ${event.pages.length}`);
155
156
// Pre-process pages list
157
event.pages = event.pages.filter(page =>
158
!page.filename.includes('.internal.')
159
);
160
161
// Generate navigation if not present
162
if (!event.navigation) {
163
event.navigation = generateCustomNavigation(event.project);
164
}
165
});
166
167
renderer.on(MarkdownRendererEvent.END, (event) => {
168
console.log(`Documentation generation complete`);
169
170
// Generate index file
171
const indexContent = generateProjectIndex(event.project, event.navigation);
172
writeFileSync(
173
path.join(event.outputDirectory, 'index.md'),
174
indexContent
175
);
176
177
// Generate sitemap
178
generateSitemap(event.pages, event.outputDirectory);
179
});
180
```
181
182
### MarkdownRenderer Interface
183
184
Extended renderer interface with custom hooks and async job support for markdown-specific functionality.
185
186
```typescript { .api }
187
/**
188
* The MarkdownRenderer extends TypeDoc's Renderer with custom hooks and async jobs
189
*/
190
interface MarkdownRenderer extends Renderer {
191
/** Dedicated markdown hooks for injecting content */
192
markdownHooks: EventHooks<MarkdownRendererHooks, string>;
193
194
/** Pre-render async jobs that run before documentation generation */
195
preRenderAsyncJobs: Array<(output: MarkdownRendererEvent) => Promise<void>>;
196
197
/** Post-render async jobs that run after documentation generation */
198
postRenderAsyncJobs: Array<(output: MarkdownRendererEvent) => Promise<void>>;
199
200
/** Store metadata about packages for packages mode */
201
packagesMeta: Record<string, { description: string; options: Options }>;
202
203
/**
204
* Event listener for page events
205
* @param event - Page event type
206
* @param callback - Callback function for page events
207
*/
208
on(
209
event: typeof MarkdownPageEvent.BEGIN | typeof MarkdownPageEvent.END,
210
callback: (page: MarkdownPageEvent) => void
211
): void;
212
213
/**
214
* Event listener for renderer events
215
* @param event - Renderer event type
216
* @param callback - Callback function for renderer events
217
*/
218
on(
219
event: typeof MarkdownRendererEvent.BEGIN | typeof MarkdownRendererEvent.END,
220
callback: (event: MarkdownRendererEvent) => void
221
): void;
222
223
/**
224
* Define a new theme for the renderer
225
* @param name - Theme name
226
* @param theme - Theme constructor class
227
*/
228
defineTheme(name: string, theme: new (renderer: Renderer) => MarkdownTheme): void;
229
}
230
```
231
232
### MarkdownRendererHooks Interface
233
234
Describes the hooks available for injecting content at various points in the markdown rendering process.
235
236
```typescript { .api }
237
/**
238
* Describes the hooks available to inject output in the markdown theme.
239
* Each hook receives a MarkdownThemeContext for accessing page data and utilities.
240
*/
241
interface MarkdownRendererHooks {
242
/** Applied at the start of markdown output */
243
['page.begin']: [MarkdownThemeContext];
244
245
/** Applied at the end of markdown output */
246
['page.end']: [MarkdownThemeContext];
247
248
/** Applied before main markdown content is rendered */
249
['content.begin']: [MarkdownThemeContext];
250
251
/** Applied at start of markdown output on index page only */
252
['index.page.begin']: [MarkdownThemeContext];
253
254
/** Applied at end of markdown output on index page only */
255
['index.page.end']: [MarkdownThemeContext];
256
}
257
```
258
259
**Hook Usage Example:**
260
261
```typescript
262
import { MarkdownThemeContext } from "typedoc-plugin-markdown";
263
264
// Register hooks for content injection
265
renderer.markdownHooks.on('page.begin', (context: MarkdownThemeContext) => {
266
// Add custom header to all pages
267
return `<!-- Generated: ${new Date().toISOString()} -->
268
<!-- Project: ${context.page.project.name} -->
269
270
`;
271
});
272
273
renderer.markdownHooks.on('page.end', (context: MarkdownThemeContext) => {
274
// Add custom footer to all pages
275
const relativeHome = context.relativeURL('/');
276
return `
277
278
---
279
[Back to Home](${relativeHome}) | [View Source](${getSourceUrl(context.page.model)})
280
`;
281
});
282
283
renderer.markdownHooks.on('content.begin', (context: MarkdownThemeContext) => {
284
// Add table of contents before main content
285
if (context.page.pageHeadings?.length > 0) {
286
return generateTOC(context.page.pageHeadings);
287
}
288
return '';
289
});
290
291
renderer.markdownHooks.on('index.page.begin', (context: MarkdownThemeContext) => {
292
// Special header for index page only
293
return `# ${context.page.project.name} Documentation
294
295
Welcome to the API documentation for ${context.page.project.name}.
296
297
`;
298
});
299
```
300
301
### Async Jobs System
302
303
System for registering asynchronous jobs that run before or after the rendering process.
304
305
**Pre-render Jobs:**
306
307
```typescript
308
// Register pre-render job
309
renderer.preRenderAsyncJobs.push(async (event: MarkdownRendererEvent) => {
310
console.log('Pre-processing documentation...');
311
312
// Generate additional metadata
313
const metadata = await analyzeProject(event.project);
314
315
// Store in renderer for use during rendering
316
renderer.packagesMeta[event.project.name] = {
317
description: metadata.description,
318
options: event.project.options
319
};
320
321
// Fetch external data
322
const changelog = await fetchChangelog(event.project);
323
324
// Add changelog page
325
event.pages.push({
326
model: event.project,
327
filename: 'CHANGELOG.md',
328
url: 'changelog.html',
329
contents: changelog
330
});
331
});
332
```
333
334
**Post-render Jobs:**
335
336
```typescript
337
// Register post-render job
338
renderer.postRenderAsyncJobs.push(async (event: MarkdownRendererEvent) => {
339
console.log('Post-processing documentation...');
340
341
// Generate search index
342
const searchIndex = await generateSearchIndex(event.pages);
343
await writeFile(
344
path.join(event.outputDirectory, 'search-index.json'),
345
JSON.stringify(searchIndex)
346
);
347
348
// Optimize images
349
await optimizeImages(event.outputDirectory);
350
351
// Generate RSS feed
352
const feed = generateRSSFeed(event.project, event.pages);
353
await writeFile(
354
path.join(event.outputDirectory, 'feed.xml'),
355
feed
356
);
357
358
console.log('Documentation post-processing complete');
359
});
360
```
361
362
**Complete Event Handling Example:**
363
364
```typescript
365
import {
366
MarkdownPageEvent,
367
MarkdownRendererEvent,
368
MarkdownThemeContext
369
} from "typedoc-plugin-markdown";
370
371
class DocumentationProcessor {
372
setupEventHandlers(renderer: MarkdownRenderer) {
373
// Page-level event handling
374
renderer.on(MarkdownPageEvent.BEGIN, this.handlePageBegin.bind(this));
375
renderer.on(MarkdownPageEvent.END, this.handlePageEnd.bind(this));
376
377
// Renderer-level event handling
378
renderer.on(MarkdownRendererEvent.BEGIN, this.handleRenderBegin.bind(this));
379
renderer.on(MarkdownRendererEvent.END, this.handleRenderEnd.bind(this));
380
381
// Content injection hooks
382
renderer.markdownHooks.on('page.begin', this.injectPageHeader.bind(this));
383
renderer.markdownHooks.on('page.end', this.injectPageFooter.bind(this));
384
renderer.markdownHooks.on('content.begin', this.injectTOC.bind(this));
385
386
// Async jobs
387
renderer.preRenderAsyncJobs.push(this.preRenderSetup.bind(this));
388
renderer.postRenderAsyncJobs.push(this.postRenderCleanup.bind(this));
389
}
390
391
private handlePageBegin(page: MarkdownPageEvent) {
392
console.log(`Rendering page: ${page.filename}`);
393
394
// Set up frontmatter
395
page.frontmatter = {
396
title: this.getPageTitle(page.model),
397
type: page.pageKind,
398
generated: new Date().toISOString()
399
};
400
}
401
402
private handlePageEnd(page: MarkdownPageEvent) {
403
// Validate generated content
404
this.validatePageContent(page);
405
406
// Add analytics tracking
407
page.contents += this.getAnalyticsCode(page);
408
}
409
410
private async preRenderSetup(event: MarkdownRendererEvent) {
411
// Initialize external services
412
await this.initializeServices();
413
414
// Pre-process project data
415
this.processProjectMetadata(event.project);
416
}
417
418
private async postRenderCleanup(event: MarkdownRendererEvent) {
419
// Generate additional files
420
await this.generateSupportingFiles(event);
421
422
// Clean up temporary resources
423
await this.cleanup();
424
}
425
}
426
```