npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

simple-commonmark-react

v0.9.0-rc2

Published

React renderer for Commonmark (rationalized Markdown) written in TypeScript

Downloads

50

Readme

Simple CommonMark React

npm version Build Status Coverage Status styled with prettier

This module renders Markdown as React components using Commonmark as parser. You can find a demo app here.

All nodes emitted by commonmark are supported except for inline html. I started this project because I needed an easier way to style the markdown components.

Install

yarn add simple-commonmark-react

Usage

This module exports a single attribute: a function called renderNodes which takes two arguments: source and options. It returns an array of React elements that you can easily render inside a div.

const renderNodes = (source: string, options: RenderOptions | undefined): ReactElement<any>[] => {
  //implementation
}

source is the markdown string that you want to render. options is an optional argument that allows you to configure the rendering. All options are undefined by default. The following options are supported:

Name | Type | Description --- | --- | --- className | string | A string with the class name(s) to add to every single markdown component, useful if you want to style your components with CSS. allowSoftBreaks | boolean | If true, all line breaks (\n) are rendered as <br /> tags customRenderers | CustomRendererDict | An object that contains functions to create custom renderers that override the default ones. See the customization section

Example

import React from 'react'
import { renderNodes } from 'simple-commonmark-react'

class MarkdownComponent extends React.Component {
  render() {
    const source = this.props.markdownText
    // we set className prop so that we can style each element with CSS
    const markdownOptions = { className: 'markdown' }
    return (
      <div>
        { renderNodes(source, markdownOptions) }  
      </div>
    )
  }  
}

How It Works

This module parses your markdown with CommonMark, and then walks the generated AST and assigns each node type a different renderer. Renderers here are classes in ./src/renderers/. The renderer takes a node, a renderOptions object and a unique key, and returns a React element. The following table shows a rough aproximation of how each node is renderered.

Type | Renderer Class | Input Markdown | Output HTML --- | --- | --- | --- emph | ItalicsRenderer | *italics* | <em>italics</em> strong | BoldRenderer | **bold** | <strong>bold</strong> link | LinkRenderer | [link](/to/some/path) | <a href="/to/some/path">link</a> image | ImageRenderer | ![link](/to/some/pic.png) | <img src="/to/some/pic.png">image</img> code | CodeRenderer | inline code | <code>inline code</code> code block | CodeBlockRenderer | \n```\nblock code\n```\n | <pre><code>block code</code></pre> block quote | QuoteBlockRenderer | > this is a quote | <blockquote><p>this is a quote</p></blockquote> paragraph | ParagraphRenderer | a paragraph | <p>a paragraph</p> list (bullet) | ListRenderer & ListItemRenderer | - item 1\n- item 2 | <ul><li>item 1</li><li>item 2</li></ul> list (ordered) | ListRenderer & ListItemRenderer |1. item 1\n2. item 2 | <ol><li>item 1</li><li>item 2</li></ol> heading | HeaderRenderer | # title | <h1>title</h1> thematic_break | BreakRenderer | *** | <hr/>

Once you have an idea how each is rendered, you can style your components with CSS.

Customization

If you can't get the style that you want with CSS or want to customize even further you can override the default renderer classes from the previous section with your custom renderer. There are many ways to create new renderers. Let's start with a simple but practical example: Let's create a new renderer for link that uses React Router for navigation.

Custom renderer with TypeScript

If you use TypeScript, you can just extend the default LinkRenderer and just override the renderNodeWithProps method to use React Router's Link instead of an <a> tag.

import { ReactElement, createElement } from 'react'
import { Link } from 'react-router-dom'
import LinkRenderer from 'simple-commonmark-react/src/renderers/LinkRenderer'

export default class ReactRouterLinkRender extends LinkRenderer {

  renderNodeWithProps(props: any): ReactElement<any> {
    //Link puts the url in 'to' rather than 'href', let's swap them
    const url = props.href
    delete props.href
    props.to = url
    return createElement(Link, props, [])
  }
}

LinkRenderer's renderNodeWithProps recieves the props object ready for an <a> tag. It already contains key, href, and even className (if it was specified with renderOptions). So all we had to do is tweak it a little to play along with React Router and render the Link class.

Custom renderer with ES6 classes

Alternatively, you could also extend LinkRenderer with an ES6 class like this:

import { createElement } from 'react'
import { Link } from 'react-router-dom'
import LinkRenderer from 'simple-commonmark-react/src/renderers/LinkRenderer'

export default class ReactRouterLinkRenderer extends LinkRenderer {

  renderNodeWithProps(props) {
    //Link puts the url in 'to' rather than 'href', let's swap them
    const url = props.href
    delete props.href
    props.to = url
    return createElement(Link, props, [])
  }
}

As you can see this is identical to the TypeScript implementation, it only lacks type annotations.

Custom renderer with regular JavaScript

If you are cool with plain old JavaScript you can implement the renderer as a regular function that returns a renderNode function.

var createElement = require('react').createElement
var Link = require('react-router-dom').Link

module.exports = function ReactRouterLinkRenderer(node, options) {
  return {
    renderNode: function (key) {
      //set key and class name from options
      var props = { key, className: options.className }
      //set title if there is any any
      var title = node.title
      if (title)
        props.title = title
      // set the link url
      props.to = node.destination

      return createElement(Link, props, [])
    }
  }

}

This one is a bit different, a lot more verbose since you can't extend the default renderer and let it do all the heavy lifting. You have to manually get your props from the markdown node and rendererOptions. Also the function that you implement is renderNode and not renderNodeWithProps because the default renderers expose only renderNode, that's the function that the library will call when it's time to render.

All 3 implementations are equivalent, in fact, you can get the TypeScript implementation by importing simple-commonmark-react-router into your project. once it is ready all you have to do is add the renderer to renderOptions's customRenderers.

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
} from 'react-router-dom'
import { renderNodes } from 'simple-commonmark-react'
import ReactRouterLinkRenderer from './ReactRouterLinkRenderer'

class MarkdownComponent extends React.Component {
  render() {
    const source = this.props.markdownText
    const markdownOptions = {
      className: 'markdown',
      customRenderers: { link: ReactRouterLinkRenderer }
    }

    return (
      <Router>
        //Here you could put your routes
        <div>
          { renderNodes(source, markdownOptions) }  
        </div>
      </Router>
    )
  }  
}

As you can see, we use 'link' as key to insert ReactRouterLinkRenderer because that's the string that CommonMark uses to identify that particular Markdown element. That's how the library knows that you are overriding link and not image or paragraph. The keys you can use for overriding are: text, softbreak, emph, strong, link, image, code, document, paragraph, item, list, heading, code_block, block_quote. If you use the wrong key, your renderer will not be used.