Skip to main content

Replace Plugin

The replace plugin enables embedding and syncing content from external markdown files into your documentation. It supports pulling content from remote repositories, handling frontmatter, and automatically updating embedded content.

Features

  • Reference Blocks - Embed content from local or remote markdown files
  • Multi-Source Support - Pull content from GitHub repositories or local files
  • Smart Syncing - Automatically update embedded content during builds
  • Frontmatter Handling - Control how frontmatter is merged from referenced files
  • Content Slicing - Extract specific sections using anchor links
  • Release Notes - Automatically generate release notes from JIRA queries
  • Image Normalization - Automatically resolve and copy image paths
  • Link Normalization - Fix relative links in embedded content

Configuration

Configure the replace plugin in your doom.config.ts:
import { defineConfig } from '@alauda/doom'
import { replacePlugin } from '@alauda/doom/plugins'

export default defineConfig({
  plugins: [
    replacePlugin({
      lang: 'en',              // Language for localized content
      localBasePath: './docs', // Base path for local files
      force: false             // Force re-fetch from remote
    })
  ],
  
  // Define reference sources
  reference: [
    {
      repo: 'owner/repo',
      branch: 'main',
      publicBase: 'docs/public',
      sources: [
        {
          name: 'installation',
          path: 'guides/installation.md'
        },
        {
          name: 'api-overview',
          path: 'api/overview.md#getting-started',
          ignoreHeading: true,
          frontmatterMode: 'merge'
        }
      ]
    }
  ]
})

Plugin Options

lang
string | null
required
Language code for localized content. Set to null for non-localized docs.
localBasePath
string
required
Base path for resolving local file references.
force
boolean
default:"false"
Force re-fetching content from remote repositories, bypassing cache.

Reference Configuration

Reference Item

repo
string
GitHub repository in owner/repo format for remote sources.
branch
string
default:"main"
Git branch to pull content from.
publicBase
string
default:"docs/public"
Path to public assets directory in the source repository.
sources
ReferenceItemSource[]
required
Array of content sources to embed.

Reference Source

name
string
required
Unique identifier for this reference (used in reference blocks).
path
string
required
Path to the source file, optionally with #anchor to extract a section.
ignoreHeading
boolean
default:"false"
Skip the heading when extracting a section.
frontmatterMode
'ignore' | 'merge' | 'remove' | 'replace'
default:"'ignore'"
How to handle frontmatter from the source file:
  • ignore - Don’t modify frontmatter
  • merge - Merge source frontmatter with current
  • remove - Remove all frontmatter
  • replace - Replace current with source frontmatter
processors
ContentProcessor[]
Content processors to apply before embedding (e.g., EJS templates).

Usage

Basic Reference Block

Embed content using reference comments: Markdown (.md):
<!-- reference-start#installation -->
<!-- reference-end -->
MDX (.mdx):
{/* reference-start#installation */}
{/* reference-end */}
The plugin automatically fetches and inserts content between the start and end markers.

Local File Reference

Embed content from local files:
export default defineConfig({
  reference: [
    {
      // No repo specified = local files
      sources: [
        {
          name: 'shared-intro',
          path: '../shared/introduction.md'
        }
      ]
    }
  ]
})
<!-- reference-start#shared-intro -->
<!-- reference-end -->

Remote Repository Reference

Embed content from GitHub repositories:
export default defineConfig({
  reference: [
    {
      repo: 'kubernetes/kubernetes',
      branch: 'master',
      sources: [
        {
          name: 'k8s-architecture',
          path: 'docs/concepts/architecture.md'
        }
      ]
    }
  ]
})

Section Extraction

Extract specific sections using anchors:
sources: [
  {
    name: 'api-auth',
    path: 'api-reference.md#authentication',
    ignoreHeading: true  // Don't include the "Authentication" heading
  }
]
The plugin extracts content from the specified heading until the next same-level heading.

Frontmatter Merging

Control how frontmatter is handled:
sources: [
  {
    name: 'shared-metadata',
    path: 'shared/page.md',
    frontmatterMode: 'merge'  // Merge frontmatter fields
  }
]
Source file (shared/page.md):
---
author: John Doe
tags: [tutorial, advanced]
---
Current file:
---
title: My Guide
tags: [guide]
---

<!-- reference-start#shared-metadata -->
<!-- reference-end -->
Result:
---
title: My Guide
author: John Doe
tags: [tutorial, advanced]  # Merged from source
---

EJS Template Processing

Process content with EJS templates:
sources: [
  {
    name: 'templated-content',
    path: 'templates/guide.md.ejs',
    processors: [
      {
        type: 'ejsTemplate',
        data: {
          version: '2.0',
          features: ['auth', 'api']
        }
      }
    ]
  }
]

Release Notes Integration

Automatically generate release notes from JIRA:
export default defineConfig({
  releaseNotes: {
    queryTemplates: {
      'bugs-v1': 'project = PROJ AND fixVersion = "1.0" AND type = Bug'
    }
  }
})
In your markdown:
<!-- release-notes-for-bugs?bugs-v1 -->
The plugin queries JIRA and generates a formatted list of issues.

Implementation

Plugin Structure

The plugin integrates with Rspress using remark and rehype plugins:
packages/doom/src/plugins/replace/index.ts:32-67
export const replacePlugin = ({
  lang,
  localBasePath,
  force,
}: {
  lang: string | null
  localBasePath: string
  force?: boolean
}): RspressPlugin => {
  let userConfig: UserConfig
  let normalizedItems: Record<string, NormalizedReferenceSource>
  return {
    name: 'doom-replace',
    config(config) {
      config.markdown ??= {}
      config.markdown.remarkPlugins ??= []
      config.markdown.remarkPlugins.push(
        [
          remarkReplace,
          {
            lang,
            localBasePath,
            root: config.root,
            items: (normalizedItems = normalizeReferenceItems(
              config.reference,
            )),
            releaseNotes: config.releaseNotes,
            force,
          },
        ],
        remarkExplicitJsx,
      )
      config.markdown.rehypePlugins ??= []
      config.markdown.rehypePlugins.push(rehypeNormalizeLink)
      return (userConfig = config)
    },
    // ...
  }
}

Content Resolution

The plugin resolves references during the build:
  1. Parse - Identify reference blocks in markdown
  2. Resolve - Fetch content from local or remote sources
  3. Transform - Apply processors and normalize images/links
  4. Inject - Insert content between reference markers
  5. Update - Write changes back to disk if content changed

Image Handling

Images from remote sources are automatically handled:
  • Remote images are copied to your public directory
  • Image paths are updated to point to the local copies
  • Relative paths are preserved and adjusted
Relative links in embedded content are automatically fixed:
  • Links are resolved relative to the source file
  • Internal links are adjusted for your site structure
  • External links remain unchanged

Auto-Update Behavior

During production builds, the plugin checks if embedded content has changed and automatically updates your source files. You’ll see a warning to commit these changes.
The plugin tracks content changes:
packages/doom/src/plugins/replace/remark-replace.ts:220-231
if (content !== newContent) {
  await fs.writeFile(filepath, newContent)

  if (!vfile.data.original && isProduction()) {
    const message = `Reference block in \`${cyan(relativePath)}\` has been updated, please commit the changes`

    if (isCI) {
      process.env.__DOOM_REBUILD__ = 'true'
    }

    logger.warn(message)
  }
}

Best Practices

  1. Use Descriptive Names - Name references clearly (e.g., k8s-install-linux not ref1)
  2. Version Control - Commit updated reference blocks when warnings appear
  3. Cache Strategy - Use force: true only when debugging remote content issues
  4. Section Extraction - Prefer extracting specific sections over entire files
  5. Test Locally - Verify references resolve correctly before pushing
  6. Document Sources - Add comments explaining where content comes from

Common Patterns

Shared API Documentation

reference: [
  {
    repo: 'company/api-specs',
    branch: 'main',
    sources: [
      { name: 'auth-api', path: 'authentication.md' },
      { name: 'users-api', path: 'users.md' },
      { name: 'billing-api', path: 'billing.md' }
    ]
  }
]

Multi-Product Documentation

reference: [
  {
    repo: 'company/product-a',
    sources: [{ name: 'product-a-intro', path: 'README.md#overview' }]
  },
  {
    repo: 'company/product-b',
    sources: [{ name: 'product-b-intro', path: 'README.md#overview' }]
  }
]

Localized Content

reference: [
  {
    repo: 'company/docs',
    sources: [
      { name: 'install-en', path: 'en/installation.md' },
      { name: 'install-zh', path: 'zh/installation.md' }
    ]
  }
]

Troubleshooting

Reference Not Found

If you see “Reference not found” errors:
  1. Verify the reference name matches the sources configuration
  2. Check the path is correct and the file exists
  3. For remote repos, ensure the repository and branch are correct

Content Not Updating

If embedded content isn’t updating:
  1. Try using force: true to bypass cache
  2. Check that reference markers are correctly formatted
  3. Verify the source file has actually changed

Anchor Not Found

If anchor extraction fails:
  1. Verify the heading exists in the source file
  2. Check heading ID generation (special chars are normalized)
  3. Use heading text instead of ID if needed

Build docs developers (and LLMs) love