DavidWells / DavidWells/markdown-magic
How does the transforms system work — how are custom transforms registered and executed?
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 868
- Forks
- 223
- PR merge metrics
- No merged PRs in 30d
Description
Question
How does the transforms system work — how are custom transforms registered and executed?
Answer
Markdown Magic works by scanning markdown for HTML comment blocks (e.g. <!-- docs TRANSFORM_NAME opt=val --> … <!-- /docs -->), running a transform function for each block, and replacing the block's inner content with whatever that function returns. A "transform" is just a named function; "plugins" are simply transforms (or factories that return transforms) you register by name.
1. What a transform is
A transform is a function with the signature (api) => string | Promise<string>. The single api argument (the "plugin API") is built in packages/block-transformer/src/index.js and includes:
content— current inner content of the comment blockoptions— key/value options parsed from the opening comment (e.g.src="./x.js" lines=1-10)transform— the transform's namesrcPath/outputPath— file pathssettings— the merged run config plussettings.regexpatternscurrentContent/originalContentand gettersgetCurrentContent(),getOriginalContent(),getOriginalBlock(),getBlockDetails()
Whatever the function returns replaces the block's contents. Non-string returns are coerced: numbers become strings, arrays/objects are JSON.stringify'd. Returning falsy leaves the existing content unchanged.
2. Built-in transforms (the default registry)
The built-ins live in packages/core/src/transforms/ and are collected into a plain object in packages/core/src/transforms/index.js:
TOC, sectionToc, CODE, FILE, REMOTE, fileTree, install (+ wordCount)
This object is the default transform registry.
3. How custom transforms are registered
You register transforms by passing a transforms object on the config to markdownMagic() — a map of name -> function:
markdownMagic('README.md', {
transforms: {
// <!-- docs customTransform optionOne=hi -->
customTransform({ content, options }) {
return `Replaced with ${options.optionOne}`
},
// A plugin is just a factory that returns a transform function
pluginExample: require('./plugin-example')({ addNewLine: true }),
}
})
Registration happens in packages/core/src/index.js:
const useTransforms = Object.assign({}, defaultTransforms, transforms)
Key points:
- Your custom transforms are merged over the defaults, so a custom transform can override a built-in of the same name.
- The name is the object key — that key is what you write after
STARTin the comment block. Lookup is case-insensitive with a lowercase fallback (transforms[name] || transforms[name.toLowerCase()], seegetTransform). - A "plugin" is conventionally a function that returns a transform function (
(pluginOptions) => (api) => string), so you can configure it at registration time. There's no separate registration API — plugins are just entries in the sametransformsobject, and they run in registration order. - If you have an
md.config.js/markdown.config.js, itstransformsare loaded automatically (CLI) via the config loader.
4. How transforms are executed
Execution flows core → processContents → blockTransformer:
- Parse —
packages/core/src/process-contents.jsdelegates toblockTransformer(comment-block-transformer), which callsparseBlocks(comment-block-parser) to find all comment blocks. Each block's first token is treated as the transform name (firstArgIsType: true); options are parsed intooptions. - Plan / validate — core builds a plan of blocks to run. Blocks whose transform name isn't in the registry are collected as
missingTransforms. By default these are skipped with a warning; withfailOnMissingTransforms: truethe run errors (Transform "X" ... does not exist). - Sort —
sortTransformsreorders so thatTOCandsectionTocrun last (they need the rest of the document finalized first). Position-based replacement offsets keep replacements correct even when blocks run out of document order. - Run sequentially —
blockTransformerreduces over the blocks with anasyncaccumulator so each transform sees the up-to-date document. For each block it:- applies any
beforeMiddleware, - looks up the function via
getTransform(name, transforms), - calls it with the plugin API object (awaiting, so transforms may be async),
- applies
afterMiddleware, - normalizes the return value, re-indents, and splices it back into the document using position offsets.
- applies any
- Result — returns
{ isChanged, updatedContents, transforms, missingTransforms, ... }. Core then writes the file back (or tooutputDir), honoring flags likeapplyTransformsToSource,removeComments, anddryRun.
5. Inline vs block transforms
Transforms also work inline (no line break between open/close), e.g.
<!-- docs (INLINE_EXAMPLE) -->**⊂◉‿◉つ**<!-- /docs -->. The same registered function is used; the transformer detects single-line context and skips added indentation/newlines.
Where to look in the code
packages/core/src/index.js— config handling,Object.assign({}, defaultTransforms, transforms)merge, planning/validation, file writingpackages/core/src/transforms/index.js— the default built-in registrypackages/core/src/transforms/*— individual built-in transform implementationspackages/core/src/process-contents.js→packages/block-transformer/src/index.js— the execution engine (blockTransformer,getTransform,sortTransforms, middleware, plugin API construction)packages/block-parser/(comment-block-parser) — parses the comment blocks and their options- README "Adding Custom Transforms" / "Plugin Example" / "Inline transforms" sections — user-facing docs
This issue was generated in response to a dictated question about the repository (E2E test, plan 25). No code changes were made.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review the README sections on Adding Custom Transforms, Plugin Example, and Inline transforms alongside packages/core/src/index.js and packages/block-transformer/src/index.js. Confirm that the user-facing documentation explains registration, lookup, execution, and missing-transform behavior; done means the documented flow matches the referenced implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100