Skip to content

Commit 096df5e

Browse files
committed
Introduce --router option
Resolves #2111
1 parent c1ebf14 commit 096df5e

File tree

12 files changed

+613
-76
lines changed

12 files changed

+613
-76
lines changed

CHANGELOG.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ title: Changelog
44

55
## Beta
66

7+
- Added a `--router` option which can be used to modify TypeDoc's output folder
8+
structure. This can be extended with plugins.
79
- TypeDoc will now only create references for symbols re-exported from modules.
810
- API: Introduced a `Router` which is used for URL creation. `Reflection.url`,
911
`Reflection.anchor`, and `Reflection.hasOwnDocument` have been removed.
1012

11-
TODO:
12-
13-
- Add option for choosing router
14-
1513
## Unreleased
1614

1715
## v0.27.5 (2024-12-14)

site/options/output.md

+70
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,76 @@ $ typedoc --theme default
124124

125125
Specify the theme name that should be used.
126126

127+
## router
128+
129+
```bash
130+
$ typedoc --router default
131+
```
132+
133+
Specify the router that should be used to determine what files to create for the
134+
HTML output and how to link between pages. Additional routers may be added by
135+
plugins/themes. TypeDoc ships with the following builtin routers:
136+
137+
- **kind** (default) - Creates folders according to their the documented member's kind.
138+
- **kind-dir** - Like **kind**, but renders each page as `index.html` within a directory for the page name. This can be used to make "clean" urls.
139+
- **structure** - Creates folders according to the module structure.
140+
- **structure-dir** - Like **structure**, but renders each page as `index.html` within a directory for the page name. This can be used to make "clean" urls.
141+
- **group** - Creates folders according to the reflection's [`@group`](../tags/group.md).
142+
- **category** - Creates folders according to the reflection's [`@category`](../tags/category.md).
143+
144+
This is easiest to understand with an example. Given the following API:
145+
146+
```ts
147+
export function initialize(): void;
148+
/** @group Opts */
149+
export class Options {}
150+
export namespace TypeDoc {
151+
export const VERSION: string;
152+
}
153+
```
154+
155+
TypeDoc will create a folder structure resembling the following, the common
156+
`assets` folder and `index.html` / `modules.html` files have been omitted for
157+
brevity.
158+
159+
**kind**
160+
161+
```text
162+
docs
163+
├── classes
164+
│ └── Options.html
165+
├── functions
166+
│ └── initialize.html
167+
├── modules
168+
│ └── TypeDoc.html
169+
└── variables
170+
└── TypeDoc.VERSION.html
171+
```
172+
173+
**structure**
174+
175+
```text
176+
├── initialize.html
177+
├── Options.html
178+
├── TypeDoc
179+
│ └── VERSION.html
180+
└── TypeDoc.html
181+
```
182+
183+
**groups**
184+
185+
```text
186+
docs
187+
├── Opts
188+
│ └── Options.html
189+
├── Functions
190+
│ └── initialize.html
191+
├── Namespaces
192+
│ └── TypeDoc.html
193+
└── Variables
194+
└── TypeDoc.VERSION.html
195+
```
196+
127197
## lightHighlightTheme
128198

129199
```bash

src/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ export {
5656
RendererEvent,
5757
MarkdownEvent,
5858
IndexEvent,
59-
DefaultRouter,
59+
BaseRouter,
60+
KindRouter,
61+
KindDirRouter,
62+
StructureRouter,
63+
StructureDirRouter,
64+
GroupRouter,
65+
CategoryRouter,
6066
PageKind,
6167
} from "./lib/output/index.js";
6268
export type {

src/lib/internationalization/locales/en.cts

+3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export = {
132132
could_not_empty_output_directory_0: `Could not empty the output directory {0}`,
133133
could_not_create_output_directory_0: `Could not create the output directory {0}`,
134134
theme_0_is_not_defined_available_are_1: `The theme '{0}' is not defined. The available themes are: {1}`,
135+
router_0_is_not_defined_available_are_1: `The router '{0}' is not defined. The available routers are: {1}`,
135136
reflection_0_links_to_1_but_anchor_does_not_exist_try_2: `{0} links to {1}, but the anchor does not exist. You may have meant:\n\t{2}`,
136137

137138
// entry points
@@ -232,6 +233,8 @@ export = {
232233
"Specify whether the output JSON should be formatted with tabs",
233234
help_emit: "Specify what TypeDoc should emit, 'docs', 'both', or 'none'",
234235
help_theme: "Specify the theme name to render the documentation with",
236+
help_router:
237+
"Specify the router name to use to determine file names in the documentation",
235238
help_lightHighlightTheme:
236239
"Specify the code highlighting theme in light mode",
237240
help_darkHighlightTheme: "Specify the code highlighting theme in dark mode",

src/lib/output/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ export { DefaultThemeRenderContext } from "./themes/default/DefaultThemeRenderCo
1818
export { Slugger } from "./themes/default/Slugger.js";
1919

2020
export {
21-
DefaultRouter,
21+
BaseRouter,
22+
KindRouter,
23+
KindDirRouter,
24+
StructureRouter,
25+
StructureDirRouter,
26+
GroupRouter,
27+
CategoryRouter,
2228
PageKind,
2329
type PageDefinition,
2430
type Router,

src/lib/output/renderer.ts

+41-14
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ import {
4040
NavigationPlugin,
4141
SitemapPlugin,
4242
} from "./plugins/index.js";
43-
import { DefaultRouter, type PageDefinition, type Router } from "./router.js";
43+
import {
44+
CategoryRouter,
45+
GroupRouter,
46+
KindDirRouter,
47+
KindRouter,
48+
StructureDirRouter,
49+
StructureRouter,
50+
type PageDefinition,
51+
type Router,
52+
} from "./router.js";
4453

4554
/**
4655
* Describes the hooks available to inject output in the default theme.
@@ -169,7 +178,12 @@ export interface RendererEvents {
169178
*/
170179
export class Renderer extends AbstractComponent<Application, RendererEvents> {
171180
private routers = new Map<string, new (app: Application) => Router>([
172-
["default", DefaultRouter],
181+
["kind", KindRouter],
182+
["structure", StructureRouter],
183+
["kind-dir", KindDirRouter],
184+
["structure-dir", StructureDirRouter],
185+
["group", GroupRouter],
186+
["category", CategoryRouter],
173187
]);
174188

175189
private themes = new Map<string, new (renderer: Renderer) => Theme>([
@@ -235,6 +249,10 @@ export class Renderer extends AbstractComponent<Application, RendererEvents> {
235249
@Option("theme")
236250
private accessor themeName!: string;
237251

252+
/** @internal */
253+
@Option("router")
254+
private accessor routerName!: string;
255+
238256
@Option("cleanOutputDir")
239257
private accessor cleanOutputDir!: boolean;
240258

@@ -317,17 +335,15 @@ export class Renderer extends AbstractComponent<Application, RendererEvents> {
317335
const momento = this.hooks.saveMomento();
318336
this.renderStartTime = Date.now();
319337

320-
// GERRIT: Support user input
321-
this.router = new (this.routers.get("default")!)(this.application);
322-
323338
if (
339+
!this.prepareRouter() ||
324340
!this.prepareTheme() ||
325341
!(await this.prepareOutputDirectory(outputDirectory))
326342
) {
327343
return;
328344
}
329345

330-
const pages = this.router.buildPages(project);
346+
const pages = this.router!.buildPages(project);
331347

332348
const output = new RendererEvent(outputDirectory, project, pages);
333349
this.trigger(RendererEvent.BEGIN, output);
@@ -402,14 +418,25 @@ export class Renderer extends AbstractComponent<Application, RendererEvents> {
402418
}
403419
}
404420

405-
/**
406-
* Ensure that a theme has been setup.
407-
*
408-
* If a the user has set a theme we try to find and load it. If no theme has
409-
* been specified we load the default theme.
410-
*
411-
* @returns TRUE if a theme has been setup, otherwise FALSE.
412-
*/
421+
private prepareRouter(): boolean {
422+
if (!this.theme) {
423+
const ctor = this.routers.get(this.routerName);
424+
if (!ctor) {
425+
this.application.logger.error(
426+
this.application.i18n.router_0_is_not_defined_available_are_1(
427+
this.routerName,
428+
[...this.routers.keys()].join(", "),
429+
),
430+
);
431+
return false;
432+
} else {
433+
this.router = new ctor(this.application);
434+
}
435+
}
436+
437+
return true;
438+
}
439+
413440
private prepareTheme(): boolean {
414441
if (!this.theme) {
415442
const ctor = this.themes.get(this.themeName);

0 commit comments

Comments
 (0)