GridsomeでTailwind CSSを使ってみる

10 min read読了の目安(約9600字

下記で使ったGridsomeのプロジェクト(Gridsomeのblog starter)にTailwind CSSを導入してみます。

https://zenn.dev/mseto/articles/strapi-starter-blog

ここではGridsomeのプロジェクトに簡単にTailwindを導入できるプラグインgridsome-plugin-tailwindcssを使用します。

VSCodeのリモートコンテナを複数使用している場合、頻繁にリモートコンテナから切断されたり、Gridsomeのビルド等を実行したとき、下記エラーになることがあります。

Command failed with exit code 137. 

この場合、Dockerのリソースのメモリが足りていない可能性があります。
Dockerの"Preferences"の"Resources"にある"Memory"をデフォルトの2GBから増やすことで改善することがあります。
メモリを増やす

gridsome-plugin-tailwindcssのインストール

下記コマンドを実行してgridsome-plugin-tailwindcssをインストールします。

$ cd frontend
$ yarn add --dev gridsome-plugin-tailwindcss tailwindcss@latest

postcss-importやpostcss-preset-envが必要な場合は下記コマンドも実行します。

$ yarn add --dev postcss-import postcss-preset-env

tailwind.config.jsの作成

下記コマンドを実行してtailwind.config.jsを作成します。

$ npx ./node_modules/.bin/tailwind init

tailwind.config.jsの編集

作成したfrontend/tailwind.config.jsを下記のように編集してPurgeの設定を追加します。
Purgeの設定をしないとビルド後に作成されるCSSのファイルサイズが非常に大きくなってしまいます。

frontend/tailwind.config.js
module.exports = {
  purge: {
    content: [
      './src/**/*.html',
      './src/**/*.js',
      './src/**/*.vue',
    ],
    options: {
      keyframes: true,
    },
  },
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

プラグインの読み込みを追加

frontend/gridsome.config.jsにgridsome-plugin-tailwindcssの読み込みを追加します。

frontend/gridsome.config.js
module.exports = {
  siteName: "Strapi Gridsome Blog",
  siteDescription: "A blog site made using Gridsome and Strapi",
  plugins: [
    {
      use: "@gridsome/source-graphql",
      options: {
        url:
          (process.env.GRIDSOME_STRAPI_URL || "http://localhost:1337") +
          "/graphql",
        fieldName: "strapi",
        typeName: "strapiTypes",
      },
    },
    // 下記を追加
    {
      use: "gridsome-plugin-tailwindcss",
      options: {
        tailwindConfig: './tailwind.config.js',
        
        // postcss-importを有効にする場合
        shouldImport: true
        
        // postcss-preset-envを有効にする場合
        shouldTimeTravel: true
        presetEnvConfig: {}, //postcss-preset-envの設定
      }
    },
  ],
};

postcss-importをインストールしている場合、optionsにshouldImport: trueを追加することで有効になります。
また、postcss-preset-envをインストールしている場合はoptionsにshouldTimeTravel: trueを追加することで有効にでき、presetEnvConfigにpostcss-preset-envの設定のオブジェクトを記述できます。

ビルドして確認

下記コマンドでGridsomeをビルドして動作確認します。

$ yarn develop

Tailwind CSSのバージョン2はPostCSSのバージョン8に依存していますが、下記のようなエラーが出る場合、Tailwindをいったんアンインストールして、TailwindのPostCSSバージョン7の互換ビルドを再インストールする必要があります。

error  in ./node_modules/tailwindcss/tailwind.css

Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: true is not a PostCSS plugin
    at Processor.normalize (/src/frontend/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:168:15)
    at new Processor (/src/frontend/node_modules/postcss-loader/node_modules/postcss/lib/processor.js:52:25)
    at postcss (/src/frontend/node_modules/postcss-loader/node_modules/postcss/lib/postcss.js:55:10)
    at /src/frontend/node_modules/postcss-loader/src/index.js:140:12

下記コマンドを実行してTailwindのアンインストールおよび、PostCSSバージョン7の互換ビルドをインストールします。

$ yarn remove tailwindcss postcss autoprefixer
$ yarn add --dev tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

ビルドが成功し、表示されたページをChromeのデベロッパーツール等で見るとTailwindが読み込まれているのが確認できます。
Chromeのデベロッパーツール

Tailblocksのコンポーネントをベースにトップページを編集

下記のTailwindのコンポーネント集を使用してGridsomeのblog starterのトップページを編集してみます。

https://tailblocks.cc/

ヘッダーは下記をベースにしました。
ヘッダー

"VIEW CODE"をクリックすると下記のように選んだコンポーネントのソースを見ることができます。
コンポーネントのソース

記事一覧は下記をベースにしました。
記事一覧

frontend/src/index.htmlのUIkit CSSの読み込み部分を削除します。

frontend/src/index.html
<!DOCTYPE html>
<html ${htmlAttrs}>
  <head>
    ${head}
  </head>
  <body ${bodyAttrs}>
    ${app}
    ${scripts}
  </body>
</html>

frontend/src/pages/Index.vueのtemplateの変更およびarticlesのクエリにdescriptionを追加します。

frontend/src/pages/Index.vue
<template>
  <Layout>
      <Articles :articles="$page.strapi.articles" />
  </Layout>
</template>

<page-query>
query {
  strapi {
    global {
      siteName
      favicon {
        url
      }
      defaultSeo {
        metaTitle
        metaDescription
        shareImage {
          url
        }
      }
    }
    homepage {
      hero {
        title
      }
      seo {
        metaTitle
        metaDescription
        shareImage {
          url
        }
      }
    }
    articles(where: { status: "published" }) {
      slug
      title
      description
      category {
        name
      }
      image {
        url
      }
      author {
        name
        picture {
          url
        }
      }
    }
  }
}
</page-query>

<script>
import Articles from "~/components/Articles";
import { getMetaTags } from "~/utils/seo";
import { getStrapiMedia } from "~/utils/medias";

export default {
  components: {
    Articles,
  },
  metaInfo() {
    const { seo } = this.$page.strapi.homepage;
    const { defaultSeo, favicon } = this.$page.strapi.global;

    // Merge default and article-specific SEO data
    const fullSeo = {
      ...defaultSeo,
      ...seo,
    };

    return {
      title: fullSeo.metaTitle,
      meta: getMetaTags(fullSeo),
      link: [
        {
          rel: "favicon",
          href: getStrapiMedia(favicon.url),
        },
      ],
    };
  },
};
</script>

frontend/src/layouts/Default.vueのstyleを削除します。

frontend/src/layouts/Default.vue
<template>
  <div>
    <Navbar />
    <slot />
  </div>
</template>

<script>
import Navbar from "~/components/Navbar";
export default {
  components: {
    Navbar,
  },
};
</script>

<style>

</style>

frontend/src/components/Navbar.vueのtemplateを変更します。

frontend/src/components/Navbar.vue
<template>
  <header class="text-gray-600 body-font">
    <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
      <g-link to="/" class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">{{ $static.strapi.global.siteName }}</g-link>
      <nav class="md:ml-auto flex flex-wrap items-center text-base justify-center">
        <g-link v-for="category in $static.strapi.categories" :key="category.id" :to="'/category/' + category.slug" class="mr-5 hover:text-gray-900">{{
          category.name
        }}</g-link>
      </nav>
    </div>
  </header>
</template>

<static-query>
query {
  strapi {
    categories {
      id
      name
      slug
    }
    global {
      siteName
    }
  }
}
</static-query>

frontend/src/components/Articles.vueのtemplateを変更します。

frontend/src/components/Articles.vue
<template>
  <section class="text-gray-600 body-font">
    <div class="container px-5 py-4 mx-auto">
      <div class="flex flex-wrap -m-4">
        <Card
          v-for="article in articles"
          :article="article"
          :key="article.id"
        />
      </div>
    </div>
  </section>
</template>

<script>
import Card from "~/components/Card";

export default {
  props: {
    articles: Array,
  },
  components: {
    Card,
  },
  computed: {

  },
};
</script>

frontend/src/components/Card.vueのtemplateを変更します。

frontend/src/components/Card.vue
<template>
  <div class="p-4 md:w-1/3">
    <g-link
      :to="{ path: '/article/' + article.slug }"
      :key="article.id"
    >
      <div class="h-full border-2 border-gray-200 border-opacity-60 rounded-lg overflow-hidden">
        <g-image :src="getStrapiMedia(article.image.url)" class="lg:h-48 md:h-36 w-full object-cover object-center" alt="blog" />
        <div class="p-6">
          <h2 v-if="article.category" class="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{{ article.category.name }}</h2>
          <h1 class="title-font text-lg font-medium text-gray-900 mb-3">{{ article.title }}</h1>
          <p class="leading-relaxed mb-3">{{ article.description }}</p>
          <div class="flex items-center flex-wrap ">

            <span class="text-gray-400 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm py-1">
              {{ article.author.name }}
              <g-image
                class="rounded-full h-12 w-12 flex items-center justify-center ml-3"
                :src="getStrapiMedia(article.author.picture.url)"
                :alt="article.title"
              />
            </span>

          </div>
        </div>
      </div>
    </g-link>
  </div>
</template>

<script>
import { getStrapiMedia } from "~/utils/medias";

export default {
  props: {
    article: Object,
  },
  methods: {
    getStrapiMedia,
  },
};
</script>

変更前
strapi-blog変更前

変更後
strapi-blog変更後

まとめ

gridsome-plugin-tailwindcssを使うことで簡単にGridsomeでTailwindを使うことができました。
Tailwindは今回使用したTailblocksだけでなく、Tailwind公式のTailwind UIなど無料・有料含めたくさんのコンポーネント集があり、これらを利用することでより簡単に、よりスピーディにサイトを制作できると思います。