Oteto Blogのロゴ

React Syntax Highlighterで差分行をハイライト(diff highlight)する

やりたいこと

Reactでコードをシンタックスハイライトする際に、ライブラリの候補として挙がってくるreact-syntax-highlighter

行番号表示など十分な機能が揃ってはいるが、唯一差分のハイライト(diff highlight)機能は備わっていない。

function foo() {
  console.log("");
  console.log("foo");
}

しかしブログを書く上で上記のように差分を強調したい場面は数多くあるので、素のPrism.jsの差分ハイライト機能に似たものを下記の要件で実装してみる。

  • コードブロックの言語名をdiff-xxと指定することで有効になる
  • +-を文頭に付けることで差分行と判定

実装方法

import SyntaxHighlighter from "react-syntax-highlighter/dist/esm/prism";

export default function CodeBlock({
  lang,
  code,
}: {
  lang: string;
  code: string;
}) {
  return <SyntaxHighlighter language={lang}>{code}</SyntaxHighlighter>;
}

まずSyntaxHighlighterをラップしただけのコンポーネントを作成。

import styles from "../_style/module/code-block.module.scss";
import SyntaxHighlighter from "react-syntax-highlighter/dist/esm/prism";

export default function CodeBlock({
  lang,
  code,
}: {
  lang: string;
  code: string;
}) {
  const addRowNums: number[] = [];
  const deleteRowNums: number[] = [];

  const diffMatch = lang.match(/^diff-(.+)$/);
  if (diffMatch) {
    const codeRows = String(children).split(/\n/);
    const excludedPrefixCodeRows = codeRows.map((codeRow, i) => {
      if (/^\+.*$/.test(codeRow)) {
        addRowNums.push(i);
        return codeRow.slice(1);
      } else if (/^-.*$/.test(codeRow)) {
        deleteRowNums.push(i);
        return codeRow.slice(1);
      }
      return codeRow;
    });

    code = excludedPrefixCodeRows.join("\n");
  } else {
    code = String(children).replace(/\n$/, "");
  }

  return <SyntaxHighlighter language={lang}>{code}</SyntaxHighlighter>;
  return (
    <SyntaxHighlighter
      language={diffMatch ? diffMatch[1] : lang}
      wrapLines={true}
      showLineNumbers={true}
      lineProps={(lineNumber) => {
        if (addRowNums.includes(lineNumber - 1)) {
          return { className: styles.addRow };
        } else if (deleteRowNums.includes(lineNumber - 1)) {
          return { className: styles.deleteRow };
        }
        return { className: undefined };
      }}
    >
      {code}
    </SyntaxHighlighter>
  );
}
  1. 受け取ったコードを行ごとに分割し、文頭が+-であればその行数を記録
    • 自分の場合+-はCSSの擬似要素として表示したかったので、文字列からは除外している
  2. linePropspropsにて、差分行に専用のCSSクラスを付与
import CodeBlock from "./codeBlock";

export default function sampleComponent() {
  const code = `
  function foo() {
-  console.log("");
+  console.log("foo");
}
  `;
  return <CodeBlock lang="diff-ts" code={code} />;
}

あとは上記のように各propsを渡しつつコンポーネントを呼び出すだけ。

function foo() {
  console.log("");
  console.log("foo");
}

これで上記のように、差分行がハイライトされて表示されるようになった。