background

Because I am unemployed recently, I have nothing to do, so I have a React component library (I don’t want to interview). I mainly dislike ANTD, and some other reasons. Also sent over the boiling point, there are also some curious implementation scheme, so there is this article.

As a component library, there is still a documentation, so I searched the market for some documentation schemes:

  1. vitepress
  2. storybook
  3. dumi

Vitepress is based on vite and Vue, based on SSR rendering, suitable for static documents, if you need to support React, you need to change some things.

As for Storybook and Dumi, I don’t like them for personal reasons. So I decided to do one myself and see how the documentation tool works.

Here, as in most component libraries, the documentation tool will be implemented through MarkDown, which will eventually be rendered to the page.

rendering

A final rendering:

implementation

How do you render markdown to the page?

We know markdown will eventually be parsed to HTML, so we can build on that. Since I’m using WebPack here, I’ll base the illustration on WebPack.

An important concept of Webpack is loader. The main function of loader is to convert the received content, return it and transfer it to the next loader, and each loader has a single responsibility. So we need to implement loader for markdwon conversion processing.

A simple loader

// Own loader
module.exports = functin mdLoader (source) {
    return source
}

// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [{test: /\.md$/,
                use: path.resolve(__dirname, '.. /loader/mdLoader.js'}]}}Copy the code

We’re going to use a couple of libraries here

  • markdown-itOne for parsingmarkdownGrammar of library
  • markdown-it-containerFor customizationmarkdowngrammar
  • Markdown-it-anchor adds an anchor point to the document
  • Markdown-it-toc-done-right adds a directory to the document
  • Gray-matter supports writing YAML Front Matter in MD

A button component document:

Since we need to render the MarkDown onto the page and match the route, we need to implement a component via loader.

module.exports = function mdLoader (source) {
   // Initialize markdown-it
  const md = new MarkdownIt({
    html: true.breaks: true.typographer: true.highlight(str, lang) {
       // Highlight code blocks
      const template = `
          <HighlightCode
            lang={\`${lang}\`}
            code={\`${str}\`} 
          >
          </HighlightCode>
      `
      return template
    }
  })

  const mdToHtml = md
    .render(source)
    .replace(/<hr>/g.'<hr />')
    .replace(/<br>/g.'<br />')
    .replace(/class=/g.'className=')

  // The react Component returned is used for routing
  return `
    import * as React from 'react'
    import Block from 'Block'
    import { CodeExample } from 'CodeExample'
    import { HighlightCode } from 'bangumi-ui'

    function MdReact () {
      return <div className='b-md-container markdown-body'>${mdToHtml}</div>
    }

    export default MdReact
  `
}
Copy the code

Here is the React Component we implemented, rendering the Markdown into HTML via markdown-it and putting it in a React Component string we wrote.

At present, a small part of this thing is completed, but it is still not enough, we still need to revamp WebPack Config.

// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [{test: /\.md$/,
                use: ['babel-loader', path.resolve(__dirname, '.. /loader/mdLoader.js')]}}]// ...
}
Copy the code

The main function of using babel-loader is to pass the result of our own loader processing to babel-loader. The babel-loader will further convert the result into a format that can be recognized by React.

React now renders the markdown, as long as the router is configured.

As for Anchor and TOC, refer to github documentation. Container is used to customize code blocks, which is used in VitePress and Vuepress. Warning, success, danger

This is the syntax of the Container, and demo is your custom name.

::: demo
:::
Copy the code

Implementation:

const md = new MarkdownIt({
    html: true.breaks: true.typographer: true.highlight(str, lang) {
      const template = `
          <HighlightCode
            lang={\`${lang}\`}
            code={\`${str}\`} 
          >
          </HighlightCode>
      `
      // process.exit()
      return template
    }
  })
  .use(container, 'desc', {
      // Render with tokens tokens is a syntax structure and index is the current start and end indices
      render(tokens, index) {
          if (tokens[index].nesting === 1) {
              // opening tag
              return `<Block>`
            } else {
              // closing tag Indicates the closing tag
              return '</Block>'}}})Copy the code

The other one is gray-matter, which I introduced with custom because the component will not be found when rendering.

module.exports = function mdLoader (source) {
   // Initialize markdown-it
  const md = new MarkdownIt({
    html: true.breaks: true.typographer: true.highlight(str, lang) {
       // Highlight code blocks
      const template = `
          <HighlightCode
            lang={\`${lang}\`}
            code={\`${str}\`} 
          >
          </HighlightCode>
      `
      return template
    }
  })
 
  const { content, data } = matter(source)
  const mdToHtml = md
    .render(content)
    .replace(/<hr>/g.'<hr />')
    .replace(/<br>/g.'<br />')
    .replace(/class=/g.'className=')

  // The react Component returned is used for routing
  return `
    import * as React from 'react'
    import Block from 'Block'
    import { CodeExample } from 'CodeExample'
    import { HighlightCode } from 'bangumi-ui'
    ${data.import}
    
    function MdReact () {
      return <div className='b-md-container markdown-body'>${mdToHtml}</div>
    }

    export default MdReact
  `
}
Copy the code

At this point aloaderThis is a complete code.

end

This article is mainly about how markdown is rendered, a brief understanding of how the documentation tools of the component library are implemented, and also a loader for parsing our MD files. The working principle of Loader is also mentioned in passing.