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;
}
FBMarkdown currently supports three types of Markdown sources, with plans to expand: trusted, sponsored, and user-generated content.
-
Trusted content mode: Embedded HTML is enabled, and all URI schemes are enabled and will be parsed as links. In addition, all images are processed normally.
-
Sponsored mode: HTML rendering is enabled, but limited to allowed tags only (defined in
TagFilterExtension
, based on the GFM spec). Additionally, URIs are limited to the {http
,https
,irc
, andmailto
} schemes, andrel="nofollow ugc"
is added to all links. -
User-generated content: All HTML is disabled, as are links and images regardless of schemes. If links are re-enabled,
rel="nofollow ugc"
will be added to all links.
To make changes to these default settings:
- You may alter the keyset of allowed URI schemes by calling the Parser function
setAllowedURISchemes()
. - You may enable embedded HTML by calling the Parser function
enableHTML_UNSAFE()
. N.B.: For complete compatibility with GitHub Flavored Markdown, support for embedded HTML must be enabled. - You may disable image filtering by calling the Renderer function
disableImageFiltering()
. - You may add
rel="nofollow ugc"
to all links by calling the Renderer functionaddNoFollowUGCAllLinks()
.
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.