nextjs+MDX渲染md文件并生成目录
一、效果展示
线上网站:点击体验
md文件
网页
二、需要使用的模块包
包名 | 作用 |
---|---|
@mdx-js/react | 用来渲染react组件 |
@next-mdx-remote | 将字符串转换成html标签 |
@remark-slug | 为html标签添加id(锚点跳转) |
@mapbox/rehype-prism | 添加代码高亮 |
@jsdevtools/rehype-toc | 生成目录 |
@remark-gfm | 渲染表格插件 |
三、使用方法
项目目录结构
1
1.在_app.js中导入mdx-js/react
import { MDXProvider } from '@mdx-js/react';
function MyApp({ Component, pageProps }) {
// 定义 mdx 中语法的映射组件
const components = {
// img: props => <Image {...props}></Image>,
h1: props => {
return <h1>
<div id={props.id} className='relative -top-24 invisible' ></div>
<a href={'#' + props.id}>{props.children}</a>
</h1>
},
h2: props => {
return <h2>
<div id={props.id} className='relative -top-24 invisible' ></div>
<a href={'#' + props.id}>{props.children}</a>
</h2>
},
h3: props => {
return <h3>
<div id={props.id} className='relative -top-24 invisible' ></div>
<a href={'#' + props.id}>{props.children}</a>
</h3>
},
h4: props => {
return <h4>
<div id={props.id} className='relative -top-24 invisible' ></div>
<a href={'#' + props.id}>{props.children}</a>
</h4>
},
wrapper: ({ children, ...props }) => {
// console.log(children.map(child => child.props.mdxType))
// console.log(React.Children.toArray(children), 999)
// console.log(children)
// console.log(props, 111)
// if (props.layout) {
// return <main {...props} />
// }
return <>{children}</>
}
}
return <MDXProvider components={components}>
<Component {...pageProps}/>
</MDXProvider>
}
export default MyApp
2.在blog/[name].js中
2.1通过getStaticProps在服务器使用fs模块运行读取content文件目录下的md文件内容
2.2配置MDX各种插件
2.3在通过MDXRemote将文件内容渲染出来
2.4通过插件获取到元数据和目录
2.5在组件prop中获取到内容
2.6通过自定义组件TableContents生成自己的目录
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Layout from "../../components/Layout";
import slug from 'remark-slug'
import remarkGfm from 'remark-gfm'
import rehypePrism from '@mapbox/rehype-prism'
import TableContents from '../../components/TableContents';
import React from 'react';
// import { useInView } from 'react-intersection-observer';
export default function BlogPage(props) {
const [id, setId] = React.useState(array[0])
return (
<div className="wrapper">
<Layout href='/blog' full={true} theme={props.theme} setTheme={props.setTheme}>
{/* <div>{props.source.frontmatter.title}</div> */}
<div className='grid grid-cols-4 gap-6 relative'>
<div className='markdown xl:col-span-3 col-span-5 p-5 rounded-lg dark:bg-[#222222] box-shadow bg-white'>
<MDXRemote {...props.source} />
</div>
<div className='xl:block hidden'>
<div className='sticky top-24 p-5 rounded-lg dark:bg-[#222222] box-shadow bg-white'>
<h3 className="text-xl text-gray-900 dark:text-gray-100 dark:opacity-90 font-bold pb-4">目录</h3>
<TableContents idTable={id} {...props.tocElement}></TableContents>
</div>
</div>
</div>
</Layout>
</div>
)
}
export async function getStaticProps(context) {
// MDX text - can be from a local file, database, anywhere
const fs = require('fs')
const path = require('path')
const toc = require("@jsdevtools/rehype-toc");
let tocElement
const { name } = context.params
let source = String(fs.readFileSync(path.join(process.cwd(), 'src', 'content', name + '.md')))
const mdxSource = await serialize(source, {
scope: {},
mdxOptions: {
remarkPlugins: [slug, remarkGfm],
rehypePlugins: [rehypePrism, [toc, {
headings: ['h1', 'h2', 'h3', 'h4'],
customizeTOC: (tocAll) => {
tocElement = tocAll
return false
}
}]],
format: 'mdx'
},
parseFrontmatter: true
})
return { props: { source: mdxSource, tocElement: tocElement } }
}
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
}
}
TableContents组件
//拼接字符串工具
import clsx from 'clsx'
import React from 'react'
export default function TableContents(props) {
switch (props.tagName) {
case 'nav': {
return (
<nav {...props.properties}>{props.children.map((item, index) => {
return <TableContents {...item} key={index} idTable={props.idTable} />
})}</nav>
)
};
case 'ol': {
return (
<ol {...props.properties}>{props.children.map((item, index) => {
return <TableContents {...item} key={index} idTable={props.idTable} />
})}</ol>
)
};
case 'li': {
return (
<li {...props.properties}>{props.children.map((item, index) => {
return <TableContents {...item} key={index} idTable={props.idTable} />
})}</li>
)
};
case 'a': {
return (
<a {...props.properties} className={clsx('block py-1 text-sm font-medium hover:text-[#428dcc] focus:outline-none dark:hover:text-gray-200 focus-visible:text-gray-700 dark:opacity-90 dark:focus-visible:text-gray-200 text-gray-400', props.properties.href == '#' + props.idTable && 'text-[#428dcc] dark:text-gray-200')}>{props.children.map((item, index) => {
return <TableContents {...item} key={index} />
})}</a>
)
};
default: return (
<>{props.value}</>
)
}
}
3.引入CSS样式
在_app.js中
markdown样式可自行设计
prism官网代码高亮样式点击链接下载
import '../styles/prism.css'
import '../styles/markdown.css'