mdxgo

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

mdx-go

A Go library that parses MDX (Markdown + JSX) source and transpiles it to a pure ESM JavaScript module, ready to be executed by any modern JS engine or bundler.

Architecture

MDX source ([]byte)
        │
        ▼
┌───────────────────────────────────┐
│  extractTopLevelStatements()      │  Pre-scans bare import/export lines
│  → statements []string            │  before Goldmark sees the source
│  → body       []byte              │
└───────────────┬───────────────────┘
                │
                ▼
┌───────────────────────────────────┐
│  Goldmark Markdown Parser         │
│                                   │
│  Extensions:                      │
│  ┌─────────────────────────────┐  │
│  │ jsxBlockParser (priority 90)│  │  Captures <Tag ...>...</Tag>
│  │ jsxInlineParser (pri. 200)  │  │  Captures inline <Tag /> in text
│  │ jsxExpressionParser(pri.199)│  │  Captures { expression }
│  └─────────────────────────────┘  │
│                                   │
│  Custom AST Nodes:                │
│  • JSXBlock    (block-level JSX)  │
│  • JSXInline   (inline JSX tags)  │
│  • JSXExpression ({ ... })        │
└───────────────┬───────────────────┘
                │ AST walk
                ▼
┌───────────────────────────────────┐
│  jsxRenderer (NodeRenderer)       │
│                                   │
│  Emits JSX-flavoured source:      │
│  • import React from 'react'      │
│  • [hoisted import/export stmts]  │
│  • export default function        │
│      MDXContent({ components }) { │
│    const _c = { h1,p,... };      │
│    return (<> ... </>);           │
│  }                                │
└───────────────┬───────────────────┘
                │ JSX string
                ▼
┌───────────────────────────────────┐
│  esbuild.Transform()              │
│                                   │
│  Options:                         │
│  • Loader: JSX                    │
│  • JSXFactory: React.createElement│
│  • Format: ESModule               │
│  • Target: ES2020                 │
└───────────────┬───────────────────┘
                │
                ▼
        Plain JS ESM string

File Structure

File Responsibility
ast.go Custom AST node types: JSXBlock, JSXInline, JSXExpression
attrlexer.go State-machine attribute lexer (handles nested {{}}, lambdas)
parser.go Goldmark BlockParser and InlineParser implementations
renderer.go Goldmark NodeRenderer that emits JSX source
extension.go Goldmark Extender that registers all parsers + renderer
mdxgo.go Public Compile([]byte) (string, error) API

Why a Custom Attribute Lexer?

Regex cannot correctly parse JSX attributes with nested structures:

// These all break naive regex approaches:
style={{ color: 'red', padding: 8 }}          // nested braces
onClose={() => { setOpen(false); work(); }}   // multi-level nesting  
label="Value with {braces} inside"            // brace inside string

The attrLexer in attrlexer.go solves this with a character-by-character state machine that tracks:

  • Brace depth (depth counter)
  • Whether we're inside a string literal (single/double/backtick)
  • Escape sequences (\", \')

Usage

import mdxgo "github.com/your-org/mdx-go"

source := []byte(`
import Button from './Button.js'

# Hello MDX

<Button type="primary">Click me</Button>

The answer is {21 * 2}.
`)

js, err := mdxgo.Compile(source)
if err != nil {
    log.Fatal(err)
}
fmt.Println(js)
// Output: ESM module with React.createElement calls

Output Format

The compiled output is an ESM module that:

  1. Re-exports any import/export statements from the MDX source
  2. Exports a default MDXContent React component
  3. Accepts a components prop for overriding default HTML elements
  4. Uses React.createElement (no JSX, ready for any bundler)

Example output for # Hello:

import React from "react";
export default function MDXContent({ components, ...props }) {
  const _c = { h1: "h1", p: "p", /* ... */ ...components };
  return React.createElement(
    React.Fragment,
    null,
    React.createElement(_c.h1, null, "Hello")
  );
}

Component Overriding

// In your app:
import MDXContent from './my-page.mdx.js'
import { Heading } from './design-system'

<MDXContent components={{ h1: Heading }} />

Supported MDX Features

Feature Status
CommonMark paragraphs, headings
Bold, italic, code
Fenced code blocks with language
Blockquotes
Ordered & unordered lists
Links and images
Thematic breaks (---)
Block-level JSX <Tag>...</Tag>
Self-closing JSX <Tag />
Inline JSX <Tag> in paragraphs
Expression interpolation {expr}
Top-level import statements
Top-level export statements
Nested JSX attributes prop={{}}
Lambda attributes onClick={() => {}}
GFM tables, strikethrough ✅ (via goldmark GFM extension)
MDX v2 ESM exports

Dependencies

License

MIT

Documentation

Overview

Package mdxgo compiles MDX (Markdown + JSX) source into a ready-to-run ESM JavaScript module.

MDX combines Markdown prose with JSX components and JavaScript expressions. mdxgo parses that source with goldmark extended by custom JSX block, inline, and expression parsers, renders it to JSX, and then transforms the JSX to plain ECMAScript modules with esbuild.

The typical entry point is Compile:

js, err := mdxgo.Compile([]byte(mdxSource))
if err != nil {
	// handle compilation error
}
// js is an ESM module string whose default export, MDXContent, is a React
// functional component accepting { components, ...props }.

Index

Constants

This section is empty.

Variables

View Source
var KindJSXBlock = ast.NewNodeKind("JSXBlock")

KindJSXBlock is the ast.NodeKind identifying a block-level JSX element such as <MyComponent prop="val">...</MyComponent>.

View Source
var KindJSXExpression = ast.NewNodeKind("JSXExpression")

KindJSXExpression is the ast.NodeKind identifying a JavaScript expression interpolation of the form { someValue }.

View Source
var KindJSXInline = ast.NewNodeKind("JSXInline")

KindJSXInline is the ast.NodeKind identifying an inline JSX element embedded within paragraph text.

Functions

func Compile

func Compile(source []byte) (string, error)

Compile parses an MDX source string and transpiles it to a pure ESM JavaScript module string ready for execution in any modern JS engine.

The compilation runs in two stages: goldmark parses the Markdown together with the custom JSX nodes into JSX-flavoured source, then esbuild transforms that JSX into plain ECMAScript modules. The returned string contains a default export named MDXContent, a React functional component accepting { components, ...props }.

Compile returns an error if goldmark fails to render the document or if esbuild reports any transform errors; the latter error includes the intermediate JSX source to aid debugging.

func ParseAttributes

func ParseAttributes(attrStr string) map[string]string

ParseAttributes lexes a complete JSX attribute-list string and returns a map of attribute name to value. It recognises boolean shorthand, single- and double-quoted strings, and brace expressions:

disabled                        → {"disabled": "true"}
type="warning"                  → {"type": "warning"}
type='warning'                  → {"type": "warning"}
count={42}                      → {"count": "{42}"}
style={{ color: 'red' }}        → {"style": "{{ color: 'red' }}"}
onClose={() => setOpen(false)}  → {"onClose": "{() => setOpen(false)}"}

Brace-expression values retain their outer braces so the renderer can re-emit them as valid JSX.

func Version

func Version() string

Version returns the library version string.

Types

type JSXBlock

type JSXBlock struct {
	ast.BaseBlock

	// TagName is the element or component name, such as "Alert", "div" or
	// "MyComp".
	TagName string

	// IsSelfClosing reports whether the tag ends with /> and therefore has no
	// children and no closing tag.
	IsSelfClosing bool

	// Attrs holds every prop parsed from the opening tag. Values are raw
	// strings: string literals are stripped of their outer quotes, while JSX
	// expressions are kept verbatim so the renderer can re-emit them.
	//
	// The field is deliberately named Attrs rather than Attributes: the
	// embedded ast.BaseBlock already promotes an Attributes method from the
	// ast.Node interface, and a field named Attributes would shadow it and
	// prevent *JSXBlock from satisfying ast.Node.
	Attrs map[string]string

	// RawInner holds the block's inner content captured verbatim in source
	// order. The renderer recompiles it as a Markdown fragment, so it may hold
	// Markdown, nested JSX or a mix of both.
	RawInner []byte
}

JSXBlock represents a block-level JSX element parsed from MDX source, for example:

<Alert type="warning" dismissible>
  Some **markdown** content here.
</Alert>

func (*JSXBlock) Dump

func (n *JSXBlock) Dump(source []byte, level int)

Dump writes a human-readable representation of the node for debugging, satisfying the ast.Node contract.

func (*JSXBlock) Kind

func (n *JSXBlock) Kind() ast.NodeKind

Kind returns KindJSXBlock, satisfying the ast.Node contract.

type JSXExpression

type JSXExpression struct {
	ast.BaseInline

	// Expression is the raw JS expression without the surrounding braces.
	Expression []byte

	// IsTopLevel reports whether the expression must be hoisted to module scope,
	// as with import declarations and export statements.
	IsTopLevel bool
}

JSXExpression represents a JavaScript expression wrapped in curly braces, such as the {2 + 2} in "The result is {2 + 2} items." It also represents top-level import and export statements, in which case IsTopLevel is true.

func (*JSXExpression) Dump

func (n *JSXExpression) Dump(source []byte, level int)

Dump writes a human-readable representation of the node for debugging, satisfying the ast.Node contract.

func (*JSXExpression) Kind

func (n *JSXExpression) Kind() ast.NodeKind

Kind returns KindJSXExpression, satisfying the ast.Node contract.

type JSXInline

type JSXInline struct {
	ast.BaseInline

	// RawContent is the full raw source of the inline JSX, such as
	// `<Kbd>Ctrl</Kbd>`.
	RawContent []byte
}

JSXInline represents a JSX tag that appears inline within paragraph text, for example the <Button> in "Click <Button onClick={handler}>here</Button> to continue." The entire raw tag, including children, is stored verbatim so the renderer can emit it unchanged.

func (*JSXInline) Dump

func (n *JSXInline) Dump(source []byte, level int)

Dump writes a human-readable representation of the node for debugging, satisfying the ast.Node contract.

func (*JSXInline) Kind

func (n *JSXInline) Kind() ast.NodeKind

Kind returns KindJSXInline, satisfying the ast.Node contract.

type MDXExtension

type MDXExtension struct {
	// Renderer is the JSX renderer shared between parsing and compilation.
	Renderer *jsxRenderer
}

MDXExtension is the goldmark.Extender that adds MDX support. It registers the JSX block, inline and expression parsers together with the JSX renderer. The renderer is exposed so that Compile can retrieve the hoisted top-level statements collected during the render walk.

func NewMDXExtension

func NewMDXExtension() *MDXExtension

NewMDXExtension creates an MDXExtension along with its shared renderer instance.

func (*MDXExtension) Extend

func (e *MDXExtension) Extend(m goldmark.Markdown)

Extend registers the MDX block parser, inline parsers and renderer with m, satisfying the goldmark.Extender interface.

The block parser is given priority 90 so it runs ahead of goldmark's default HTML block parser (priority 70). The inline parsers sit above goldmark's defaults. The renderer is registered at priority 100: goldmark applies node renderers in reverse priority order, so a number below the default HTML renderer's 1000 ensures every kind registered here overrides it.

Directories

Path Synopsis
cmd
example command
Command example demonstrates the mdx-go library by compiling a sample MDX document and printing the resulting ESM JavaScript module.
Command example demonstrates the mdx-go library by compiling a sample MDX document and printing the resulting ESM JavaScript module.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL