FBMarkdown is an extensible parser and renderer for GitHub Flavored Markdown, written in Hack.
It is used to render the Hack and HHVM documentation.
For docs.hhvm.com, we wanted:
- GitHub Flavored Markdown for familiarity
- support for custom extensions
Originally, the Ruby GFM pipeline was the best fit; over time, we started to want to:
- make it easier and faster to contribute to docs.hhvm.com
- remove the Ruby dependencies to make it easy to render markdown in other Hack projects
- produce and mutate an AST before rendering
- support multiple renders
FBMarkdown exists to address all of these goals.
- HHVM 3.24 or above.
- hhvm-autoload
hhvm composer.phar require facebook/fbmarkdown
use namespace Facebook\Markdown;
function render(string $markdown): string {
$ast = Markdown\parse(new Markdown\ParserContext(), $markdown);
$html = (new Markdown\HTMLRenderer(
new Markdown\RenderContext()
))->render($ast);
return $html;
}
// or simply
function render(string $markdown): string {
return Markdown\Environment::html()->convert($markdown);
}
For complete compatibility with GitHub Flavored Markdown, support for embedded HTML must be enabled; it is disabled by default as a security precaution.
$ctx = (new Markdown\ParserContext())->enableHTML_UNSAFE();
$ast = Markdown\parse($ctx, $markdown);
// using `Facebook\Markdown\Environment`
$unsafe = true;
$environment = Markdown\Environment::html($unsafe);
$ast = $environment->parse($markdown);
If you are re-using contexts to render multiple independent snippets, you will need to call ->resetFileData()
on the context.
- The classes in the
Facebook\Markdown\UnparsedBlocks
namespace convert markdown text to a tree of nodes representing the block structure of the document, however the content of the blocks is unparsed. - The contents of the blocks ('inlines') are parsed using the classes in the
Facebook\Markdown\Inlines
namespace. - Finally, the classes of the
Facebook\Markdown\Blocks
namespace are used to represent the fully parsed AST - blocks and Inlines.
The AST is recursively walked, emitting output for each note - e.g. the HTML renderer produces strings.
There are 2 main ways to extend FBMarkdown: extending the parser, and transforming the AST.
Extend Facebook\Markdown\Inlines\Inline
or a subclass, and pass your classname to
$render_ctx->getInlineContext()->prependInlineTypes(...)
.
There are then several approaches to rendering:
- instantiate your subclass, and add support for it to a custom renderer
- instantiate your subclass, and make it implement the
Facebook\Markdown\RenderableAsHTML
interface - if it could be replaced with several existing inlines, return a
Facebook\Markdown\Inlines\InlineSequence
, then you won't need to extend the renderer.
You will need to implement the Facebook\Markdown\UnparsedBlocks\BlockProducer
interface, and pass your classname
to $render_ctx->getBlockContext()->prependBlockTypes(...)
.
There are then several approaches to rendering:
- create a subclass of
Block
, and add support for it to a custom renderer - create a subclass of
Block
, and make it implement theFacebook\Markdown\RenderableAsHTML
interface - if it could be replaced with several existing blocks, return a
Facebook\Markdown\Blocks\BlockSequence
- if it could be replaced with a paragraph of inlines, return a
Facebook\Markdown\Blocks\InlineSequenceBlock
Extend Facebook\Markdown\RenderFilter
, and pass it to $render_ctx->appendFilters(...)
.
The Hack and HHVM documentation uses most of these approaches; see:
FBMarkdown is MIT-licensed.
FBMarkdown may contain third-party software; see third_party_notices.txt for details.