React-markdown: Plugins are not working

Created on 23 Jun 2018  路  11Comments  路  Source: remarkjs/react-markdown

Thanks for creating this amazing package! This is a really awesome idea.

I would like to ask for some help with plugins. For some reason the don't do anything.

My code

import React from "react";
import { render } from "react-dom";
const ReactMarkdown = require("react-markdown");
const toc = require("remark-toc");

const input = `
# Alpha

## Table of Contents

## Bravo

### Charlie

## Delta
`;

const App = () => <ReactMarkdown source={input} plugins={[toc]} />;

render(<App />, document.getElementById("root"));

codesandbox: https://codesandbox.io/s/340pq1r2l6

The output:

<div id="root">
    <div>
        <h1>Alpha</h1>
        <h2>Table of Contents</h2>
        <h2>Bravo</h2>
        <h3>Charlie</h3>
        <h2>Delta</h2>
    </div>
</div>

The table of contents is not created also slugs are not added to headings.

help wanted 馃檹

Most helpful comment

So, turned out this version support only _parser_ plugins. Basically, if you take a look at this diagram only first stage (namely, parse()) is called during the processing and most of the react-* are actually transformers that are applied in the second run() stage.

Probably, it could be fixed by just running run() in the ReactMarkdown function itself after initial parse call. I haven't tried that.

For me the workaround that did the job was to use astPlugins parameter (which is not documented for some reason, may be even a good one). It takes plugins in a very similar format:

import linker from '../src/Documentation/remark-linker'
...
astPlugins={[linker()]}

Note, that function call is made to actually get the transformer from the plugin description.

I'm not sure this is 100% compatible with all the plugins. In my case I wrote the plugin I needed for the dvc documentation site:

function linker() {

  function transformer(tree) {
    visit(tree, 'inlineCode', function(node, index, parent) {
      if (parent.type !== 'link' && /dvc [a-z-.]+/.test(node.value)) {
        parent.children[index] = {
          type: 'link',
          url: 'https://dvc.org/doc/commands-reference/' + node.value.split(' ')[1],
          children: [node],
          position: node.position
        };
      }
    })
    return tree
  }

  return transformer
}

All 11 comments

Can't get remark-autolink-headings to work either. The resulting AST transform function in the plugin is never called.

So, turned out this version support only _parser_ plugins. Basically, if you take a look at this diagram only first stage (namely, parse()) is called during the processing and most of the react-* are actually transformers that are applied in the second run() stage.

Probably, it could be fixed by just running run() in the ReactMarkdown function itself after initial parse call. I haven't tried that.

For me the workaround that did the job was to use astPlugins parameter (which is not documented for some reason, may be even a good one). It takes plugins in a very similar format:

import linker from '../src/Documentation/remark-linker'
...
astPlugins={[linker()]}

Note, that function call is made to actually get the transformer from the plugin description.

I'm not sure this is 100% compatible with all the plugins. In my case I wrote the plugin I needed for the dvc documentation site:

function linker() {

  function transformer(tree) {
    visit(tree, 'inlineCode', function(node, index, parent) {
      if (parent.type !== 'link' && /dvc [a-z-.]+/.test(node.value)) {
        parent.children[index] = {
          type: 'link',
          url: 'https://dvc.org/doc/commands-reference/' + node.value.split(' ')[1],
          children: [node],
          position: node.position
        };
      }
    })
    return tree
  }

  return transformer
}

@rexxars Any thoughts on a solution or workaround for this? @shcheklein 's analysis is correct--transformer plugins do not work with react-markdown.

I ran into this while trying to integrate remark-ping for @mention syntax. It doesn't work with react-markdown, so I ended up using remark/rehype directly.

// render-markdown.js
import remark from "remark";
import remarkPing from "remark-ping";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeHighlight from "rehype-highlight";

export default function renderMarkdown(source) {
  const plugins = [
    [
      remarkPing,
      {
        //..config...
      },
    ],
    remarkRehype,
    rehypeStringify,
    rehypeHighlight
  ];
  const remarkInst = remark().use(plugins);
  return new Promise((resolve, reject) => {
    remarkInst.process(source, (err, rendered) => {
      err ? reject(err) : resolve(rendered.contents);
    });
  });
}

// Markdown.jsx
import React from "react";
import t from "prop-types";
import renderMarkdown from "./render-markdown";

export default class Markdown extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      rendered: ""
    };
  }
  componentDidMount() {
    const { source } = this.props;
    renderMarkdown(source).then(rendered => {
      this.setState({ rendered });
    });
  }
  render() {
    const { source, ...rest } = this.props;
    return (
      <div
        dangerouslySetInnerHTML={{ __html: this.state.rendered }}
        {...rest}
      />
    );
  }
}
Markdown.propTypes = {
  source: t.string.isRequired
};

This really isn't ideal, if only for the impact on bundle size (rehype-highlight alone ends up adding 190 kb to the min/gzip bundle).

The bundle size issue is the reason why I wanted to not include the full remark suite. If we are able to come up with a solution without adding too many bytes to the mix, that would be great. I unfortunately don't have time to dig into this right now, but would be very happy if someone else were to take a stab at it.

I needed to run a plugin for my react project so I dug into this issue a bit. @shcheklein was on the right track. I added the parser.runSync() function to get plugin transforms working. The other issue I came across was that transform generated nodes did not have position which would cause astToReact() to throw an error. I patched that by giving nodes' their parent's positioning. Not sure if that's the best idea, but it got things working. My fork is available here: https://github.com/frankieali/react-markdown. I updated the demo as well to show some third party plugins (remark-behead,remark-toc,remark-sectionize,remark-captions, and remark-linkify-regex) at work. I'll make a PR if you think this works.
My fix adds nothing to the bundle size (excepting the demo, which does not have to be PR'ed in).

Anyone knows why the remark-slug plugin doesn鈥檛 work? According to the above PR, looks like it should... See https://github.com/rexxars/react-markdown/issues/397

Did anyone manage to get remark-autolink-headings plugin working?

I鈥檓 also having trouble getting remark-external-links working. This is
important because it works around a security hole; the hole is properly
fixed by #350, but that PR hasn鈥檛 been merged.

Repro: https://codesandbox.io/s/silly-cherry-bznd3?file=/src/index.js

Note that the pre-rendered output properly includes both target and
rel attributes, whereas the react-markdown output has neither
attribute, suggesting that the plugin is not being called at all. Yet no
error is shown in the UI or console.

Repro code, for posterity

import React from "react"; // v16.13.1
import {render} from "react-dom"; // v16.13.1
import ReactMarkdown from "react-markdown"; // v4.3.1
import remark from "remark"; // v12.0.0
import externalLinks from "remark-external-links"; // v6.1.0
import html from "remark-html"; // v11.0.2

const input = "Here is [an example link](https://example.com).";

async function main() {
  const rendered = await new Promise((resolve, reject) => {
    remark()
      .use(externalLinks)
      .use(html)
      .process(input, (err, file) => {
        if (err != null) {
          reject(err);
        }
        resolve(file);
      });
  });
  const App = () => (
    <div>
      <p>Pre-rendered:</p>
      <pre>
        <code>{rendered}</code>
      </pre>
      <p>
        With <code>ReactMarkdown</code>:
      </p>
      <ReactMarkdown source={input} plugins={[externalLinks]} />
    </div>
  );
  render(<App />, document.getElementById("root"));
}

main();

Perhaps this issue should be reopened?

@shcheklein I'm trying to follow your example, I'm pretty new in AST can you point me out from where you are importing the visit function in your code?

    visit(tree, 'inlineCode', function(node, index, parent) {

馃檹

@nahumzs hi! sure here is the link - https://github.com/iterative/dvc.org/blob/7f88227d131c5fcf8eead8d7d6c62876152fb395/src/Documentation/Markdown/utils/remark-linker.js (we've updated the plugin since then). I'll edit the existing comment.

Many of these are related to #384, in that they depend on hProperties being supported to add HTML attributes, this will be resolved by #428

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  4Comments

hiteshpatwari picture hiteshpatwari  路  3Comments

joelin109 picture joelin109  路  3Comments

LinusU picture LinusU  路  3Comments

jonjaffe picture jonjaffe  路  3Comments