手撸组件库之文档工具
背景
因为最近失业中,便闲来无事,就整了个React
组件库(不想面试),主要不喜欢antd
,还有一些其他原因。也发过沸点,也有一些好奇实现方案的,所以便有了本文。
作为一个组件库,还是要有个文档的,所以搜了下市面的一些文档方案:
vitepress
storybook
dumi
vitepress
是基于vite
和vue
实现,基于SSR进行渲染,适合静态文档,如果需要支持react
的话,需要魔改一些东西,懒得折腾。
至于storybook
和dumi
,个人原因不太喜欢。所以就打算自己撸一个,顺便了解下文档工具是怎么实现的。
这边和大部分组件库一样,都将通过markdown
去实现文档工具,最终要把markdown
渲染到页面上。
效果图
最终的一个效果图:
实现
要如何把markdown
渲染到页面呢?
我们知道markdown
最终会解析成html
,可以根据这点做文章。由于我这里用的是webpack
,所以将基于webpack
进行说明。
webpack
有个重要的概念是loader
,loader
的主要作用是对接收到的内容做转换处理,并返回出去,传递给下一个loader
,而且每个loader
的职责是单一的。所以我们需要实现loader
,用于对markdwon
做转换处理。
一个简单的loader
// 自己的loader
module.exports = functin mdLoader (source) {
return source
}
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.md$/,
use: path.resolve(__dirname, '../loader/mdLoader.js')
}
]
}
}
这边会用到几个库
- markdown-it 一个用于解析
markdown
语法的库 - markdown-it-container 用于自定义
markdown
语法 - markdown-it-anchor 给文档添加锚点
- markdown-it-toc-done-right 给文档添加目录
- gray-matter 支持在md写YAML Front matter
一个button组件的文档:
由于我们需要把markdown
渲染到页面上,并且匹配对应路由,所以我们需要通过loader
去实现一个组件。
module.exports = function mdLoader (source) {
// 初始化markdown-it
const md = new MarkdownIt({
html: true,
breaks: true,
typographer: true,
highlight(str, lang) {
// 高亮代码块
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=')
// 返回的react component 用于做路由
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
`
}
上面就是我们实现的一个react component
,并且通过markdown-it
把markdown
渲染成了html
,并放入了我们写好的一个react component
字符串中。
目前这个东西算是完成了一小部分,但是这样依然是不够的,我们还需要改造下webpack config
。
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.md$/,
use: ['babel-loader', path.resolve(__dirname, '../loader/mdLoader.js')]
}
]
}
// ...
}
现在的话应该是这个样子,这里使用babel-loader
的主要作用是通过把我们自己写好的loader
处理后的结果传递给babel-loader
,babel-loader
会进一步转换,变成可以被React所认识的格式。
至此,你所写的markdown
就可以被react
所渲染了,只要配好router
就可以。
至于anchor、toc
参考github
文档说明,这里简单说下container
,container
的作用是自定义代码块,这点在vitepress、vuepresss
有被使用,指warning、success、danger
这是里面提前定义好的内容。
这是container
的语法,demo是你自定义的名字。
::: demo
:::
实现:
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', {
// 渲染时候会执行该函数,tokens是解析后的一个语法结构,index是当前的一个开始和结束下标
render(tokens, index) {
if (tokens[index].nesting === 1) {
// opening tag 开始标记
return `<Block>`
} else {
// closing tag 结束标记
return '</Block>'
}
}
})
还有一个是gray-matter
,这个是因为组件在渲染的时候,会找不到该组件,我用来自定义引入的。
module.exports = function mdLoader (source) {
// 初始化markdown-it
const md = new MarkdownIt({
html: true,
breaks: true,
typographer: true,
highlight(str, lang) {
// 高亮代码块
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=')
// 返回的react component 用于做路由
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
`
}
至此一个loader
的编写就算结束了,这是一个完整的代码。
end
这篇文主要是说了下markdown
是如何被渲染的,简单的了解下组件库的文档工具都是一个怎么样的实现方式,并且也撸了一个loader
用于解析我们的md
文件。顺便也提到了loader
的工作原理。
转载自:https://juejin.cn/post/7002898476388319269