{"$schema":"https://doc-kit.nodejs.org/schemas/api-doc/1.0.1.json","id":"creating-generators","path":"/creating-generators","type":"module","module":"creating-generators","title":"Creating generators","introducedIn":null,"sourceLink":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Every output of doc-kit is a generator, and yours can be one too: an RSS\nfeed, a changelog, TypeScript declarations, anything the API entries can\nbecome. This guide starts with a generator in one file, then adds options,\nworker threads and streaming, and ends with publishing it as a package.","summary":"Every output of doc-kit is a generator, and yours can be one too: an RSS feed, a changelog, TypeScript declarations, anything the API entries can become. This guide starts with a generator in one file, then adds options, worker threads and streaming, and ends with publishing it as a package.","examples":[],"children":[{"kind":"section","id":"your-first-generator","name":"Your first generator","title":"Your first generator","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"A generator is a module whose default export gives its `name`, the generator\nit reads from (`dependsOn`), and a `generate` function that receives that\ngenerator's output. This one lists every experimental API:\n\n```mjs displayName=\"experimental.mjs\"\nimport { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n\n  // The typed API entries, the same the site and the JSON are built from\n  dependsOn: '@doc-kit/core/metadata',\n\n  async generate(entries) {\n    const { output } = getConfig('experimental');\n\n    const list = entries\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `- ${entry.heading.data.name} (${entry.api})`);\n\n    await writeFile(join(output, 'experimental.md'), list.join('\\n'));\n  },\n};\n```\n\nPass its path as a [target](./cli.html#doc-kit-generate), next to any others.\nHere it runs next to the website (`html`), which comes from\n`@doc-kit/generator-react`, so [install](./getting-started.html#install) that\npackage beside the CLI:\n\n```bash\nnpx doc-kit generate -t html -t ./experimental.mjs -i \"docs/**/*.md\" -o out\n```\n\nIt reads the same parse of the docs as `html`, and writes\n`out/experimental.md`. (With pnpm, add `@doc-kit/core` to your dependencies\nso the module can import it.)","summary":"A generator is a module whose default export gives its `name`, the generator it reads from (`dependsOn`), and a `generate` function that receives that generator's output. This one lists every experimental API:","examples":[{"language":"mjs","displayName":"experimental.mjs","code":"import { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n\n  // The typed API entries, the same the site and the JSON are built from\n  dependsOn: '@doc-kit/core/metadata',\n\n  async generate(entries) {\n    const { output } = getConfig('experimental');\n\n    const list = entries\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `- ${entry.heading.data.name} (${entry.api})`);\n\n    await writeFile(join(output, 'experimental.md'), list.join('\\n'));\n  },\n};"},{"language":"bash","displayName":null,"code":"npx doc-kit generate -t html -t ./experimental.mjs -i \"docs/**/*.md\" -o out"}],"children":[]},{"kind":"section","id":"how-generators-fit-together","name":"How generators fit together","title":"How generators fit together","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"Each generator declares the generator it reads from with `dependsOn`. doc-kit\nfollows those links from the targets it's given, loads every generator they\nneed, and runs the ones that don't depend on one another at the same time.\nThe shared stages are documented with the rest of the\n[generators](./generators.html#pipeline-stages).\n\nBesides `name`, `dependsOn` and `generate`, a generator can declare default\noptions, a function to run in worker threads, and a Markdown pipeline. The\nrest of this guide covers them one at a time, and\n[`@doc-kit/core`](./packages/core.html#writing-a-generator) lists every property\nin one place.","summary":"Each generator declares the generator it reads from with `dependsOn`. doc-kit follows those links from the targets it's given, loads every generator they need, and runs the ones that don't depend on one another at the same time. The shared stages are documented with the rest of the generators.","examples":[],"children":[]},{"kind":"section","id":"configuration","name":"Configuration","title":"Configuration","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`defaultConfiguration` holds the defaults of the generator's options. Users\noverride them in a section named after the generator in their\n[configuration file](./configuration.html), and `getConfig(name)` returns the\nresult:\n\n```mjs displayName=\"experimental.mjs\"\nimport { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  defaultConfiguration: {\n    // The file the list is written to, in the output directory\n    fileName: 'experimental.md',\n  },\n\n  async generate(entries) {\n    const { output, fileName } = getConfig('experimental');\n\n    const list = entries\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `- ${entry.heading.data.name} (${entry.api})`);\n\n    await writeFile(join(output, fileName), list.join('\\n'));\n  },\n};\n```\n\n```mjs displayName=\"doc-kit.config.mjs\"\nexport default {\n  experimental: {\n    fileName: 'unstable.md',\n  },\n};\n```\n\n* `getConfig(name)` is the default export of\n  `@doc-kit/core/utils/configuration/index.mjs`. Besides the generator's own\n  options, what it returns holds every\n  [global option](./configuration.html#global-options) the generator doesn't\n  set itself: `output`, `project`, `minify`, `ref`, and `version` (a `SemVer`\n  from the `semver` package) among them.\n* A default for a global option wins over the global value for that\n  generator: `jsx-ast` defaults `ref` to `'main'`. Users still change it in\n  the generator's own section.\n* `defaultConfiguration` can also be a function, which receives the user's\n  configuration (the configuration file and command-line options, with\n  targets resolved to import specifiers) and returns the defaults. The\n  [`html`](./generators/html.html) generator uses one to show its search box\n  only when the search index is also a target.\n* `getConfig()` without a name returns the whole configuration on the main\n  thread. [Worker threads](#worker-threads) only get the generator's own\n  section.","summary":"`defaultConfiguration` holds the defaults of the generator's options. Users override them in a section named after the generator in their configuration file, and `getConfig(name)` returns the result:","examples":[{"language":"mjs","displayName":"experimental.mjs","code":"import { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  defaultConfiguration: {\n    // The file the list is written to, in the output directory\n    fileName: 'experimental.md',\n  },\n\n  async generate(entries) {\n    const { output, fileName } = getConfig('experimental');\n\n    const list = entries\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `- ${entry.heading.data.name} (${entry.api})`);\n\n    await writeFile(join(output, fileName), list.join('\\n'));\n  },\n};"},{"language":"mjs","displayName":"doc-kit.config.mjs","code":"export default {\n  experimental: {\n    fileName: 'unstable.md',\n  },\n};"}],"children":[]},{"kind":"section","id":"input-and-output","name":"Input and output","title":"Input and output","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`generate(input, worker)` receives the complete output of the generator it\ndepends on. When that generator streams, its chunks arrive collected in one\narray. These are the stages most generators read from:\n\n| `dependsOn`                        | `input`                                                                                   |\n| ---------------------------------- | ----------------------------------------------------------------------------------------- |\n| `@doc-kit/core/metadata`           | The [API entries](./generators/metadata.html), one per heading. Most generators use this. |\n| `@doc-kit/core/ast`                | The [Markdown files](./generators/ast.html), each parsed as `{ tree, path, mdx }`.        |\n| `@doc-kit/core/ast-js`             | The [`.js` files](./generators/ast-js.html) among the inputs, each parsed to ESTree.      |\n| `@doc-kit/generator-react/jsx-ast` | The [pages](./generators/jsx-ast.html), each with its content compiled to JSX code.       |\n\nTo read from a generator of your own, point `dependsOn` at its module with\n`import.meta.resolve('./other.mjs')`, since a relative path resolves from the\nworking directory. Without a `dependsOn`, `generate` receives `undefined` and\nreads its own sources, as [`ast`](./generators/ast.html) does.\n\nWhat `generate` returns, or resolves to, is what the generators depending on\nit receive. The CLI doesn't print or save a target's result, so a generator\nproducing an output writes its files itself. Many do both:\n\n```mjs displayName=\"experimental.mjs\"\nimport { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeJSON } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  async generate(entries) {\n    const { output, minify } = getConfig('experimental');\n\n    const experimental = entries.filter(\n      entry => entry.stability?.data.index === '1'\n    );\n\n    if (output) {\n      await writeJSON(join(output, 'experimental.json'), experimental, minify);\n    }\n\n    // What the generators reading from this one receive\n    return experimental;\n  },\n};\n```\n\n`@doc-kit/core/utils/file.mjs` has two helpers for writing:\n\n* `writeFile(file, data[, options])` writes a file as\n  `fs.promises.writeFile()` does, creating its directory first.\n* `writeJSON(file, value[, minify])` writes a value as JSON, indented unless\n  `minify` is `true`.\n\n`output` is `undefined` when no output directory is given, which is why the\nbuilt-in generators check it before writing. To copy static files that ship\nwith your module, use `cp()` from `node:fs/promises`:\n\n```mjs\nawait cp(new URL('./assets', import.meta.url), join(output, 'assets'), {\n  recursive: true,\n});\n```","summary":"`generate(input, worker)` receives the complete output of the generator it depends on. When that generator streams, its chunks arrive collected in one array. These are the stages most generators read from:","examples":[{"language":"mjs","displayName":"experimental.mjs","code":"import { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeJSON } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  async generate(entries) {\n    const { output, minify } = getConfig('experimental');\n\n    const experimental = entries.filter(\n      entry => entry.stability?.data.index === '1'\n    );\n\n    if (output) {\n      await writeJSON(join(output, 'experimental.json'), experimental, minify);\n    }\n\n    // What the generators reading from this one receive\n    return experimental;\n  },\n};"},{"language":"mjs","displayName":null,"code":"await cp(new URL('./assets', import.meta.url), join(output, 'assets'), {\n  recursive: true,\n});"}],"children":[]},{"kind":"section","id":"worker-threads","name":"Worker threads","title":"Worker threads","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"For heavy work on many independent items, give the generator a\n`processChunk` and set `hasParallelProcessor`. `generate` then receives a\n`worker`, whose `stream()` splits the items into chunks and runs\n`processChunk` on them across worker threads:\n\n```mjs displayName=\"experimental.mjs\"\nimport { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  hasParallelProcessor: true,\n\n  // Runs in a worker thread, once per chunk of entries\n  async processChunk(input, itemIndices, { marker }) {\n    return itemIndices\n      .map(index => input[index])\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `${marker} ${entry.heading.data.name} (${entry.api})`);\n  },\n\n  async generate(entries, worker) {\n    const { output } = getConfig('experimental');\n    const lines = [];\n\n    for await (const chunk of worker.stream(entries, { marker: '-' })) {\n      lines.push(...chunk);\n    }\n\n    // Chunks arrive in the order they finish\n    lines.sort();\n\n    await writeFile(join(output, 'experimental.md'), lines.join('\\n'));\n  },\n};\n```\n\nFiltering entries is light work, but heavier work takes the same shape.\n\n* `worker.stream(items, deps)` splits `items` into chunks of\n  [`chunkSize`](./configuration.html#execution-options) items (10 by default),\n  runs them on up to `threads` workers, and yields each chunk's result as it\n  finishes, in the order they finish. Every `processChunk` call gets `deps`.\n* `processChunk(input, itemIndices, deps)` processes the items at\n  `itemIndices` and resolves to an array of results. Read each item as\n  `input[index]`: in a worker, `input` holds only the chunk's items, but with\n  one thread, or two items or fewer, `processChunk` runs on the main thread\n  with the whole input.\n* The items, `deps`, the results, and the generator's configuration section\n  are copied between threads with the\n  [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).\n  Functions can't be copied (passing one throws a `DataCloneError`), and class\n  instances arrive as plain objects.\n* Each worker imports your module on its own, so `processChunk` sees nothing\n  `generate` set up: pass what it needs in `deps`. A module only\n  `processChunk` uses can be imported inside it, as the `metadata` generator\n  does, so the main thread never loads it.\n* In a worker, `getConfig(name)` returns the generator's own section, with\n  the global options it inherits, and nothing else. Its `version` is a plain\n  object there, not a `SemVer`.","summary":"For heavy work on many independent items, give the generator a `processChunk` and set `hasParallelProcessor`. `generate` then receives a `worker`, whose `stream()` splits the items into chunks and runs `processChunk` on them across worker threads:","examples":[{"language":"mjs","displayName":"experimental.mjs","code":"import { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\n\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  hasParallelProcessor: true,\n\n  // Runs in a worker thread, once per chunk of entries\n  async processChunk(input, itemIndices, { marker }) {\n    return itemIndices\n      .map(index => input[index])\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `${marker} ${entry.heading.data.name} (${entry.api})`);\n  },\n\n  async generate(entries, worker) {\n    const { output } = getConfig('experimental');\n    const lines = [];\n\n    for await (const chunk of worker.stream(entries, { marker: '-' })) {\n      lines.push(...chunk);\n    }\n\n    // Chunks arrive in the order they finish\n    lines.sort();\n\n    await writeFile(join(output, 'experimental.md'), lines.join('\\n'));\n  },\n};"}],"children":[]},{"kind":"section","id":"streaming-or-collecting","name":"Streaming or collecting","title":"Streaming or collecting","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`generate` either resolves to its whole output, as in the examples above, or\nstreams it: as an async generator function, it yields its results in arrays,\nusually each chunk as a worker finishes it.\n\n```mjs displayName=\"experimental.mjs\"\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  hasParallelProcessor: true,\n\n  async processChunk(input, itemIndices, { marker }) {\n    return itemIndices\n      .map(index => input[index])\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `${marker} ${entry.heading.data.name} (${entry.api})`);\n  },\n\n  // Yields each chunk's lines as soon as a worker finishes them\n  async *generate(entries, worker) {\n    for await (const chunk of worker.stream(entries, { marker: '-' })) {\n      yield chunk;\n    }\n  },\n};\n```\n\ndoc-kit collects what a streaming generator yields into one array, once, and\nhands that array to every generator reading from it:\n\n* Each value it yields must be an array, whose items are appended to the\n  output.\n* The output keeps the order the values were yielded in, which for\n  `worker.stream()` is the order the chunks finished in.\n* The generators reading from it start when the stream ends, with the\n  complete output.\n\nStreaming suits a stage other generators build on, whose output is just its\nchunks' results, as `ast`, `metadata`, and `jsx-ast` are. Collect when the\ngenerator needs every result before it can finish: to sort them,\ncross-reference them, or write them to a single file.","summary":"`generate` either resolves to its whole output, as in the examples above, or streams it: as an async generator function, it yields its results in arrays, usually each chunk as a worker finishes it.","examples":[{"language":"mjs","displayName":"experimental.mjs","code":"export default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n\n  hasParallelProcessor: true,\n\n  async processChunk(input, itemIndices, { marker }) {\n    return itemIndices\n      .map(index => input[index])\n      .filter(entry => entry.stability?.data.index === '1')\n      .map(entry => `${marker} ${entry.heading.data.name} (${entry.api})`);\n  },\n\n  // Yields each chunk's lines as soon as a worker finishes them\n  async *generate(entries, worker) {\n    for await (const chunk of worker.stream(entries, { marker: '-' })) {\n      yield chunk;\n    }\n  },\n};"}],"children":[]},{"kind":"section","id":"declaring-a-dependent","name":"Declaring a dependent","title":"Declaring a dependent","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`dependsOn` pulls another generator's output in. The inverse, `dependent`,\npushes a generator's output into another generator's pipeline. Use it when a\ngenerator transforms an intermediate representation (adding, filtering, or\nrewriting entries) rather than producing an output of its own. The\n[`section-pages`](./generators/section-pages.html) generator, which adds a page\nfor each section of a module, declares two:\n\n```mjs displayName=\"index.mjs\"\nexport default {\n  name: 'section-pages',\n\n  dependsOn: '@doc-kit/core/metadata',\n\n  // Deliver this generator's output through `html` and `sitemap`\n  dependent: [\n    '@doc-kit/generator-react/html',\n    '@doc-kit/generator-react/sitemap',\n  ],\n\n  generate,\n};\n```\n\ndoc-kit walks up each dependent's pipeline, starting at the dependent itself,\nand splices the generator in front of the first generator there that reads the\nsame `dependsOn`. `html` depends on\n`jsx-ast`, which depends on `metadata`, so `jsx-ast` is rewired to read from\n`section-pages` instead. `sitemap` reads `metadata` directly, so it's rewired\nitself.\n\n![A generator declaring a dependent is spliced into that dependent's pipeline](./diagrams/dependent.svg)\n\n* Requesting a generator that declares dependents delivers its output\n  through them: through the ones the run already includes, or through all of\n  them when it includes none. `-t section-pages` builds the site and the\n  sitemap, while `-t html -t section-pages` builds just the site.\n* Several generators can splice in at the same point; they form a chain.\n* A dependent whose pipeline never reads the generator's `dependsOn` is an\n  error.","summary":"`dependsOn` pulls another generator's output in. The inverse, `dependent`, pushes a generator's output into another generator's pipeline. Use it when a generator transforms an intermediate representation (adding, filtering, or rewriting entries) rather than producing an output of its own. The `section-pages` generator, which adds a page for each section of a module, declares two:","examples":[{"language":"mjs","displayName":"index.mjs","code":"export default {\n  name: 'section-pages',\n\n  dependsOn: '@doc-kit/core/metadata',\n\n  // Deliver this generator's output through `html` and `sitemap`\n  dependent: [\n    '@doc-kit/generator-react/html',\n    '@doc-kit/generator-react/sitemap',\n  ],\n\n  generate,\n};"}],"children":[]},{"kind":"section","id":"markdown-pipelines","name":"Markdown pipelines","title":"Markdown pipelines","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"A generator processing Markdown declares its [unified](https://unifiedjs.com/)\npipeline as `markdown`, and gets a processor for it with `getProcessor(name)`:\n\n```mjs displayName=\"entries-html.mjs\"\nimport { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\nimport { getProcessor } from '@doc-kit/core/utils/markdown/processor.mjs';\n\nexport default {\n  name: 'entries-html',\n  description: 'Renders every API entry to HTML',\n  dependsOn: '@doc-kit/core/metadata',\n\n  markdown: {\n    remarkPlugins: ['...'],\n    rehypePlugins: [\n      ['remark-rehype', { allowDangerousHtml: true }],\n      '...',\n      '@doc-kit/core/plugins/shiki/rehype.mjs',\n      ['rehype-stringify', { allowDangerousHtml: true }],\n    ],\n  },\n\n  async generate(entries) {\n    const { output } = getConfig('entries-html');\n    const processor = getProcessor('entries-html');\n\n    const html = await Promise.all(\n      entries.map(async ({ content }) =>\n        processor.stringify(await processor.run(content))\n      )\n    );\n\n    await writeFile(join(output, 'entries.html'), html.join('\\n'));\n  },\n};\n```\n\nPlugins are listed as in the\n[`markdown` option](./configuration.html#markdown-plugins), and resolve from the\ngenerator's module: paths relative to it, and packages it can import (this one\nneeds `remark-rehype` and `rehype-stringify` installed).\n\n* The lists run in order, on one processor: remark plugins on the Markdown\n  tree, rehype plugins on HTML, and recma plugins on JavaScript. So a rehype\n  list starts with `remark-rehype`, and a recma list with `rehype-recma`.\n* `'...'` marks where a list takes the configured plugins, from `global` and\n  from the generator's own section. A list without one takes none, and doc-kit\n  warns about plugins configured for it. A generator rendering Markdown (one\n  with rehype or recma plugins) only takes the remark plugins of its own\n  section, as the global ones already ran when `ast` parsed the files.\n* Each thread imports the generator, and its pipeline with it, so the options\n  in `markdown` can be anything, functions included. `getProcessor()` works\n  in `generate` and in `processChunk` alike.\n* A plugin's module default-exports the plugin, a list of plugins, or a\n  preset. A plugin that needs asynchronous setup exports an async\n  `load(options)` returning the plugin instead.\n\n`getProcessor(name[, options])` takes these options:\n\n* `mdx` {boolean} Whether the processor parses MDX. Plugins can check it with\n  `this.data('mdx')`. **Default:** `false`.\n* `configured` {boolean} Whether it runs the configured plugins, rather than\n  only the generator's own. **Default:** `true`.","summary":"A generator processing Markdown declares its unified pipeline as `markdown`, and gets a processor for it with `getProcessor(name)`:","examples":[{"language":"mjs","displayName":"entries-html.mjs","code":"import { join } from 'node:path';\n\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\nimport { writeFile } from '@doc-kit/core/utils/file.mjs';\nimport { getProcessor } from '@doc-kit/core/utils/markdown/processor.mjs';\n\nexport default {\n  name: 'entries-html',\n  description: 'Renders every API entry to HTML',\n  dependsOn: '@doc-kit/core/metadata',\n\n  markdown: {\n    remarkPlugins: ['...'],\n    rehypePlugins: [\n      ['remark-rehype', { allowDangerousHtml: true }],\n      '...',\n      '@doc-kit/core/plugins/shiki/rehype.mjs',\n      ['rehype-stringify', { allowDangerousHtml: true }],\n    ],\n  },\n\n  async generate(entries) {\n    const { output } = getConfig('entries-html');\n    const processor = getProcessor('entries-html');\n\n    const html = await Promise.all(\n      entries.map(async ({ content }) =>\n        processor.stringify(await processor.run(content))\n      )\n    );\n\n    await writeFile(join(output, 'entries.html'), html.join('\\n'));\n  },\n};"}],"children":[{"kind":"section","id":"highlighting-code","name":"Highlighting code","title":"Highlighting code","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"The Shiki plugin, `@doc-kit/core/plugins/shiki/rehype.mjs`, highlights code\nblocks in every language Shiki bundles; its options are described in\n[Code highlighting](./configuration.html#code-highlighting). To highlight other\ncode the way the pipeline does, `getHighlighter(name)` returns the highlighter\nof a generator's Shiki plugin, and throws when its pipeline has none:\n\n```mjs displayName=\"entries-html.mjs\"\nimport { getHighlighter } from '@doc-kit/core/plugins/shiki/highlighter.mjs';\n\n// In `generate` or `processChunk`\nconst highlighter = getHighlighter('entries-html');\n\nconst html = highlighter.highlightToHtml('const answer = 42;', 'js');\n```\n\n* `highlightToHtml` {Function} `highlightToHtml(code, lang[, meta])` returns\n  the inner HTML of the highlighted `<code>` element.\n* `highlightToHast` {Function} `highlightToHast(code, lang[, meta])` returns\n  the highlighted code as a HAST tree.\n* `resolveLanguage` {Function} `resolveLanguage(lang)` returns the language\n  to highlight `lang` as, `'text'` when Shiki doesn't know it. It registers a\n  bundled language the first time, so pass a language through it before using\n  `shiki` directly.\n* `shiki` {Object} The Shiki instance.\n* `langs` {Array} The languages it highlights.","summary":"The Shiki plugin, `@doc-kit/core/plugins/shiki/rehype.mjs`, highlights code blocks in every language Shiki bundles; its options are described in Code highlighting. To highlight other code the way the pipeline does, `getHighlighter(name)` returns the highlighter of a generator's Shiki plugin, and throws when its pipeline has none:","examples":[{"language":"mjs","displayName":"entries-html.mjs","code":"import { getHighlighter } from '@doc-kit/core/plugins/shiki/highlighter.mjs';\n\n// In `generate` or `processChunk`\nconst highlighter = getHighlighter('entries-html');\n\nconst html = highlighter.highlightToHtml('const answer = 42;', 'js');"}],"children":[]}]},{"kind":"section","id":"typing-a-generator","name":"Typing a generator","title":"Typing a generator","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"`@doc-kit/core` declares the types of a generator as globals, which importing\nits `generators/types` module brings in:\n\n```ts displayName=\"types.d.ts\"\nimport type {} from '@doc-kit/core/generators/types';\nimport type { MetadataEntry } from '@doc-kit/core/generators/metadata/types';\nimport type { GlobalConfiguration } from '@doc-kit/core/utils/configuration/types';\n\nexport type Configuration = {\n  fileName: string;\n};\n\n// What `getConfig('experimental')` returns\nexport type Options = GlobalConfiguration & Configuration;\n\nexport type Generator = Omit<\n  GeneratorMetadata<\n    // Its options\n    Configuration,\n    // What `generate` receives, and what it resolves to\n    Generate<Array<MetadataEntry>, void>,\n    // What `processChunk` gets per item, what it resolves to, and its `deps`\n    ProcessChunk<MetadataEntry, Array<string>, { marker: string }>\n  >,\n  // `GeneratorMetadata` only takes the names of the built-in generators\n  'name'\n> & { name: string };\n```\n\n```mjs displayName=\"experimental.mjs\"\nimport getConfig from '@doc-kit/core/utils/configuration/index.mjs';\n\n/** @type {import('./types').Generator} */\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n  defaultConfiguration: { fileName: 'experimental.md' },\n\n  async generate(entries) {\n    /** @type {import('./types').Options} */\n    // @ts-expect-error `getConfig()` only knows the built-in generators\n    const { output, fileName } = getConfig('experimental');\n\n    // ...\n  },\n};\n```\n\n* The type requires `description`, `dependsOn` and `defaultConfiguration`,\n  even though doc-kit doesn't: give a generator without options\n  `defaultConfiguration: {}`, and one that reads its own sources\n  `dependsOn: undefined`.\n* `getConfig()` is typed for the built-in names only, so type checking flags a\n  call with your generator's name. Mark that call with `// @ts-expect-error`\n  and type its result yourself, as above.\n* A generator that streams declares `AsyncGenerator<Array<Item>>` as what\n  `generate` returns.","summary":"`@doc-kit/core` declares the types of a generator as globals, which importing its `generators/types` module brings in:","examples":[{"language":"ts","displayName":"types.d.ts","code":"import type {} from '@doc-kit/core/generators/types';\nimport type { MetadataEntry } from '@doc-kit/core/generators/metadata/types';\nimport type { GlobalConfiguration } from '@doc-kit/core/utils/configuration/types';\n\nexport type Configuration = {\n  fileName: string;\n};\n\n// What `getConfig('experimental')` returns\nexport type Options = GlobalConfiguration & Configuration;\n\nexport type Generator = Omit<\n  GeneratorMetadata<\n    // Its options\n    Configuration,\n    // What `generate` receives, and what it resolves to\n    Generate<Array<MetadataEntry>, void>,\n    // What `processChunk` gets per item, what it resolves to, and its `deps`\n    ProcessChunk<MetadataEntry, Array<string>, { marker: string }>\n  >,\n  // `GeneratorMetadata` only takes the names of the built-in generators\n  'name'\n> & { name: string };"},{"language":"mjs","displayName":"experimental.mjs","code":"import getConfig from '@doc-kit/core/utils/configuration/index.mjs';\n\n/** @type {import('./types').Generator} */\nexport default {\n  name: 'experimental',\n  description: 'Lists every experimental API',\n  dependsOn: '@doc-kit/core/metadata',\n  defaultConfiguration: { fileName: 'experimental.md' },\n\n  async generate(entries) {\n    /** @type {import('./types').Options} */\n    // @ts-expect-error `getConfig()` only knows the built-in generators\n    const { output, fileName } = getConfig('experimental');\n\n    // ...\n  },\n};"}],"children":[]},{"kind":"section","id":"publishing-a-generator","name":"Publishing a generator","title":"Publishing a generator","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"A package exports each of its generators from a subpath:\n\n```json displayName=\"package.json\"\n{\n  \"name\": \"doc-kit-rss\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"exports\": {\n    \"./rss\": \"./src/rss/index.mjs\"\n  },\n  \"peerDependencies\": {\n    \"@doc-kit/core\": \"^1.3.0\"\n  }\n}\n```\n\nUsers install it next to the CLI and pass the subpath as a target, on the\ncommand line or in the `target` of their configuration file:\n\n```bash\nnpm install --save-dev @doc-kit/cli doc-kit-rss\nnpx doc-kit generate -t doc-kit-rss/rss -i \"docs/**/*.md\" -o out\n```\n\n* List `@doc-kit/core` as a peer dependency, so the generator shares the\n  CLI's copy: `getConfig()` reads the configuration the CLI gave that copy.\n* A generator it depends on from another package, such as\n  `@doc-kit/generator-react/jsx-ast`, needs that package installed too.\n* Its `name` is the section users configure it in, so pick one that doesn't\n  collide with the built-in generators.","summary":"A package exports each of its generators from a subpath:","examples":[{"language":"json","displayName":"package.json","code":"{\n  \"name\": \"doc-kit-rss\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"exports\": {\n    \"./rss\": \"./src/rss/index.mjs\"\n  },\n  \"peerDependencies\": {\n    \"@doc-kit/core\": \"^1.3.0\"\n  }\n}"},{"language":"bash","displayName":null,"code":"npm install --save-dev @doc-kit/cli doc-kit-rss\nnpx doc-kit generate -t doc-kit-rss/rss -i \"docs/**/*.md\" -o out"}],"children":[{"kind":"section","id":"adding-a-built-in-generator","name":"Adding a built-in generator","title":"Adding a built-in generator","scope":"module","overloadOf":null,"stability":null,"added":[],"deprecated":[],"removed":[],"napiVersion":[],"changes":[],"description":"In this repository, each generator has a directory of its own, with the object\nin `index.mjs`, its functions in `generate.mjs` and its types in `types.d.ts`.\nA new generator also gets a subpath in its package's `exports`, such as\n`\"./my-format\": \"./src/generators/my-format/index.mjs\"` in\n`packages/core/package.json`, and a shorthand name in\n`packages/core/src/generators/index.mjs`, which maps the name users type to\nthat specifier. The key matches the generator's `name`:\n\n```mjs displayName=\"packages/core/src/generators/index.mjs\"\nexport const publicGenerators = {\n  json: '@doc-kit/core/json',\n  'my-format': '@doc-kit/core/my-format',\n  // ...\n};\n```\n\n`publicGenerators` are the targets `doc-kit generate --help` lists. Stages\nthat produce no output of their own go in `internalGenerators` instead, which\nthe help leaves out. A `README.md` next to the generator's `index.mjs`,\ndocumenting its options, becomes its page on this site; add it to the sidebar\nin `www/doc-kit.config.mjs`. The\n[contributing guide](https://github.com/nodejs/doc-kit/blob/main/CONTRIBUTING.md)\ncovers the rest, from running the tests to adding a changeset.","summary":"In this repository, each generator has a directory of its own, with the object in `index.mjs`, its functions in `generate.mjs` and its types in `types.d.ts`. A new generator also gets a subpath in its package's `exports`, such as `\"./my-format\": \"./src/generators/my-format/index.mjs\"` in `packages/core/package.json`, and a shorthand name in `packages/core/src/generators/index.mjs`, which maps the name users type to that specifier. The key matches the generator's `name`:","examples":[{"language":"mjs","displayName":"packages/core/src/generators/index.mjs","code":"export const publicGenerators = {\n  json: '@doc-kit/core/json',\n  'my-format': '@doc-kit/core/my-format',\n  // ...\n};"}],"children":[]}]}]}