docusaurus中引入shiki实现代码高亮

0.效果

  • 设置各种主题
    docusaurus中引入shiki实现代码高亮
  • 使用聚焦插件
    docusaurus中引入shiki实现代码高亮
  • 标注某个单词
    docusaurus中引入shiki实现代码高亮
    除了twoslash用不了,基本功能还是可以。

因为docusaurus打包之后本质上是一个静态网站,没有后端代码,所以没办法读取文件,也就无法使用twoslash

1. 思路

主要是使用shikicodeToHtml方法,将代码字符串转成渲染的html

所以封装了一个Code组件,接收字符串作为子组件,然后用shiki转化。这里利用了docusaurusmdx解析机制,因为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. docusaurusmarkdown的处理

24行26行,我利用了docusaurusmarkdown的处理获取代码字符串。

无论你在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已经被解析过了

docusaurus中引入shiki实现代码高亮
所以可以直接取这里面的代码字符串和语言值

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 作者:头上有煎饺

(0)
上一篇 2024年6月2日 上午10:00
下一篇 2024年7月15日 下午4:00

相关推荐

发表回复

登录后才能评论