Skip to content

project-millipede/recma-component-resolver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

recma-component-resolver

Build-time component capture and selective forwarding for nested MDX includes

A compile-time Unified/Recma package that analyzes compiled MDX modules, derives component requirements across nested include graphs, and rewrites MDX include callsites so each child receives only the components it actually renders.

Table of Contents

  1. Overview
  2. Getting Started
  3. Quick Start
  4. Usage
  5. Reference
  6. Capabilities

Overview

Features

  • Compile-Time Capture: Collect local component usage and nested MDX include edges from compiled _createMdxContent output.
  • Reachability-Aware Graph Derivation: Build a canonical include graph from captured module facts, including shared children and cycles.
  • Minimal Per-Include Forwarding: Rewrite each include boundary with only the components the direct child needs locally.
  • Arbitrary Nested Depth: Preserve access to the root component map through a hidden __mdxRootComponents carrier.
  • Small Runtime Surface: Generated output adds a compact __pickMdxComponents helper only when subset forwarding is needed.

Why Selective Forwarding?

By default, nested MDX includes often receive the full components map at every boundary. That works, but it over-forwards unrelated components and makes deep include chains harder to reason about.

This package splits the problem into three build-time stages:

  1. Capture per-module facts with recmaCapture
  2. Derive the reachable include graph with buildResolvedComponentGraph
  3. Rewrite include callsites with recmaRewrite

The result is a boundary-local forwarding model:

  • page.mdx -> intro.mdx receives only intro.mdx's local component needs
  • page.mdx -> api.mdx receives only api.mdx's local component needs
  • intro.mdx -> examples.mdx is resolved independently
  • api.mdx -> examples.mdx is resolved independently

This avoids transitive packing at parent boundaries while still supporting arbitrary nested depth.

Getting Started

Installation

npm install recma-component-resolver unified
# or
pnpm add recma-component-resolver unified

This package is recma-only. It expects compiled MDX JavaScript as input and is typically used alongside an MDX compiler or bundler such as @mdx-js/mdx or @mdx-js/esbuild.

Quick Start

If the goal is the shortest path to adoption:

  1. Run a first build with recmaCapture
  2. Derive the resolved component graph
  3. Run a second build with recmaRewrite
import {
  buildResolvedComponentGraph,
  recmaCapture,
  recmaRewrite,
  type CapturedModule
} from 'recma-component-resolver';

const capturedModules: Record<string, CapturedModule> = {};

const runBuild = async (
  entryPath: string,
  recmaPlugins: Array<unknown>
): Promise<string> => {
  // Run the existing MDX compile or bundle step here.
  // Example integrations: @mdx-js/mdx, @mdx-js/esbuild, or a custom bundler.
  return '';
};

await runBuild('/content/page.mdx', [
  [
    recmaCapture,
    {
      onModuleCapture: payload => {
        capturedModules[payload.moduleId] = payload;
      }
    }
  ]
]);

const resolvedComponentGraph = buildResolvedComponentGraph(
  '/content/page.mdx',
  capturedModules
);

await runBuild('/content/page.mdx', [
  [
    recmaRewrite,
    {
      capturedModules,
      requiredComponentsByImportIdentifier:
        resolvedComponentGraph.requiredComponentsByImportIdentifier
    }
  ]
]);

resolvedComponentGraph.usedComponentNames can also be attached to emitted metadata if the consuming runtime wants a complete list of reachable components.

Usage

This package has three explicit stages.

Step 1: Capture Module Facts

recmaCapture scans compiled _createMdxContent output and emits one CapturedModule per compiled module.

Example source graph:

page.mdx
   |
   |-> intro.mdx -> examples.mdx
   |
   |-> api.mdx ---> examples.mdx (shared)

Example local component usage:

  • page.mdx uses Notice
  • intro.mdx uses Steps
  • api.mdx uses CodeBlock and Notice
  • examples.mdx uses Preview

Example capture result for page.mdx:

{
  moduleId: '/content/page.mdx',
  moduleReferencesByIdentifier: {
    Intro: './intro.mdx',
    Api: './api.mdx'
  },
  componentNames: ['Notice'],
  includeSpecifiers: ['./intro.mdx', './api.mdx']
}

Register the capture pass in the first build:

import {
  recmaCapture,
  type CapturedModule
} from 'recma-component-resolver';

const capturedModules: Record<string, CapturedModule> = {};

const capturePlugins = [
  [
    recmaCapture,
    {
      onModuleCapture: payload => {
        capturedModules[payload.moduleId] = payload;
      }
    }
  ]
];

Step 2: Build the Resolved Component Graph

buildResolvedComponentGraph(entryPath, capturedModules) converts raw capture results into the data needed by the rewrite phase.

Example result for the graph above:

{
  usedComponentNames: ['Notice', 'Steps', 'CodeBlock', 'Preview'],
  requiredComponentsByImportIdentifier: {
    '/content/page.mdx': {
      Intro: ['Steps'],
      Api: ['CodeBlock', 'Notice']
    },
    '/content/intro.mdx': {
      Examples: ['Preview']
    },
    '/content/api.mdx': {
      Examples: ['Preview']
    },
    '/content/examples.mdx': {}
  }
}

Important behavior:

  • Reachability is discovered with BFS from the entry module
  • Shared children are processed once
  • Parent boundaries receive only the direct child's local requirements
  • Grandchild requirements are not packed into parent-child subsets

Step 3: Rewrite Include Callsites

recmaRewrite uses the captured modules plus the derived requiredComponentsByImportIdentifier map to rewrite nested MDX include callsites.

Register the rewrite pass in the second build:

import {
  buildResolvedComponentGraph,
  recmaRewrite,
  type CapturedModule
} from 'recma-component-resolver';

const capturedModules: Record<string, CapturedModule> = {};

const resolvedComponentGraph = buildResolvedComponentGraph(
  '/content/page.mdx',
  capturedModules
);

const rewritePlugins = [
  [
    recmaRewrite,
    {
      capturedModules,
      requiredComponentsByImportIdentifier:
        resolvedComponentGraph.requiredComponentsByImportIdentifier
    }
  ]
];

Typical compiled output shape before rewrite:

_jsx(Api, { components: props.components })

Typical compiled output shape after rewrite:

_jsx(
  Api,
  Object.assign({}, { components: props.components }, {
    components: __pickMdxComponents(
      props.__mdxRootComponents || props.components,
      ['CodeBlock', 'Notice']
    ),
    __mdxRootComponents: props.__mdxRootComponents || props.components
  })
)

This keeps the direct child subset minimal while preserving a stable root component source for deeper nested boundaries.

Reference

Exports

import {
  buildResolvedComponentGraph,
  recmaCapture,
  recmaRewrite,
  type CapturedModule
} from 'recma-component-resolver';

recmaCapture

Recma plugin that captures per-module facts from compiled MDX output.

Options:

type CaptureOptions = {
  onModuleCapture: (payload: CapturedModule) => void;
};

CapturedModule

Captured metadata for a single compiled content module.

type CapturedModule = {
  moduleId: string;
  moduleReferencesByIdentifier: Record<string, string>;
  componentNames: Array<string>;
  includeSpecifiers: Array<string>;
};

Field meanings:

  • moduleId: canonical absolute module identifier
  • moduleReferencesByIdentifier: local include identifiers mapped to their original specifiers
  • componentNames: local custom component names in first-seen order
  • includeSpecifiers: rendered nested include specifiers discovered in the module

buildResolvedComponentGraph

Build-time derivation step that resolves the reachable include graph and computes per-boundary forwarding requirements.

Signature:

const resolvedComponentGraph = buildResolvedComponentGraph(
  entryPath,
  capturedModules
);

Returned shape:

{
  usedComponentNames: Array<string>,
  requiredComponentsByImportIdentifier: Record<
    string,
    Record<string, Array<string>>
  >
}

recmaRewrite

Recma plugin that rewrites MDX include callsites with minimal component subsets.

Options:

type RewriteOptions = {
  capturedModules: Record<string, CapturedModule>;
  requiredComponentsByImportIdentifier?: Record<
    string,
    Record<string, Array<string>>
  >;
};

requiredComponentsByImportIdentifier is optional at the type level, but selective forwarding depends on passing the derived map from buildResolvedComponentGraph.

Capabilities

  • Boundary-local subsets: Each include boundary receives only the direct child's local component requirements.
  • No transitive packing: Parent boundaries do not accumulate grandchild or deeper descendant requirements.
  • Root-direct access: Nested children resolve from __mdxRootComponents || props.components, not through intermediate filtered subsets.
  • Shared include support: Shared children are handled correctly during graph derivation.
  • Cycle tolerance: Reachability traversal processes each module once.
  • Compiler-agnostic integration: Works with any MDX build pipeline that can register recma plugins and provide a stable module path per file.

Non-Goals

  • This package does not bundle MDX files by itself.
  • This package does not resolve alias-based module specifiers.
  • This package does not parse raw markdown; it operates on compiled MDX output.

About

Auto-resolves component dependencies across nested MDX includes. MDX doesn't support selective component passing for nested files - this plugin analyzes your MDX files and injects only the components actually needed at each include boundary.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors