Next.jsのMDXでシンタックスハイライト付き差分表示を実装する(@shikijs/transformers)
背景:前回の記事で予告していた差分表示を実装
以前の記事「Shiki と rehype-pretty-code でMDXのシンタックスハイライトを移行した記録」で、差分表示については以下のように触れていた。
注意:
diff-tsxのような複合指定は Shiki では非対応。差分を表現したい場合は、@shikijs/transformersのtransformerNotationDiffを使用するとのこと。この辺はまた別記事としてまとめたい。
今回、実際に @shikijs/transformers の transformerNotationDiff を導入し、MDXでシンタックスハイライト付きの差分表示を実現した。
この記事では、その実装手順を記録する。
環境
- Next.js 16.0.7 (App Router)
- TypeScript
@next/mdx16.0.7 (MDXのレンダリング)rehype-pretty-code0.14.1 (コードブロックの整形)shiki3.19.0 (シンタックスハイライト)@shikijs/transformers3.20.0 (差分表示機能)
実装前の状態
このプロジェクトでは既に rehype-pretty-code と shiki を使用してシンタックスハイライトを実装済みだった(前回の記事で導入)。
しかし、差分表示のための transformerNotationDiff はまだ設定していなかったため、コードの変更箇所を視覚的に表現できない状態だった。
実装手順
1. @shikijs/transformersパッケージをインストール
まず、@shikijs/transformersパッケージをインストールした。
yarn add @shikijs/transformers2. next.config.mjsに transformers オプションを追加
next.config.mjsを更新し、rehype-pretty-codeの設定にtransformerNotationDiffを追加した。
import createMDX from '@next/mdx'
import remarkGfm from 'remark-gfm'
import rehypePrettyCode from 'rehype-pretty-code'
import { transformerNotationDiff } from '@shikijs/transformers'
/** @type {import('next').NextConfig} */
const nextConfig = {
trailingSlash: true,
turbopack: {
root: process.cwd(),
},
experimental: {
ppr: false,
},
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}
const withMDX = createMDX({
remarkPlugins: [remarkGfm],
rehypePlugins: [],
rehypePlugins: [
[
rehypePrettyCode,
{
theme: 'github-dark',
transformers: [transformerNotationDiff()],
},
],
],
})
export default withMDX(nextConfig)今回のプロジェクトでは@next/mdxを使用していたので、next.config.mjsのrehypePluginsに設定を追加した。
既にremark-gfmを使用していたため、remarkPluginsはそのままで、rehypePluginsにrehype-pretty-codeとtransformerNotationDiffを設定した。
3. 差分表示のスタイリング
次に、app/globals.cssに差分表示用のスタイルを追加した。
/* 差分表示用のスタイル */
[data-line].diff {
--diff-bg-color: oklch(0.75 0.11 var(--diff-hue) / 0.15);
position: relative;
padding-left: 1rem;
border-image: linear-gradient(var(--diff-bg-color), var(--diff-bg-color)) fill 0 / 0 / 0 50px;
&::before {
content: var(--diff-content);
position: absolute;
left: 0;
color: oklch(0.58 0.09 var(--diff-hue));
}
}
/* 追加行 */
[data-line].diff.add {
--diff-hue: 157.92;
--diff-content: "+";
}
/* 削除行 */
[data-line].diff.remove {
--diff-hue: 18.82;
--diff-content: "-";
}使用方法
MDXファイル内で、コードブロックに// [!code ++]と// [!code --]を記述することで差分を表現できる。
基本的な使い方
```js
function example() {
console.log("古いコード"); // [\!code --]
console.log("新しいコード"); // [\!code ++]
}
```実際の表示例
function example() {
console.log("古いコード");
console.log("新しいコード");
const oldValue = 42;
const newValue = 100;
}活用例
TypeScriptの型定義の変更
interface User {
id: number;
name: string;
firstName: string;
lastName: string;
email: string;
age: number;
birthDate: Date;
}SQLクエリの修正
-- 旧クエリ
select id, name from users;
-- 新クエリ
select id, first_name, last_name from users; Reactコンポーネントのリファクタリング
export function Button({ label }: { label: string }) {
return <button>{label}</button>;
return (
<button className="btn-primary">
{label}
</button>
);
}transformerNotationDiffの仕組み
transformerNotationDiff()は、コード内の[!code ++]と[!code --]というマーカーを検出し、以下のようにHTMLを変換する。
- 追加行:
<span class="line diff add"> - 削除行:
<span class="line diff remove"> - 外側の
<pre>タグにhas-diffクラスを追加
これにより、CSSセレクタで簡単にスタイリングできる。
この実装のメリット
可読性の向上
プレーンテキストの差分と異なり、シンタックスハイライトが維持されるため、コードの意味を理解しやすい。
技術記事に最適
リファクタリングやバグ修正の説明が視覚的に分かりやすくなり、読者が変更箇所を一目で把握できる。
簡単な記法
コメントとして記述するため、コピー&ペーストしても元のコードとして有効なまま使える。
複数言語対応
JavaScript、TypeScript、SQL、CSS、Pythonなど、shikiが対応しているあらゆる言語で使用可能。
まとめ
- MDXでシンタックスハイライト付きの差分表示を実現するには
@shikijs/transformersが必要 transformerNotationDiff()をrehype-pretty-codeのtransformersオプションに追加する- MDX内で
// [!code ++]と// [!code --]を使って差分をマーク - CSSで追加行・削除行のスタイルをカスタマイズできる