カスタムコンポーネント機能の実装

PR22で追加しました。

今回の新機能

  1. コンポーネント単位のカスタムが可能になりました。

実装の話

実は前回の使い回しなので、ほとんど書くことが無いんですよね〜。

CLIからカスタムコンポーネントのファイルを指定して、vmで実行。 その返り値を<MDXTag>に渡すだけでおしまい。

型の紹介でもしておきましょう。@types/reactで定義されている JSX.IntrinsicElementsを使えばカスタム可能なコンポーネントの一覧を得ることができます。 それを今回ほしい型に整形して定義しておきます。

export type CustomComponents = { [P in keyof JSX.IntrinsicElements]?: (props: JSX.IntrinsicElements[P]) => React.ReactNode };

これを利用することにより、圧倒的に開発が楽になります!

さて、カスタムコンポーネントが利用できるようになったことで、とても重要な機能が利用できるようになりました。

それは、Syntax Highlight機能です。しかも、サーバーサイドレンダリングで!

この記事の執筆当時のカスタムコンポーネントを紹介しましょう。

カスタムコンポーネントでSyntax Highlightを実装する

// tslint:disable-next-line:no-reference
/// <reference path="../node_modules/custom-site/typings/@mdx-js/index.d.ts"/>

import { CustomComponents } from "@mdx-js/tag";
import * as Prism from "prismjs";
import * as React from "react";

/**
 * ハイライトしたい言語のjsファイルを読み込んでおく
 */
require("prismjs/components/prism-typescript.min.js")

const SUPPORT_LANGUAGES = Object.keys(Prism.languages);

const getLanguageDefinition = (lang: string): Prism.LanguageDefinition | null => {
  if (SUPPORT_LANGUAGES.includes(lang)) {
    return Prism.languages[lang];
  }
  return null;
}

export const customComponents = (): CustomComponents => {
  return {
    pre: props => <>{ props.children }</>,
    code: props => {
      const lang = props.className ? props.className.slice("language-".length) : "";
      const code = typeof props.children === "string" ? props.children : "";
      const grammar = getLanguageDefinition(lang);
      let highlightHtml: string | undefined;
      if (grammar) {
        highlightHtml = Prism.highlight(code, grammar);
      }
      if (!lang || !grammar) {
        return <pre><code {...props} /></pre>;
      }
      const newProps: JSX.IntrinsicElements["code"] = {
        ...props,
        children: undefined,
        dangerouslySetInnerHTML: {
          __html: highlightHtml
        }
      };
      return (
      <pre className={props.className}>
        <code {...newProps} />
      </pre>
      );
    },
  };
};

シンタックスハイライト ライブラリ

Syntax Highlightのライブラリとして、PrismJSを利用するようにしました。 highlight.jsという選択肢もあったのですが、導入から使いやすさ、拡張性からPrismJSを使用することにしました。

他にも、PrismJSは次のようなことができます。(highlightjsにもあるかもしれませんが)

カスタムコンポーネントでライブラリの要求する構造を作る

PrismJSは次のような構造を求めます。(主にCSS)

<pre class="language-*">
  <code class="language-*">/* コード本体 */</code>
</pre>

Markdownで与える情報は限定的なものなので、<pre>タグと<code>タグの両方に同時に同じ classNameを与えるには何かしらの手を加える必要があります。

カスタムコンポーネントを利用すると、これが一瞬で実現できます。 どうやるかというと、<pre>の生成はスキップして、<code>の中で同時に<pre>を作ってしまえばよい、という発想です。 そしたら、<code>のみに与えられるはずだったclassNameを<pre>の方にも与えてしまえば、あら不思議!できあがり!

おわり!最高ですね!

ああああああーーー僕はこれがやりたかったんだああああああああ!!!!!!

サーバーレンダリングの恩恵

JavaScriptをOFFにしてこのページを見てください!シンタクスハイライト付くんですよ! これが静的サイトだ!という感じですよね!クライアントのJSの力でハイライトをつけるなんてリッチなことリソースの無駄したくないし、 ブラウザごとの差異なんで気にしたくないので、最高ですねぇ。SPAでもないのでこれで十分!!!!

次やること

カスタムコンポーネント機能、雑に作ったけど、シンタックスハイライトを手に入れることが出来る強力な新機能でした〜!

次はGoogle Analyticsを先に入れましょう。どれくらい見られてるのかわからないのでね...! その次はー、ファイルから作成日と、更新日の取得あたりかな〜 その次は〜、実験的に挑戦してみたい機能があるのでそれかな。

その前に、次の記事はパッケージ名変更の記事です(同じに日に書いたので言える)

関連記事