UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

377 lines (376 loc) 22.3 kB
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; /** * Holds all logic used render and output the final documentation. * * The {@link Renderer} class is the central controller within this namespace. When invoked it creates * an instance of {@link Theme} which defines the layout of the documentation and fires a * series of {@link RendererEvent} events. Instances of {@link BasePlugin} can listen to these events and * alter the generated output. */ import * as fs from "fs"; import * as path from "path"; import { RendererEvent, PageEvent, IndexEvent, } from "./events.js"; import { writeFileSync } from "../utils/fs.js"; import { DefaultTheme } from "./themes/default/DefaultTheme.js"; import { Option, EventHooks, AbstractComponent } from "../utils/index.js"; import { loadHighlighter } from "../utils/highlighter.js"; import { Reflection } from "../models/index.js"; import { setRenderSettings } from "../utils/jsx.js"; import { AssetsPlugin, HierarchyPlugin, IconsPlugin, JavascriptIndexPlugin, MarkedPlugin, NavigationPlugin, SitemapPlugin, } from "./plugins/index.js"; /** * The renderer processes a {@link ProjectReflection} using a {@link Theme} instance and writes * the emitted html documents to a output directory. You can specify which theme should be used * using the `--theme <name>` command line argument. * * {@link Renderer} is a subclass of {@link EventDispatcher} and triggers a series of events while * a project is being processed. You can listen to these events to control the flow or manipulate * the output. * * * {@link Renderer.EVENT_BEGIN}<br> * Triggered before the renderer starts rendering a project. The listener receives * an instance of {@link RendererEvent}. * * * {@link Renderer.EVENT_BEGIN_PAGE}<br> * Triggered before a document will be rendered. The listener receives an instance of * {@link PageEvent}. * * * {@link Renderer.EVENT_END_PAGE}<br> * Triggered after a document has been rendered, just before it is written to disc. The * listener receives an instance of {@link PageEvent}. * * * {@link Renderer.EVENT_END}<br> * Triggered after the renderer has written all documents. The listener receives * an instance of {@link RendererEvent}. * * * {@link Renderer.EVENT_PREPARE_INDEX}<br> * Triggered when the JavascriptIndexPlugin is preparing the search index. Listeners receive * an instance of {@link IndexEvent}. * * @summary Writes HTML output from TypeDoc's models * @group Common */ let Renderer = (() => { let _classSuper = AbstractComponent; let _themeName_decorators; let _themeName_initializers = []; let _themeName_extraInitializers = []; let _cleanOutputDir_decorators; let _cleanOutputDir_initializers = []; let _cleanOutputDir_extraInitializers = []; let _cname_decorators; let _cname_initializers = []; let _cname_extraInitializers = []; let _githubPages_decorators; let _githubPages_initializers = []; let _githubPages_extraInitializers = []; let _cacheBust_decorators; let _cacheBust_initializers = []; let _cacheBust_extraInitializers = []; let _lightTheme_decorators; let _lightTheme_initializers = []; let _lightTheme_extraInitializers = []; let _darkTheme_decorators; let _darkTheme_initializers = []; let _darkTheme_extraInitializers = []; let _highlightLanguages_decorators; let _highlightLanguages_initializers = []; let _highlightLanguages_extraInitializers = []; let _ignoredHighlightLanguages_decorators; let _ignoredHighlightLanguages_initializers = []; let _ignoredHighlightLanguages_extraInitializers = []; let _pretty_decorators; let _pretty_initializers = []; let _pretty_extraInitializers = []; return class Renderer extends _classSuper { static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _themeName_decorators = [Option("theme")]; _cleanOutputDir_decorators = [Option("cleanOutputDir")]; _cname_decorators = [Option("cname")]; _githubPages_decorators = [Option("githubPages")]; _cacheBust_decorators = [Option("cacheBust")]; _lightTheme_decorators = [Option("lightHighlightTheme")]; _darkTheme_decorators = [Option("darkHighlightTheme")]; _highlightLanguages_decorators = [Option("highlightLanguages")]; _ignoredHighlightLanguages_decorators = [Option("ignoredHighlightLanguages")]; _pretty_decorators = [Option("pretty")]; __esDecorate(this, null, _themeName_decorators, { kind: "accessor", name: "themeName", static: false, private: false, access: { has: obj => "themeName" in obj, get: obj => obj.themeName, set: (obj, value) => { obj.themeName = value; } }, metadata: _metadata }, _themeName_initializers, _themeName_extraInitializers); __esDecorate(this, null, _cleanOutputDir_decorators, { kind: "accessor", name: "cleanOutputDir", static: false, private: false, access: { has: obj => "cleanOutputDir" in obj, get: obj => obj.cleanOutputDir, set: (obj, value) => { obj.cleanOutputDir = value; } }, metadata: _metadata }, _cleanOutputDir_initializers, _cleanOutputDir_extraInitializers); __esDecorate(this, null, _cname_decorators, { kind: "accessor", name: "cname", static: false, private: false, access: { has: obj => "cname" in obj, get: obj => obj.cname, set: (obj, value) => { obj.cname = value; } }, metadata: _metadata }, _cname_initializers, _cname_extraInitializers); __esDecorate(this, null, _githubPages_decorators, { kind: "accessor", name: "githubPages", static: false, private: false, access: { has: obj => "githubPages" in obj, get: obj => obj.githubPages, set: (obj, value) => { obj.githubPages = value; } }, metadata: _metadata }, _githubPages_initializers, _githubPages_extraInitializers); __esDecorate(this, null, _cacheBust_decorators, { kind: "accessor", name: "cacheBust", static: false, private: false, access: { has: obj => "cacheBust" in obj, get: obj => obj.cacheBust, set: (obj, value) => { obj.cacheBust = value; } }, metadata: _metadata }, _cacheBust_initializers, _cacheBust_extraInitializers); __esDecorate(this, null, _lightTheme_decorators, { kind: "accessor", name: "lightTheme", static: false, private: false, access: { has: obj => "lightTheme" in obj, get: obj => obj.lightTheme, set: (obj, value) => { obj.lightTheme = value; } }, metadata: _metadata }, _lightTheme_initializers, _lightTheme_extraInitializers); __esDecorate(this, null, _darkTheme_decorators, { kind: "accessor", name: "darkTheme", static: false, private: false, access: { has: obj => "darkTheme" in obj, get: obj => obj.darkTheme, set: (obj, value) => { obj.darkTheme = value; } }, metadata: _metadata }, _darkTheme_initializers, _darkTheme_extraInitializers); __esDecorate(this, null, _highlightLanguages_decorators, { kind: "accessor", name: "highlightLanguages", static: false, private: false, access: { has: obj => "highlightLanguages" in obj, get: obj => obj.highlightLanguages, set: (obj, value) => { obj.highlightLanguages = value; } }, metadata: _metadata }, _highlightLanguages_initializers, _highlightLanguages_extraInitializers); __esDecorate(this, null, _ignoredHighlightLanguages_decorators, { kind: "accessor", name: "ignoredHighlightLanguages", static: false, private: false, access: { has: obj => "ignoredHighlightLanguages" in obj, get: obj => obj.ignoredHighlightLanguages, set: (obj, value) => { obj.ignoredHighlightLanguages = value; } }, metadata: _metadata }, _ignoredHighlightLanguages_initializers, _ignoredHighlightLanguages_extraInitializers); __esDecorate(this, null, _pretty_decorators, { kind: "accessor", name: "pretty", static: false, private: false, access: { has: obj => "pretty" in obj, get: obj => obj.pretty, set: (obj, value) => { obj.pretty = value; } }, metadata: _metadata }, _pretty_initializers, _pretty_extraInitializers); if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } themes = new Map([ ["default", DefaultTheme], ]); /** @event */ static EVENT_BEGIN_PAGE = PageEvent.BEGIN; /** @event */ static EVENT_END_PAGE = PageEvent.END; /** @event */ static EVENT_BEGIN = RendererEvent.BEGIN; /** @event */ static EVENT_END = RendererEvent.END; /** @event */ static EVENT_PREPARE_INDEX = IndexEvent.PREPARE_INDEX; /** * A list of async jobs which must be completed *before* rendering output. * They will be called after {@link RendererEvent.BEGIN} has fired, but before any files have been written. * * This may be used by plugins to register work that must be done to prepare output files. For example: asynchronously * transform markdown to HTML. * * Note: This array is cleared after calling the contained functions on each {@link Renderer.render} call. */ preRenderAsyncJobs = []; /** * A list of async jobs which must be completed after rendering output files but before generation is considered successful. * These functions will be called after all documents have been written to the filesystem. * * This may be used by plugins to register work that must be done to finalize output files. For example: asynchronously * generating an image referenced in a render hook. * * Note: This array is cleared after calling the contained functions on each {@link Renderer.render} call. */ postRenderAsyncJobs = []; /** * The theme that is used to render the documentation. */ theme; /** * Hooks which will be called when rendering pages. * Note: * - Hooks added during output will be discarded at the end of rendering. * - Hooks added during a page render will be discarded at the end of that page's render. * * See {@link RendererHooks} for a description of each available hook, and when it will be called. */ hooks = new EventHooks(); #themeName_accessor_storage = __runInitializers(this, _themeName_initializers, void 0); /** @internal */ get themeName() { return this.#themeName_accessor_storage; } set themeName(value) { this.#themeName_accessor_storage = value; } #cleanOutputDir_accessor_storage = (__runInitializers(this, _themeName_extraInitializers), __runInitializers(this, _cleanOutputDir_initializers, void 0)); get cleanOutputDir() { return this.#cleanOutputDir_accessor_storage; } set cleanOutputDir(value) { this.#cleanOutputDir_accessor_storage = value; } #cname_accessor_storage = (__runInitializers(this, _cleanOutputDir_extraInitializers), __runInitializers(this, _cname_initializers, void 0)); get cname() { return this.#cname_accessor_storage; } set cname(value) { this.#cname_accessor_storage = value; } #githubPages_accessor_storage = (__runInitializers(this, _cname_extraInitializers), __runInitializers(this, _githubPages_initializers, void 0)); get githubPages() { return this.#githubPages_accessor_storage; } set githubPages(value) { this.#githubPages_accessor_storage = value; } #cacheBust_accessor_storage = (__runInitializers(this, _githubPages_extraInitializers), __runInitializers(this, _cacheBust_initializers, void 0)); /** @internal */ get cacheBust() { return this.#cacheBust_accessor_storage; } set cacheBust(value) { this.#cacheBust_accessor_storage = value; } #lightTheme_accessor_storage = (__runInitializers(this, _cacheBust_extraInitializers), __runInitializers(this, _lightTheme_initializers, void 0)); get lightTheme() { return this.#lightTheme_accessor_storage; } set lightTheme(value) { this.#lightTheme_accessor_storage = value; } #darkTheme_accessor_storage = (__runInitializers(this, _lightTheme_extraInitializers), __runInitializers(this, _darkTheme_initializers, void 0)); get darkTheme() { return this.#darkTheme_accessor_storage; } set darkTheme(value) { this.#darkTheme_accessor_storage = value; } #highlightLanguages_accessor_storage = (__runInitializers(this, _darkTheme_extraInitializers), __runInitializers(this, _highlightLanguages_initializers, void 0)); get highlightLanguages() { return this.#highlightLanguages_accessor_storage; } set highlightLanguages(value) { this.#highlightLanguages_accessor_storage = value; } #ignoredHighlightLanguages_accessor_storage = (__runInitializers(this, _highlightLanguages_extraInitializers), __runInitializers(this, _ignoredHighlightLanguages_initializers, void 0)); get ignoredHighlightLanguages() { return this.#ignoredHighlightLanguages_accessor_storage; } set ignoredHighlightLanguages(value) { this.#ignoredHighlightLanguages_accessor_storage = value; } #pretty_accessor_storage = (__runInitializers(this, _ignoredHighlightLanguages_extraInitializers), __runInitializers(this, _pretty_initializers, void 0)); get pretty() { return this.#pretty_accessor_storage; } set pretty(value) { this.#pretty_accessor_storage = value; } renderStartTime = (__runInitializers(this, _pretty_extraInitializers), -1); markedPlugin; constructor(owner) { super(owner); this.markedPlugin = new MarkedPlugin(this); new AssetsPlugin(this); new IconsPlugin(this); new HierarchyPlugin(this); new JavascriptIndexPlugin(this); new NavigationPlugin(this); new SitemapPlugin(this); } /** * Define a new theme that can be used to render output. * This API will likely be changing at some point, to allow more easily overriding parts of the theme without * requiring additional boilerplate. * @param name * @param theme */ defineTheme(name, theme) { if (this.themes.has(name)) { throw new Error(`The theme "${name}" has already been defined.`); } this.themes.set(name, theme); } /** * Render the given project reflection to the specified output directory. * * @param project The project that should be rendered. * @param outputDirectory The path of the directory the documentation should be rendered to. */ async render(project, outputDirectory) { setRenderSettings({ pretty: this.pretty }); const momento = this.hooks.saveMomento(); this.renderStartTime = Date.now(); if (!this.prepareTheme() || !(await this.prepareOutputDirectory(outputDirectory))) { return; } const output = new RendererEvent(outputDirectory, project); output.urls = this.theme.getUrls(project); this.trigger(RendererEvent.BEGIN, output); await this.runPreRenderJobs(output); this.application.logger.verbose(`There are ${output.urls.length} pages to write.`); output.urls.forEach((mapping) => { this.renderDocument(...output.createPageEvent(mapping)); }); await Promise.all(this.postRenderAsyncJobs.map((job) => job(output))); this.postRenderAsyncJobs = []; this.trigger(RendererEvent.END, output); this.theme = void 0; this.hooks.restoreMomento(momento); } async runPreRenderJobs(output) { const start = Date.now(); this.preRenderAsyncJobs.push(this.loadHighlighter.bind(this)); await Promise.all(this.preRenderAsyncJobs.map((job) => job(output))); this.preRenderAsyncJobs = []; this.application.logger.verbose(`Pre render async jobs took ${Date.now() - start}ms`); } async loadHighlighter() { await loadHighlighter(this.lightTheme, this.darkTheme, // Checked in option validation this.highlightLanguages, this.ignoredHighlightLanguages); } /** * Render a single page. * * @param page An event describing the current page. * @return TRUE if the page has been saved to disc, otherwise FALSE. */ renderDocument(template, page) { const momento = this.hooks.saveMomento(); this.trigger(PageEvent.BEGIN, page); if (page.model instanceof Reflection) { page.contents = this.theme.render(page, template); } else { throw new Error("Should be unreachable"); } this.trigger(PageEvent.END, page); this.hooks.restoreMomento(momento); try { writeFileSync(page.filename, page.contents); } catch (error) { this.application.logger.error(this.application.i18n.could_not_write_0(page.filename)); } } /** * Ensure that a theme has been setup. * * If a the user has set a theme we try to find and load it. If no theme has * been specified we load the default theme. * * @returns TRUE if a theme has been setup, otherwise FALSE. */ prepareTheme() { if (!this.theme) { const ctor = this.themes.get(this.themeName); if (!ctor) { this.application.logger.error(this.application.i18n.theme_0_is_not_defined_available_are_1(this.themeName, [...this.themes.keys()].join(", "))); return false; } else { this.theme = new ctor(this); } } return true; } /** * Prepare the output directory. If the directory does not exist, it will be * created. If the directory exists, it will be emptied. * * @param directory The path to the directory that should be prepared. * @returns TRUE if the directory could be prepared, otherwise FALSE. */ async prepareOutputDirectory(directory) { if (this.cleanOutputDir) { try { await fs.promises.rm(directory, { recursive: true, force: true, }); } catch (error) { this.application.logger.warn(this.application.i18n.could_not_empty_output_directory_0(directory)); return false; } } try { fs.mkdirSync(directory, { recursive: true }); } catch (error) { this.application.logger.error(this.application.i18n.could_not_create_output_directory_0(directory)); return false; } if (this.githubPages) { try { const text = "TypeDoc added this file to prevent GitHub Pages from " + "using Jekyll. You can turn off this behavior by setting " + "the `githubPages` option to false."; fs.writeFileSync(path.join(directory, ".nojekyll"), text); } catch (error) { this.application.logger.warn(this.application.i18n.could_not_write_0(path.join(directory, ".nojekyll"))); return false; } } if (this.cname) { fs.writeFileSync(path.join(directory, "CNAME"), this.cname); } return true; } }; })(); export { Renderer };