0.效果
- 设置各种主题
- 使用聚焦插件
- 标注某个单词
除了twoslash
用不了,基本功能还是可以。
因为docusaurus
打包之后本质上是一个静态网站,没有后端代码,所以没办法读取文件,也就无法使用twoslash
。
1. 思路
主要是使用shiki
的codeToHtml
方法,将代码字符串转成渲染的html
。
所以封装了一个Code
组件,接收字符串作为子组件,然后用shiki
转化。这里利用了docusaurus
的mdx
解析机制,因为docusaurus
配置的markerdown loader
会把所有code-fence
语法的片段转成内部的code
组件,所以这里我利用了它的机制,获取了原始的字符串和语言类型。
// 封装一个Code
<Code>
// children接收一个字符串
// 内部可以获取到语言类型和代码内容
// 通过codeToHtml直接转化为高亮代码
```json
{
"node": "20.11.0",
"yarn": "1.22.21"
}
```
</Code>
2. 实现
2.1. 安装依赖
yarn add -D shiki
2.2. 封装Code
组件
export type CodeProps = {
/**
* 也可以在chidren中传递
*/
code?: string;
/**
* 显示行号
*/
showLineNumber?: boolean;
} & Partial<CodeToHastOptions<BundledLanguage, BundledTheme>>;
// Code组件
const Code = ({
code,
lang: propsLang = "tsx",
showLineNumber = true,
children,
...options
}: PropsWithChildren<CodeProps>) => {
// 封装了一个异步加载的组件
// 保证codeToHtml执行完成
const CodeToHtml = lazy(async () => {
// 这一长串是取的docusaurus解析markdown后的组件内容
// @ts-ignore
const rawCode = children?.props?.children?.props?.children || code;
// @ts-ignore
const lang = children?.props?.children?.props?.className?.replace("language-", "") || propsLang;
// 设置shiki的转换器
const transformers: ShikiTransformer[] = [
transformerNotationFocus(),
transformerNotationWordHighlight(),
];
if (showLineNumber) {
transformers.push(transformerLineNumber());
}
// 调用shiki的codeToHtml转喊
const html = await codeToHtml(rawCode, {
lang,
themes: {
dark: "catppuccin-mocha",
light: "catppuccin-latte",
},
transformers,
...options,
});
// 渲染内容
return {
default: () => <div dangerouslySetInnerHTML={{ __html: html }}></div>,
};
});
return (
<Suspense fallback={<>加载中....</>}>
<CodeToHtml />
</Suspense>
);
};
2.2.1. docusaurus
对markdown
的处理
24行
到26行
,我利用了docusaurus
对markdown
的处理获取代码字符串。
无论你在markdown
中的哪里写了<```>
这个符号,都会被转义成内部的一个Pre
组件
packages\docusaurus-theme-classic\src\theme\MDXComponents\Pre.tsx
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {type ReactNode} from 'react';
import type {Props} from '@theme/MDXComponents/Pre';
export default function MDXPre(props: Props): ReactNode | undefined {
// With MDX 2, this element is only used for fenced code blocks
// It always receives a MDXComponents/Code as children
return <>{props.children}</>;
}
这里面的children
已经被解析过了
所以可以直接取这里面的代码字符串和语言值
2.2.2. 显示行号
上面的操作已经算是完成接入了,这一小节介绍下自定义转换器,实现显示行号。
import { ShikiTransformer } from "shiki";
import styles from "./index.module.scss";
interface TransformerLineNumberOptions {}
const transformerLineNumber: (options?: TransformerLineNumberOptions) => ShikiTransformer = () => {
return {
name: "transformerLineNumber",
line(hast, line) {
// 给每一行添加样式
this.addClassToHast(hast, [styles["line-number"]]);
return hast;
},
code(hast) {
// 整个代码块加样式
this.addClassToHast(hast, [styles["code"]]);
return hast;
},
};
};
export default transformerLineNumber;
实际上没什么代码逻辑,就是css
样式
.code {
// display: table的目的是行号宽度保持一致
display: table;
margin-bottom: 16px;
// 因为shiki需要满足其他插件的需要
// 最后一行会多一个空白行
// 这里防止多一行
& > span.line:last-child:empty {
display: none;
}
}
.line-number {
display: table-row;
// 自行搜索css计数器
counter-increment: a 1;
&::before {
display: table-cell;
padding: 0 16px;
// 计数器目前只能用在content上
content: counter(a);
}
&:last-of-type::before {
content: none;
}
}
3. 其他
没有测试完所有功能,但是够用了,自带的高亮其实也可以,但是没有shiki
自由。
原文链接:https://juejin.cn/post/7389049604632346651 作者:头上有煎饺