function remarkToMarkdown(obj, remarkPlugins) {
  /**
   * Rewrite the remark-stringify text visitor to simply return the text value,
   * without encoding or escaping any characters. This means we're completely
   * trusting the markdown that we receive.
   */
  function remarkAllowAllText() {
    const Compiler = this.Compiler;
    const visitors = Compiler.prototype.visitors;
    visitors.text = node => node.value;
  }

  /**
   * Provide an empty MDAST if no value is provided.
   */
  const mdast = obj || u('root', [u('paragraph', [u('text', '')])]);

  const remarkToMarkdownPluginOpts = {
    commonmark: true,
    fences: true,
    listItemIndent: '1',

    /**
     * Use asterisk for everything, it's the most versatile. Eventually using
     * other characters should be an option.
     */
    bullet: '*',
    emphasis: '*',
    strong: '*',
    rule: '-',
  };

  const processor = unified()
    .use({ settings: remarkToMarkdownPluginOpts })
    .use(remarkEscapeMarkdownEntities)
    .use(remarkStripTrailingBreaks)
    .use(remarkToMarkdownPlugin)
    .use(remarkAllowAllText)
    .use(createRemarkShortcodeStringifier({ plugins: getEditorComponents() }))
    .use(remarkPlugins);

  /**
   * Transform the MDAST with plugins.
   */
  const processedMdast = processor.runSync(mdast);

  /**
   * Serialize the MDAST to markdown.
   */
  const markdown = processor.stringify(processedMdast).replace(/\r?/g, '');

  /**
   * Return markdown with trailing whitespace removed.
   */
  return trimEnd(markdown);
}
Serialize an MDAST to a Markdown string.