metascraper + unified でブログカードを作った

こんにちは。ブログ作って9ヶ月放置した人です。

正月に書こうと思っていたんですが、

  • ブログのデザインがひどくて書く気になれなかった
  • ブログカードを実装したかった
    • 2021年買ってよかったものリストを書こうと思ったらブログカード作りたくなった

みたいな経緯があって正月はこのブログのブログカードを作ってました。

今日はそのブログです。ちなみにブログカード以外にもちょこちょこ読みやすくなるように CSS をいじってます。

なんで正月に書かずに今書いているかというと最後の詰めをサボったからです。すみません。

ブログカード完成品

今回作るにあたって、

  • サムネイルとタイトル、説明を表示する
  • ビルド時にサイトから OGP でデータを読み込んでビルドする

ことを要件としました。できあがったものがこちらです。

Next.js + Netlify CMS でブログを作る

ブログ作りました!🎉🎉🎉 自分で作ったブログは機能こそ少ないですが、とても愛着が湧いていいです。せっかく作ったので、備忘録がてら今回作ったブログの作り方を書いておきます。 目次 ``` - 構成 - Next.js - Netlify CMS - やった

みための改善余地はまだあるなと思いつつ、ある程度作りたいものが作れたので良しとしました。 使いかたは、リンクにしたい URL を [] で囲って、URL の最後に :embedを追加すると使えます。上のリンクは

[https://hayaokimura.com/blogs/create-blog-with-next-netlify-cms:embed]

みたいな感じです。

やったこと

最終的にやったこととしては、

  • ReactMarkdown を使うのをやめる
  • unified をそのままつかって markdown to HTML 変換をビルド時に行う
  • 特定の文字列を embed タグとして扱う unified plugin を書く
  • レンダリング時は HTML to react をする。その時 embed タグを解釈する

ReactMarkdown を使うのをやめる

もともとこのブログは ReactMarkdown をつかって markdown を react 化していました。(作ったばかりのブログカード使いたがるのは許してください。)

GitHub - remarkjs/react-markdown: Markdown component for React

Markdown component for React. Contribute to remarkjs/react-markdown development by creating an account on GitHub.

ですが、この方法だとそもそも markdown to react をページを読み込んだときに行っています。

自分が今回やりたかったのはビルド時に markdown to react するとともに OGP でデータをサイトから読み込むことだったので、 ReactMarkdown をこのまま使うことはできませんでした。

unified をつかって md to HTML をビルド時にする。

unified はいろんな文字列を構文木に変換して扱えるようにするライブラリです。 ReactMarkdown の内部でも結局は unified のプラグインで md to react が成されています。

GitHub - unifiedjs/unified: ☔️ interface for parsing, inspecting, transforming, and serializing content through syntax trees

☔️ interface for parsing, inspecting, transforming, and serializing content through syntax trees - unifiedjs/unified

これを使って、まず markdown to HTML をビルド時に行うようにしました。HTML の文字列として props に保存される形になります。

特定の文字列を embed タグとして扱う unified plugin を書く

unified で markdown を HTML にするとき、特定の文字列の場合(今回は [] で囲まれた :embed を含んだ URL 文字列)に OGP から情報を取ってきて、出力する HTML の文字列に OGP でとってきた情報を含めたいです。

unified は Transformer という構文木を変換するプラグインを挟むことで、変換時にいろいろな処理をすることができます。unified 公式のこの図がわかりやすいです。

unified がやること

今回の場合、Input が markdown、Output が HTML です。Parserで、markdown を構文木にした後、Transformer を構文木に作用させる事によって、色々できます。今回の場合は、特定の文字列の場合に embed という HTML タグにするようにしました。

async function transformer(tree, file) {
  const nodes = selectPossibleOembedLinkNodes(tree);

  await nodes.map((node) => processNode(node));

  async function processNode(node) {
    if (!node.children || node.children.length == 0) {
      return;
    }
    const firstText = node.children[0];
    if (firstText.type !== "text") {
      return;
    }
    const value = firstText.value;

    if (value && isEmbed(value)) {
      node.type = "rehype-embed";
      const { body: HTML, url } = await got(extractUrl(value));
      const metadata = await metascraper({ HTML, url });
      node.data = metadata;
      node.children = null;
    }
  }
}

node.typerehype-embed を指定し、metascraper ライブラリで取得した OGP のデータを node.data にわたすことで、props としてフロントで取得できるようにしています。

レンダリング時に HTML を React に変換する。その時ブログカードコンポーネントを作る

ビルド時にうまく行っていれば、[https://hoge.hoge:embed]という文字列は rehype-embed タグと OGP のデータに変換されてフロントに渡ってきます。 フロントでもunifiedを用いて HTML から React に変換します。このとき、特定のタグの変換の仕方を components を与えることで指定できます。今回の場合下記のように RehypeEmbed コンポーネントを指定することで、ブログカードを作ることができます。

const processor = unified().use(rehype2react, {createElement: React.createElement, components: {"rehype-embed": RehypeEmbed}});
export const RehypeEmbed: React.FC<any> = (props) => {
  return (
    <ALink href={props.url} target="_blank" rel="noopener noreferrer">
      <ImageWrapper>
        <Image src={props.image} />
      </ImageWrapper>
      <DescriptionWrapper>
        <Title>{props.title}</Title>
        <Description>{props.description}</Description>
      </DescriptionWrapper>
    </ALink>
  );
}

こうすると metadata として HTML に渡した情報が props として利用でき、React で利用できます。

まとめ

意外と長くなってしまった。 unified を触るのが初めてだったので結構楽しかったですが、実際は結構手こずってました笑

React でブログ作ってる人は参考にしてみてください。では!

シェアする