The problem: an Ast isn't renderable yet
Lesson 2's Ast describes the document's structure (this is a heading, that's a list), but it's a Markdoc-specific shape, not something an HTML renderer or React can consume directly, and it hasn't resolved anything a config would supply, no variable values, no custom tags. transform() is the stage that does both: it walks the Ast and produces a render tree, a plain, renderer-agnostic tree of Markdoc.Tag objects and raw strings.
The code, piece by piece
const renderTree = Markdoc.transform(ast, {});transform takes the Ast plus a config object. Passing {} here means no custom tags, variables, or functions are registered, transform still runs completely, built-in Markdown constructs (headings, paragraphs, lists, bold text) resolve to their default Tag equivalents on their own, no config entry needed for heading -> h1 or list -> ul, that mapping is Markdoc's built-in default behavior (the same one Lesson 14 later shows how to override).
function describeRenderNode(node, depth = 0) { if (typeof node === "string") { ... } console.log(`<${node.name}>`, node.attributes); for (const child of node.children) { ... }}Render tree nodes are Markdoc.Tag instances with .name, .attributes, .children, note the field is .name here, not .type like an Ast node, that's one of several small shape differences between the two trees. Render tree children can also just be plain strings (raw text), Ast nodes never appear as bare strings the way render tree children do.
Ast vs. render tree, side by side
- Produced by:
Astcomes fromMarkdoc.parse(source); the render tree comes fromMarkdoc.transform(ast, config). - Node identity field:
Astnodes carry.type(e.g."heading"); render tree nodes carry.name(e.g."h1"). - Needs a config?
Ast: no. Render tree: yes, even{}counts. - Can contain raw strings as children?
Ast: no, always nodes. Render tree: yes. - What consumes it:
Astfeedstransform()/validate(); the render tree feeds a renderer,renderers.htmlorrenderers.react.
Checkpoint
Markdoc.transform(ast, config): the middle pipeline stage,Ast+ config -> render tree. This is where tags, variables, and functions actually get resolved (starting Lesson 7).- Render tree shape:
Markdoc.Tagobjects with.name/.attributes/.children, plus raw strings as children, distinct from theAst's.type/.attributes/.childrenshape. - Config isn't optional in spirit, even if it's optional in code: an empty
{}still produces a full render tree, because built-in Markdown constructs have defaultTagmappings baked in.
If anything here still feels unclear, ask before moving to Lesson 4.