Skip to content

Commit

Permalink
feat: 实现md大纲解析插件
Browse files Browse the repository at this point in the history
  • Loading branch information
c0dedance committed Oct 21, 2023
1 parent 47403b6 commit 7d6b3c2
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 4 deletions.
12 changes: 11 additions & 1 deletion e2e/playground/basic/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ literals www.example.com, https://example.com, and [email protected].

```js
console.log(2)
```
```

# h1

## h2 \`code\`

### h3 [link](https://islandjs.dev)

#### h4 你好啊

##### h5
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@playwright/test": "^1.26.1",
"@types/fs-extra": "^11.0.2",
"@types/hast": "^2.3.4",
"@types/mdast": "^3.0.10",
"@types/node": "^20.8.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
Expand All @@ -52,10 +53,13 @@
"dependencies": {
"@mdx-js/rollup": "2.1.3",
"@vitejs/plugin-react": "^4.1.0",
"acorn": "^8.10.0",
"cac": "^6.7.14",
"fast-glob": "^3.3.1",
"fs-extra": "^11.1.1",
"github-slugger": "^2.0.0",
"hast-util-from-html": "^2.0.1",
"mdast-util-mdxjs-esm": "^1.3.0",
"ora": "^7.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -65,9 +69,11 @@
"rehype-stringify": "^9.0.3",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.0",
"remark-mdx": "^2.1.5",
"remark-mdx-frontmatter": "^3.0.0",
"remark-parse": "^10.0.1",
"remark-rehype": "^11.0.0",
"remark-stringify": "^10.0.2",
"rollup": "^4.0.2",
"shiki": "^0.14.5",
"unified": "^10.1.2",
Expand Down
39 changes: 38 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 54 additions & 2 deletions src/node/__test__/md.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import remarkMdx from 'remark-mdx'
import remarkStringify from 'remark-stringify'
import rehypeStringify from 'rehype-stringify'

import shiki from 'shiki'
import { rehypePluginPreWrapper } from '../plugin-mdx/rehypePlugins/preWrapper'
import { THEME, rehypePluginShiki } from '../plugin-mdx/rehypePlugins/shiki'
import {
rehypePluginPreWrapper,
THEME,
rehypePluginShiki,
remarkPluginToc,
} from '../plugin-mdx'

describe('Markdown compile cases', async () => {
// 初始化 processor
Expand All @@ -17,6 +24,12 @@ describe('Markdown compile cases', async () => {
})
.use(rehypeStringify)

const remarkProcessor = unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkPluginToc)
.use(remarkStringify)

test('Compile title', async () => {
const mdContent = '# 123'
const result = processor.processSync(mdContent)
Expand All @@ -30,6 +43,7 @@ describe('Markdown compile cases', async () => {
'"<p>I am using <code>Island.js</code></p>"'
)
})

test('Compile code block', async () => {
const mdContent = '```js\nconsole.log(123)\n```'
const result = processor.processSync(mdContent)
Expand All @@ -38,4 +52,42 @@ describe('Markdown compile cases', async () => {
<span class=\\"line\\"></span></code></pre></div>"
`)
})

test('Compile TOC', async () => {
const mdContent = `\
# h1
## h2 \`code\`
### h3 [link](https://islandjs.dev)
#### h4 你好啊
##### h5
`

const result = remarkProcessor.processSync(mdContent)
expect(result.value.toString().replace(mdContent, ''))
.toMatchInlineSnapshot(`
"export const toc = [
{
\\"id\\": \\"h2-code\\",
\\"text\\": \\"h2 code\\",
\\"depth\\": 2
},
{
\\"id\\": \\"h3-link\\",
\\"text\\": \\"h3 link\\",
\\"depth\\": 3
},
{
\\"id\\": \\"h4-你好啊\\",
\\"text\\": \\"h4 你好啊\\",
\\"depth\\": 4
}
]
"
`)
})
})
4 changes: 4 additions & 0 deletions src/node/plugin-mdx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ import type { Plugin } from 'vite'
export async function createPluginMdx(): Promise<Plugin[]> {
return [await pluginMdxRollup()]
}

export * from './rehypePlugins/preWrapper'
export * from './rehypePlugins/shiki'
export * from './remarkPlugins/toc'
2 changes: 2 additions & 0 deletions src/node/plugin-mdx/pluginMdxRollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import remarkPluginFrontmatter from 'remark-frontmatter'
import shiki from 'shiki'
import { rehypePluginPreWrapper } from './rehypePlugins/preWrapper'
import { THEME, rehypePluginShiki } from './rehypePlugins/shiki'
import { remarkPluginToc } from './remarkPlugins/toc'

import type { Plugin } from 'vite'

Expand All @@ -16,6 +17,7 @@ export async function pluginMdxRollup(): Promise<Plugin> {
remarkPluginGFM,
remarkPluginFrontmatter,
[remarkPluginMDXFrontMatter, { name: 'frontmatter' }],
remarkPluginToc,
],
rehypePlugins: [
rehypePluginSlug,
Expand Down
65 changes: 65 additions & 0 deletions src/node/plugin-mdx/remarkPlugins/toc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Slugger from 'github-slugger'
import { visit } from 'unist-util-visit'
import { Root } from 'mdast'
import { parse } from 'acorn'
import type { Plugin } from 'unified'
import type { MdxjsEsm, Program } from 'mdast-util-mdxjs-esm'

const slugger = new Slugger()

interface TocItem {
id: string
text: string
depth: number
}
interface ChildNode {
type: 'link' | 'text' | 'inlineCode'
value: string
children?: ChildNode[]
}
export const remarkPluginToc: Plugin<[], Root> = () => {
return (tree) => {
// 初始化 toc 数组
const toc: TocItem[] = []
// 遍历 tree
visit(tree, 'heading', (node) => {
// debugger
if (!node.depth || !node.children) return
// 收集 h2~h4 的标题
if (node.depth > 1 && node.depth < 5) {
const text = (node.children as ChildNode[])
.map((n) => {
// case: link内容解析
switch (n.type) {
case 'link':
return n.children?.map((c) => c.value).join('') || ''
default:
return n.value
}
})
.join('')
// id 通过 slugger 生成规范化的标题id
const id = slugger.slug(text)
const item: TocItem = {
id,
text,
depth: node.depth,
}
toc.push(item)
}
})
// 插入 toc
const insertCode = `export const toc = ${JSON.stringify(toc, null, 2)}`
tree.children.unshift({
type: 'mdxjsEsm',
value: insertCode,
// 附加ast信息
data: {
estree: parse(insertCode, {
ecmaVersion: 2020,
sourceType: 'module',
}) as Program,
},
} as MdxjsEsm)
}
}

0 comments on commit 7d6b3c2

Please sign in to comment.