MDX pages — tcm-ts

MDX pages

An .mdx page is Markdown with components in it (MDX). In tcm it's a definition, as code is: what it imports and renders are references by hash, it's typechecked when it's saved, and it's a React component that code renders.

---
title: Counting
---

import { Counter } from "#codebase/ui"

# Counting

Click it: <Counter start={2} />. It's {@link ui.Counter}.
tcm add counting.mdx --into blog      # saves blog.counting
tcm edit blog.counting                # writes counting.mdx; change it, then `tcm update counting.mdx`
tcm view blog.counting

Checked when saved

  • It's typechecked as MDX compiles it, by TypeScript's content mapper for MDX (mdx-content-mapper, installed the first time it's needed). That covers components' props, the expressions in {…}, what's imported, and props and components as MDX types them. A page that doesn't check isn't saved, and each problem is given at its line in the page: counting.mdx:9:20: Type 'string' is not assignable to type 'number'. Code blocks aren't checked.
  • It says what props it uses: {/** @typedef Props @property {string} name */} lets it use {props.name}. Without one, it uses none but components. (Code rendering it isn't checked against them.)
  • Editors check it as you write it. In VS Code (and any editor running tcm lsp), an open .mdx page shows its problems at its lines, as saving it would.
  • Imports are by name, from #codebase/… and from packages, as in code. A page can't import a stylesheet (import "./x.css"), a whole namespace of files, or import() a module; a component it renders can.
  • A page can't export. It's one definition: data goes in its frontmatter, code in the codebase.
  • It can say where it goes in its first line, as tcm edit writes it: {/* tcm: path blog.counting */}, or {/* tcm: namespace blog */} plus its own name. That line isn't kept.

Renames and updates

  • A rename doesn't change it. After tcm mv ui.Counter widgets.Clicker, the page reads <Clicker />, imported from #codebase/widgets, and keeps its hash.
  • An update carries through. Updating a component the page renders re-derives the page, and the code that renders the page, as it does for code.
  • It's checked again. A page that no longer typechecks against what it uses now (a component's props changed) keeps the version it had and goes in tcm todo, with each problem at its line, as code does. Fix it with tcm edit, then tcm update.
  • Imports aren't kept as written. tcm view and tcm edit write them from the page's references, at its top (under its frontmatter), with current names, as they do for a .tsx file. Moving or regrouping them changes nothing; a change to its text does.

Rendering one

A page is a React component. Code imports it like any definition. Its name is a file's (blog.counting), so it's rendered under a capitalized name:

// tcm: namespace site
import { counting as Counting } from "#codebase/blog";

export function App() {
  return <main><Counting /></main>;
}
  • Its components run where they need to. The page renders on the server inside server components. A component in it that has state ships to the browser and hydrates, as any browser component does (React).
  • components swaps Markdown's elements, as in MDX: <Counting components={{ h1: Title }} />.
  • {@link name} is a reference by hash, as in Markdown pages. It follows renames, and the page shows the name in code style. Links to other pages are kept as written.
  • Its frontmatter isn't shown. The component's text is the page as saved, frontmatter included.

In a collection

A namespace of MDX pages is a collection like one of Markdown pages. Each entry's data is its frontmatter, checked against the schema when the page is saved, and its Content is the page:

export const Posts = () =>
  posts.all().map((p) => (
    <article key={p.name}>
      <h2>{p.data.title}</h2>
      <p.Content />
    </article>
  ));

Where it shows

tcm view, tcm edit, tcm export, mirrors and packages write the page as .mdx, with its imports. The web UI and the code browser show it as written, and also render it as Markdown with its JSX and expressions shown as code.

Built from c482a14e