Gridsome開発における小ワザ4選

Gridsomeでイチからブログを作るシリーズはブログを作ることにフォーカスしていましたが、今回は普段僕がGridsomeでサイトを作るにあたって使ってる小ワザの紹介です。中には公式に案内があるものもありますが、ドキュメントは得てして隅まで読まれないことも多いので紹介しておきます。

概要

今回紹介するのは

  • 開発環境とプレビュー環境を分け、開発時の表示を高速化する
  • GraphQLの部分にシンタックスハイライトを効かせる(VSCode)
  • 記事にシンタックスハイライトをつける
  • 自動的にfrontmatter要素を生成する

の4つです。

開発環境とプレビュー環境を分け、開発時の表示を高速化する

Gridsomeでサイトを作るにあたって、ブログのようなページ数の多いものだと、ページ数に比例してビルド時間や開発サーバーの立ち上げに時間がかかるようになってきます。

サイト自体の開発には全ページ必要ないので、ページとして扱う対象を絞ることで立ち上げにかかる時間を短縮できます。

逆に新しくページを追加したとき、サイト内でリンクがまさしく張られているか確認したかったりで全ページ必要になることもあるでしょう。
もしくは新しく記事を書くとき、前後の他のページは必要ないが実際にサイトでどう見えるかプレビューしたい、ということもあるでしょう。

これら上手くハンドリングするためにプレビュー環境を作ってみます。アプローチとしては環境変数で記事ページ対象となるディレクトリをわけ、使いやすいようにscript定義してあげる方法です。

まず、gridsome.config.js

module.exports = {
  // 中略...
  plugins: [
    {
      use: `@gridsome/vue-remark`,
      options: {
        typeName: `Post`,
        baseDir:
          process.env.NODE_ENV === `production` || process.env.NODE_PREVIEW
            ? `./contents/posts`
            : `./contents/drafts`,
        },
      },
  ]
}

こんな感じでNODE_ENVproductionの時(=ビルドした本番の時)もしくは、NODE_PREVIEWという環境変数がtrueと判定されるときは/contents/postsが記事用のディレクトリの中身をページ対象にします。
そうでなければ(=gridsome developで起動するような開発環境の時)は/contents/draftsを記事用のディレクトリとする、という指定になります。
したがって、開発中に表示したい表示確認用の記事や、記事の下書きはcontents/draftsに入れておきます。

これを使いやすいようにpackage.json

{
  "scripts": {
    "build": "gridsome build",
    "develop": "gridsome develop",
    "preview": "NODE_PREVIEW=true gridsome develop",
    "explore": "gridsome explore",
  },
}

のように設定します。

そうすれば通常の開発は今までどおり

$ yarn develop

でいいですし、本番とどうようの状態でプレビューしたいときは

$ yarn preview

で起動します。

GraphQLの部分にシンタックスハイライトを効かせる(VSCode)

Gridsomeは通常のVue.jsのSFC(シングルファイルコンポーネント)の書き方にプラスして、GraphQLで書くことがあります。
これは通常のVue.jsにはなくGridsome独自の拡張なので、当然エディタには認識されません。

しかし、VSCodeとVSCode用のVue.js拡張のVeturを使っていれば対応できるので、その設定です。

公式にちゃんと言及がありますのでこちらを参照ください。
Dev tools - Gridsome

特に難しいことなく書いてあるとおりに従って、必要な拡張を入れ、設定ファイルに書き加えれば完了です。

記事にシンタックスハイライトをつける

@gridsome/vue-remarkを使ってMarkdownで記事を書いている場合、@gridsome/remark-prismjsを使うことで簡単に記事内のコードブロックにシンタックスハイライトが効くようになります。

@gridsome/remark-prismjs - Gridsome

公式の説明だと@gridsome/vue-remarkではないのでほぼ同じですがあらためて説明します。

まず、インストール

$ yarn add @gridsome/remark-prismjs

次にmain.jsでCSSを読み込み

import 'prismjs/themes/prism.css'

export default function (Vue) {
  // ...
}

ちなみにPrismのパッケージに内蔵しているもの以外のPrismテーマも使えるのでその場合はassetsなどに置き、importします。

gridsome.config.jsにプラグインとして使う指定をします。

module.exports = {
  // 中略...
  plugins: [
    {
      use: `@gridsome/vue-remark`,
      options: {
        typeName: `Post`,
        baseDir:
          process.env.NODE_ENV === `production` || process.env.NODE_PREVIEW
            ? `./contents/posts`
            : `./contents/drafts`,
      },
      plugins: [
        `@gridsome/remark-prismjs`,
      ],
    }
  ]
}

基本的にはこれだけでOKです。このようにprismだけでなくremark用のプラグインは同様の設定でほとんどのものが使えます。

また、やや特殊な例ですが、Gridsomeのようなコードを載せる場合、GraphQL要素にもシンタックスハイライトで表示するようには、gridsome.server.js

const Prism = require(`prismjs`)

// highlight page-query and static-query in html
Prism.languages.html.graphql = {
  pattern: /(<(page|static)-query[\s\S]*?>)[\s\S]*?(?=<\/(page|static)-query>)/i,
  inside: Prism.languages.graphql,
  lookbehind: true,
  greedy: true,
}

module.exports = function(api) {
  //...
}

と指定しておくと、Gridsomeのコードなどを載せても良い感じに表示できます。

自動的にfrontmatter要素を生成する(例:description)

例えば、TOPの記事一覧表示では記事本文は載せず、記事の説明(description)を載せるサイトだとした場合、記事の内容の冒頭部分から自動的に冒頭を抜粋してみます。

まず、抜粋する部分を実装します。例えばこういう感じです。

const remark = require(`remark`)
const strip = require(`strip-markdown`)

function toText(md) {
  let result
  remark()
    .use(strip)
    .process(md, (err, file) => {
      if (err) throw err
      result = file.contents
    })
  return result
}

exports.generate = generate
const MAX_LENGTH = 140
function generate(content) {
  if (!content) {
    return ``
  }
  // 最初の文だけを抜き出し
  const matched = replaced.match(/(.*?)(\n\n#|\n\n---)/s)
  let excerpt
  if (matched) {
    excerpt = replaced.match(/(.*?)(\n\n#|\n\n---)/s)[1]
  } else {
    excerpt = replaced
  }

  // plain text化した後最大文字数まででカット
  const striped = toText(excerpt)
  if (striped.length > MAX_LENGTH) {
    return striped.substring(0, MAX_LENGTH) + `...`
  } else {
    return striped
  }
}

これをauto-description.jsとしておきましょう。
そうしたらgridsome.server.js

const autoDescription = require(`./auto-description.js`)
module.exports = function(api) {
  api.loadSource(({ addSchemaResolvers }) => {
    addSchemaResolvers({
      Post: {
        description: {
          type: `String`,
          resolve(node) {
            if (node.description) {
              return node.description
            } else {
              return autoDescription.generate(node.content)
            }
          },
        },
      }
    }
  }
}

みたいに使います。記事のほうでfrontmatterにdescriptionが指定されていればそれを使い、何も指定がなければ自動で記事本文から抜粋してnodeのdescriptionに詰められます。

今回はdescriptionを例に取りましたが応用していろいろなことができます。僕のこのブログの場合、他にも自動でOGPアイキャッチの指定やキーワードの指定も行なっています。

感想

やはりこうやっていろいろ工夫できるのは、ブログサービスではなく自前で開発してるがゆえの醍醐味ですね。

今後もいくつか思いついたら紹介していきたいと思っています。

Vue.jsとTypeScriptでGoogleChrome用拡張を開発するPomodoro Technique 再入門 - 今まで僕のやっていたものがポモドーロテクニックは全然理解せずにやっていた違うものでした