MDX への行番号とコードの強調表示の追加


この非常に短いクイック ヒントでは、コード レンダラー MDX を使用して、行番号とコードの強調表示をサポートする prism-react-renderer と Gatsby でコード ブロックを設定する方法を学習します.プレビューは CodeSandbox にあります.

まず、MDX ブログが設定されていることを確認します.すでにお持ちの場合は、パッケージにスキップできます.そうでない場合は、最初に Gatsby のドキュメントの Getting Started with MDX をお読みください.

このクイック ヒントに必要なパッケージをインストールします.

npm install mdx-utils prism-react-renderer

src/components/code.js で Code React コンポーネントを作成し、ファイルを空のままにします.

また、CSS ファイルを作成します.

html,
body {
  margin: 0;
  padding: 0;
}

html {
  font-family: sans-serif;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}

.prism-code {
  font-size: 1rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
  -webkit-overflow-scrolling: touch;
  background-color: transparent;
  overflow: initial;
}

.token {
  display: inline-block;
}

p > code,
li > code {
  background: rgb(1, 22, 39);
  color: rgb(214, 222, 235);
  padding: 0.4em 0.3rem;
}

.gatsby-highlight {
  font-size: 1rem;
  position: relative;
  -webkit-overflow-scrolling: touch;
  overflow: auto;
}

gatsby-highlight > code[class*="language-"],
.gatsby-highlight > pre[class*="language-"] {
  word-spacing: normal;
  word-break: normal;
  overflow-wrap: normal;
  line-height: 1.5;
  tab-size: 4;
  hyphens: none;
}

.line-number-style {
  display: inline-block;
  padding-left: 1em;
  padding-right: 1em;
  width: 1.2em;
  user-select: none;
  opacity: 0.3;
  text-align: center;
  position: relative;
}

.highlight-line {
  background-color: rgb(2, 55, 81);
  border-left: 4px solid rgb(2, 155, 206);
}

.highlight-line .line-number-style {
  opacity: 0.5;
  width: calc(1.2em - 4px);
  left: -2px;
}

MDXProvider を含むファイルに切り替えます.たとえば、 defaultLayoutsgatsby-plugin-mdx 内の gatsby-config.js オプション:

module.exports = {
  plugins: [
    {
      resolve: "gatsby-plugin-mdx",
      options: {
        defaultLayouts: {
          default: require.resolve("./src/components/layout.js"),
        },
      },
    },
  ],
}

MDXProvider と次のコードの残りの部分 (基本的には、ラッピング MDXProvider に配置されたコンポーネントです) をレイアウト ファイルに追加する必要があります.

import * as React from "react"
import { MDXProvider } from "@mdx-js/react"
import { preToCodeBlock } from "mdx-utils"
import Code from "./code"
import "./layout.css"

const components = {
  pre: (preProps) => {
    const props = preToCodeBlock(preProps)
    if (props) {
      return <Code {...props} />
    } else {
      return <pre {...preProps} />
    }
  },
}

const Layout = ({ children }) => (
  <MDXProvider components={components}>
    <div style={{ margin: "0 auto", maxWidth: 960, padding: "2rem" }}>
      {children}
    </div>
  </MDXProvider>
)

export default Layout


重要なのは、プロバイダーに components を渡し、以前に作成した Code React コンポーネントが使用されることです.

上記のコンポーネントに次を追加します.

import * as React from "react"
import Highlight, { defaultProps } from "prism-react-renderer"
import theme from "prism-react-renderer/themes/nightOwl"

const RE = /{([\d,-]+)}/

const calculateLinesToHighlight = (meta) => {
  if (!RE.test(meta)) {
    return () => false
  }
  const lineNumbers = RE.exec(meta)[1]
    .split(`,`)
    .map((v) => v.split(`-`).map((x) => parseInt(x, 10)))
  return (index) => {
    const lineNumber = index + 1
    const inRange = lineNumbers.some(([start, end]) =>
      end ? lineNumber >= start && lineNumber <= end : lineNumber === start
    )
    return inRange
  }
}

const Code = ({ codeString, language, metastring, ...props }) => {
  const shouldHighlightLine = calculateLinesToHighlight(metastring)

  return (
    <Highlight
      {...defaultProps}
      code={codeString}
      language={language}
      theme={theme}
      {...props}
    >
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <div className="gatsby-highlight" data-language={language}>
          <pre className={className} style={style}>
            {tokens.map((line, i) => {
              const lineProps = getLineProps({ line, key: i })

              if (shouldHighlightLine(i)) {
                lineProps.className = `${lineProps.className} highlight-line`
              }

              return (
                <div {...lineProps}>
                  <span className="line-number-style">{i + 1}</span>
                  {line.map((token, key) => (
                    <span {...getTokenProps({ token, key })} />
                  ))}
                </div>
              )
            })}
          </pre>
        </div>
      )}
    </Highlight>
  )
}

export default Code

calculateLinesToHighlight ヘルパー関数は、行の強調表示の表記である入力として metastring を取得します.リンクされた CodeSandbox の例でわかるように、{1,9-12} と記述して、1 行目と 9 行目から 12 行目を強調表示できます.