Post-processing

Folder: src/parsers/postprocessors/

Postprocessors run after HTML rendering to enhance the output.

Role in the Pipeline

          ┌───────────────────────┼───────────────────────┐
          ▼                       ▼                       ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  PREPROCESSORS  │     │    RENDERERS    │     │ POSTPROCESSORS  │
│                 │ ──▶ │                 │ ──▶ │ ◀── YOU ARE HERE│
│                 │     │                 │     │ • heading-ids   │
│                 │     │                 │     │ • external-links│
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                                        │
                                                        ▼
                                                  Transformers

Postprocessor Files

File Purpose
heading-ids.ts Adds IDs to headings for anchor links
external-links.ts Adds security attributes to external links
index.ts Module exports

Available Postprocessors

Heading IDs

Automatically adds IDs to headings for anchor links:

<!-- Input -->
<h2>Getting Started</h2>

<!-- Output -->
<h2 id="getting-started">Getting Started</h2>

This enables:

  • Direct linking to sections via #getting-started
  • Table of contents generation
  • In-page navigation

Usage:

import { headingIdsPostprocessor, createHeadingIdsPostprocessor } from '@parsers/postprocessors';

// Use default
pipeline.addPostprocessor(headingIdsPostprocessor);

// Or create with options
const customHeadingIds = createHeadingIdsPostprocessor({
  prefix: 'section-',  // Add prefix to all IDs
});

Adds security attributes to external links:

<!-- Input -->
<a href="https://example.com">Link</a>

<!-- Output -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Link</a>

Security Attributes:

Attribute Purpose
target="_blank" Opens in new tab
rel="noopener" Prevents window.opener access
rel="noreferrer" Prevents referrer header

Usage:

import { externalLinksPostprocessor, createExternalLinksPostprocessor } from '@parsers/postprocessors';

// Use default
pipeline.addPostprocessor(externalLinksPostprocessor);

// Or create with options
const customExternalLinks = createExternalLinksPostprocessor({
  excludeDomains: ['example.com'],  // Don't mark as external
});

Custom Postprocessors

Create custom postprocessors by implementing the Processor interface:

import type { Processor, ProcessContext } from '@parsers/types';

const myPostprocessor: Processor = {
  name: 'my-postprocessor',
  async process(html: string, context: ProcessContext): Promise<string> {
    // Transform HTML here
    return transformedHtml;
  }
};

// Add to pipeline
pipeline.addPostprocessor(myPostprocessor);

Example: Add Copy Buttons to Code Blocks

const copyButtonPostprocessor: Processor = {
  name: 'copy-buttons',
  async process(html) {
    return html.replace(
      /<pre><code/g,
      '<pre class="has-copy-button"><button class="copy-btn">Copy</button><code'
    );
  }
};

Example: Lazy Load Images

const lazyImagePostprocessor: Processor = {
  name: 'lazy-images',
  async process(html) {
    return html.replace(
      /<img /g,
      '<img loading="lazy" '
    );
  }
};

Processing Order

Postprocessors run in the order they are added:

pipeline
  .addPostprocessor(headingIdsPostprocessor)    // 1st
  .addPostprocessor(externalLinksPostprocessor) // 2nd
  .addPostprocessor(transformerProcessor);      // 3rd - transformers

Order matters when processors depend on each other's output.

Error Handling

Missing Position Prefix

[DOCS ERROR] Files missing required XX_ position prefix:
  - overview.md
  - installation.md

Docs files must be named with a position prefix (01-99).
Examples:
  01_getting-started.md
  02_installation.md

File Not Found

[PARSER ERROR] Content file not found: /path/to/missing.md
  Code: FILE_NOT_FOUND

Directory Not Found

[PARSER ERROR] Content directory not found: /path/to/missing/
  Code: DIR_NOT_FOUND