Google Apps ScriptからSlackやLINEに通知を送る

**Google Apps Script(以下、GAS)とはGoogleが開発したサーバレスな関数の実行環境です。**GASはGoogle SpreadsheetやGoogle Documentなどサービスと連携してプログラムを実行できるため、業務やルーティンワークの自動化に最適です。

この記事は、GASからSlackやLINEにメッセージを投稿する方法を紹介します。また、GASのコードを記述しているので、コピーしてそのまま使うことができます。

関連記事: GASをclasp(CLIツール)+ TypeScriptでローカルで開発する

Slackのチャンネルにメッセージを投稿する

Slackのチャンネルにメッセージを投稿する場合は、Incoming Webhookを利用します。

SlackのIncoming Webhookに登録する

SlackのIncoming Webhook

「Slackに追加」をクリックして、投稿するチャンネルを選択します。

その後、Webhook URLが表示されるのでこのURLをコピーします。これがPOSTリクエストを送るエンドポイントです。

SlackのIncoming Webhook URL

最下部までスクロールして、「設定を保存」をクリックします。

GASのプロパティに値を保存する

Webhook URLを知っていると誰でもチャンネルに投稿できてしまうので、外に漏れないようにGASのプロパティに登録しておきましょう。

GASのプロパティは環境変数のようなもので、ファイルのオーナーしか閲覧できません。

GASから、[ファイル > プロジェクトのプロパティ > スクリプトのプロパティ]を開きます。

GAS

「行を追加」をクリックして、Key/Valueを追加しましょう。

GASのスクリプトのプロパティ

ここでは、SLACK_CHANNEL_URLをキーにしています。

GASでコードを書く

プロパティに登録した値は、PropertiesServiceを使って取得します。

slack.js
const SLACK_CHANNEL_URL = PropertiesService
    .getScriptProperties()
    .getProperty('SLACK_CHANNEL_URL')

function send() {
  // 投稿するメッセージ
  const text = 'メッセージを投稿します'

  // 投稿者名とアイコンを設定する
  const data = {
     "username" : 'Slack Panda',
     "icon_emoji": ':panda_face:',
     text,
  };

  const params = {
    "method" : "POST",
    "contentType" : "application/json",
    "payload" : JSON.stringify(data),
  };

  UrlFetchApp.fetch(SLACK_CHANNEL_URL, params)
}

GASに組み込みのUrlFetchAppオブジェクトを使ってSlackのエンドポイントにPOSTリクエストを送ります。

[実行 > 関数を実行 > send]をクリックします。

GASの関数実行

Slackにメッセージを投稿できました 🎉

Slackのメッセージ

あとは、JavaScriptで投稿するメッセージを好きなようにカスタマイズしましょう!

LINEにメッセージを投稿する

次は、LINEのグループに投稿してみましょう。

LINE Notifyに登録して、アクセストークンを取得する

LINE Notifyにアクセスして、アクセストークンを取得しましょう。

LINE Notify API Document

右上のログインを押して、アカウントを登録します。スマホのアカウントと同じメールアドレスとパスワードを入力します。

その後、右上のログインのところにユーザー名が表示されます。[ユーザー名 > マイページ]に遷移し、ページ最下部の「トークンを発行する」をクリックします。

開発者向けアクセストークン

メッセージを通知するグループを選択して、アクセストークンを取得します。このトークンをコピーしておきましょう。

このトークンは一度しか表示されないので要注意です。

Slackの時と同様にGASのプロパティにアクセストークンを保存します。

ここでは、LINE_NOTIFY_TOKENをキーにしています。

Google Apps Scriptでコードを書く

LINE Notifyのエンドポイント https://notify-api.line.me/api/notify にPOSTでリクエストを送ります。

line.js
const LINE_NOTIFY_TOKEN = PropertiesService
    .getScriptProperties()
    .getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'

// 本文を組み立てる
function getMessage() {
  return `LINEに投稿するメッセージ`
}

function send() {
  const options = {
    "method": "POST",
    "headers": {
      "Authorization": `Bearer ${LINE_NOTIFY_TOKEN}`,
    },
    "payload": {'message': getMessage()},
  }

  UrlFetchApp.fetch(ENDPOINT, options);
}

Slackの場合と同様に、関数sendを実行します。

LINEへのメッセージ

LINEにメッセージを投稿できました 🎉

SlackとLINEの連携をbot化する

SlackとLINEの通知を、定期実行してbot化します。そのためには、関数を実行するトリガーを設定します。

GASのトリガーを設定する

[編集 > 現在のプロジェクトのトリガー]をクリックします。

GASの画面

トリガーの設定画面に遷移後、「トリガーを追加」をクリックします。

トリガー設定画面

「実行する関数」をsendにし、「イベントのソースを選択」で時間主導型を設定します。これで日付ベースで定期実行のタイミングを決定できます。

例えば、「毎日、朝8~9時に通知を送る」場合は、「日付ベースのタイマー」で「午前8時 ~ 9時」を選択します。

Google Apps Scriptの場合、分の指定はできないので、8 ~ 9時の1時間のどこかタイミングで関数が実行されます。

「保存」ボタンをクリックして設定を保存しましょう。これで定期実行するbot化できました 🎉

まとめ

今回はGoogle Apps Scriptを使って、SlackとLINEに通知を送る方法を紹介しました。

Google Apps Scriptを使いこなして、他のサービスと上手に連携しましょう!

Google Apps ScriptSlackLINE
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

Gmailで受信したメールをGASでLINEに転送する

私事ながら2019年に結婚しました。それから結婚式の式場を選び、日取りを決めて、当日の準備に当たります。すると、式場から打ち合わせのメールが不定期に飛んできます。

私はメールの受信箱を頻繁に見ないので、やりとりはSlackにしたいと式場に申し出てもあえなく断られました。そこで、GmailからLINEにメールを転送すれば見逃しがなくなると考え、Google Apps Script(以下、GAS)で実装しました。

本記事では、GmailからLINEに特定のメールを転送する方法をご紹介します。

関連記事:

GASでGmailの受信メールをLINEに転送するコードを解説します

まずコードを掲載します。その後、コメントを付与した箇所に解説を加えていきます。

main.js
const ENDPOINT = 'https://notify-api.line.me/api/notify'

// 1. 転送したいメールの送信元アドレスを指定する
const FROM_ADDRESS = [''].join(' OR ')
// 2. トリガーの設定間隔と合わせる
const MINUTES_INTERVAL = 5

function fetchNotices() {
  const now = Math.floor(new Date().getTime() / 1000)
  const intervalMinutesAgo = now - (60 * MINUTES_INTERVAL)
  // 3. 検索条件を設定
  const query = `is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo}`

  // 4. メールを取得する
  const threads = GmailApp.search(query)
  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------

${latestMessage.getPlainBody().slice(0, 350)}
`
    notices.push(notice)

    // 5. メールを既読にする
    latestMessage.markRead()
  }

  return notices
}

1. 転送したいメールの送信元アドレスを指定する

const FROM_ADDRESS = [''].join(' OR ')

**配列内に転送したいメールの送信者のアドレスを追加します。**これは@example.comのようにドメインを指定しても構いません。これで、全ての新着メールではなく、特定の送信者からのメールだけ転送するような設定ができます。

もしこの配列が空である場合、定数FROM_ADDRESSには空文字列が代入されるため、全てのメールを取得することになります。

このため、全てのメールをGmailからLINEに転送したい場合、特にメールアドレスを記述する必要はありません。

2. 5分前までの新着メールを取得する

const MINUTES_INTERVAL = 5

定数MINUTES_INTERVALで5分前までの新着メールを転送対象とします。この時間は、あとで設定するトリガー(関数の実行タイミング)と合わせるようにします。

3. 検索条件を設定(フィルター設定)

const query =
  `is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo}`

queryはメールの検索条件です。この条件に該当するメールだけを転送対象とします。以下が今回使う絞り込みの条件です。

keyvalue条件
isunread未読のメールのみis:unread
fromgas@xxx.com OR yyy.com転送するメールの送信元from(gas@xxx.com OR yyy.com)
after1592707480この時間以降のメールafter:1592707480

この検索条件は、Gmailのフィルター設定をすると自動で作成されます。条件を変更する場合は、Gmailのフィルター設定から条件を指定して、その結果を使ってコードを書き換えましょう。

Gmailのフィルターを設定する画面

なお、検索条件の組み立てはビルダーパターンで設計するのが定石です。

ただし、メールの検索条件を頻繁に変更することはないので、今回は文字列で必要十分です。

4. 新着メールを取得する

  const threads = GmailApp.search(query)
  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    // ...
  }

検索条件に該当するメールは、GmailAppのsearchメソッドで取得します。返り値の型はGmailThread[]です。

次にgetMessagesForThreads(threads)メソッドで、それぞれのメールの一連のやりとりを取得します。返り値はGmailMessage[][]です。

for (const messages of mails)で2次元配列mailsの値をmessagesに格納します。for (const a of b)は、配列bの値を変数aに格納する記法です。

これと似た記法でfor (const a in b)というものもありますが、こちらは配列のindexを取得する記法です。

const array =  ['a', 'b', 'c']

for (const char of array) {
    console.log(char)
}

// a
// b
// c

for (const index in array) {
    console.log(index)
}

// 0
// 1
// 2

これで最新のメールを取得できました。

GmailにおけるThreadとMessageの違い

ここでThreadMessageという用語の整理をしましょう。細かい内容なので読み飛ばしてもらっても構いません。

searchメソッドで取得できるThreadは「あるメールとそのメールに対する一連の返信」です。あるメールを送った後そのメールに返信がつき、さらにそのメールに返信する、というのがメールの使い方です。Threadには一番最初のメールとそれに付随する返信を含んだものです。

一方、getMessagesForThreadsメソッドで取得するMessageは単体のメールです。元のメールならそのメール、返信ならその返信です。

つまり、Threadは最初のメールとそれに対する返信メールです。一件一件のメールそのものはMessageと呼ばれているのです。これがThreadとMessgeの違いです。

5. 新着メールを既読にする

latestMessage.markRead()

markRead()メソッドでメールを既読にできます。これで毎回Gmailの未読件数の増加を防げますね。

5分単位で新着メールをチェックする

新着メールを取得する処理を記述できました。次は、5分ごとにLINEに転送する設定を書きます。

GASのトリガーを5分単位で設定する

次に、トリガーを設定しましょう。上記で設定した間隔(今回は5分)間隔でGASを動かすようにします。

トリガー設定画面

GASのトリガー設定方法は「GASのトリガーを設定する」をご覧ください。

実行する関数はmain関数にします(記事最後のコード全文を参照)。

LINEにメールを転送する

最後にLINEにメールを転送する関数を作成しましょう。

const LINE_NOTIFY_TOKEN = PropertiesService
  .getScriptProperties()
  .getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'

function send(mail) {
  const options = {
    'method': 'POST',
    'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
    'payload': {'message': mail},
  }

  UrlFetchApp.fetch(ENDPOINT, options)
}

LINEに転送するためには、LINE Notifyのトークンを取得する必要があります。

また、取得したトークンはPropertiesServiceで使えるように、GASのプロパティに格納しておきましょう。

これでメールの転送準備ができました。

実際にメールを転送してみる

実際に自分にメールを送ってみて、LINEに転送できているか確認してみましょう。

LINEの画面

きちんと転送されていますね。なお、コード全文は記事の最後に記載しています。

まとめ

式場を決めてすぐの頃、妻との間で「式場からのメール確認した?」というやりとりを何度かしたので、これは二人がよく見るLINEに転送せねばと思いコードを実装しました。

GASのおかげで妻との会話の始まりが「メールチェックした?してない?」ではなく、「返信内容をどうする?」という本質的な内容に変化し、効果を実感できました。

非同期コミュニケーションが生まれる仕組みを作り、時間を有効に活用しましょう 🎉

コード全文を記載します

今回使ったコードの全文を記載します。

main.js
const LINE_NOTIFY_TOKEN = PropertiesService
  .getScriptProperties()
  .getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'

const FROM_ADDRESS = [''].join(' OR ')
const MINUTES_INTERVAL = 5

function main() {
  const notices = fetchNotices()

  if (notices.length === 0) {
    return
  }

  for (const notice of notices) {
    send(notice)
  }
}

function fetchNotices() {
  const now = Math.floor(new Date().getTime() / 1000)
  const intervalMinutesAgo = now - (60 * MINUTES_INTERVAL)
  const query = `(is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo})`

  const threads = GmailApp.search(query)

  if (threads.length === 0) {
    return []
  }

  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------

${latestMessage.getPlainBody().slice(0, 350)}
`
    notices.push(notice)

    latestMessage.markRead()
  }

  return notices
}

function send(notice) {
  if (LINE_NOTIFY_TOKEN === null) {
    Logger.log('LINE_NOTIFY_TOKEN is not set.')
    return
  }

  const options = {
    'method': 'POST',
    'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
    'payload': {'message': notice},
  }

  UrlFetchApp.fetch(ENDPOINT, options)
}
Google Apps ScriptLINEGmail
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

LaravelにCircle CIを導入する手順

Circle CIのアカウントは作成済み、レポジトリは連携済みという前提で進めていきます。

Laravelのバージョンは6.2です。

関連記事: Laravel + BrowsersyncでBladeファイルの変更を検知して自動でブラウザを更新する

Circle CIのconfig.ymlを準備する

プロジェクトルートに.circleci/config.ymlを作成します。

.circleci.gitがあるディレクトリと同じ階層に置く必要があります。

config.ymlは下記のように記述します。

config.yml
version: 2
jobs:
  build:
    docker:
      # PHP用のイメージを指定
      - image: circleci/php:7.3-node-browsers

    steps:
      # working_directoryにソースコードを配置する
      # working_directoryはデフォルトでは~/projectに設定されている
      - checkout

      - run: sudo docker-php-ext-install zip

      # vendor配下のpackageをキャッシュから呼び出す
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "composer.json" }}
            - v1-dependencies-

      - run: composer install -n --prefer-dist

      # vendor配下をキャッシュする。
      # 二回目以降のcomposer installでは
      # 新しく追加されたpackageのみダウンロードするため、
      # CIの実行時間が短縮される
      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor
      - restore_cache:
          keys:
            - node-v1-{{ checksum "package.json" }}
            - node-v1-
      - run: yarn install
      - save_cache:
          key: node-v1-{{ checksum "package.json" }}
          paths:
            - node_modules

      # テスト用データベースにsqliteを利用する
      - run: touch storage/testing.sqlite
      - run: php artisan migrate --env=testing --database=sqlite_testing --force

      # phpunitを実行する
      - run: ./vendor/bin/phpunit
      # codeceptionを使う場合はコメントアウトを外す
#      - run: ./vendor/bin/codecept build
#      - run: ./vendor/bin/codecept run

その他のconfig.ymlの設定は、公式ドキュメントを参照してくださいね。

また、php:7.3-node-browsers以外のコンテナイメージを利用する場合は、Circle CIで使えるDocker Image一覧から探してみましょう。

.env.testingを用意する

Circle CIが用意しているLaravel用の公式レポジトリを参考にします。

test用の環境変数ファイルである.env.testingを用意しましょう。

.env.testing
APP_ENV=testing
APP_DEBUG=true
APP_KEY=27ceqUDGvdm77abRFSNQbFSVFIz4dGIH
DB_DEFAULT=sqlite_testing

テスト時にsqliteを利用する設定を書く

config/database.phpsqlite_testingの設定を追加します。

'connections' => [
    // ...

    'sqlite_testing' => [
        'driver'   => 'sqlite',
        'database' => storage_path().'/testing.sqlite',
        'prefix'   => '',
    ],
],

これで設定は完了です。

Circle CIの実行結果の成功

ローカルでCircle CIを動かす

たった2コマンドでCircle CIを実行できます(Dockerはすでにインストールしているものとします)。

Circle CIをローカルにインストールしましょう。

$ curl -fLSs https://circle.ci/cli | bash

プロジェクトルートで、Circle CIを実行します。

$ circleci local execute --job build

実行結果は下記です(一番最後のコマンド./vendor/bin/phpunitの実行結果を抜粋)。

====>> ./vendor/bin/phpunit
  #!/bin/bash -eo pipefail
./vendor/bin/phpunit
PHPUnit 8.4.1 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 195 ms, Memory: 18.00 MB

OK (2 tests, 2 assertions)
Success!

これで.circleci/config.ymlを変更した時に、毎回pushして確認せずに済みますね。

ただし、ローカルではPHPのpackageとJSのmoduleはキャッシュされません。

このため、Circle CI実行するたびにcomposer installyarn installで全てのパッケージをダウンロードしてくるので気をつけてくださいね。

その他、circleciコマンドについては公式ドキュメントをチェックしてみてください。

Circle CIとSlackを連携する

SlackのApp管理でCircle CIのアプリを追加してください。

結果を通知するチャンネルを選び、作成されたWebhookのURLをコピーします。

そのURLをCircle CIのプロジェクトのSETTINGS > NOTIFICATIONS > Chat NotificationのWebhook URLの欄に記入します。

Circle CIのWebhook URLの設定画面

& Test Hookをクリックすると、Slackに通知が飛びます。

Circle CIのSlackへの通知結果

Circle CIの結果のSlack通知の設定が完了しました。

Webアプリの持続的な開発のために、継続的インテグレーションを実施していきましょう。

LaravelCircle CISlack
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

スプレッドシートのデータを使ってUML図を作成する

本記事のアイデアは、業務でプロダクトオーナーやライターさんが診断チャートを作る際にUMLを手で書くのがとても大変そうだったので、手軽にする方法はないかと考えて実装したものです。

**この記事では、GASを使ってスプレッドシートのデータをUMLで表現できよるように変換します。**スプレッドシートに記載した診断チャートのような選択肢を選んでいくデータ用意します。そして、この選択肢と結果の関係をPlantUMLの記法に落とし込んで、UML図として図示する方法をしていきます。

データは下図のように3カラム設定します。左からそれぞれ、「from(質問)」、「選択肢」、「to(結果)」となることを想定しています。

スプレッドシート

スプレッドシートからUMLの記法に書き起こす際に、Google Apps Script(以下、GAS)を利用します。

関連記事: Google Apps ScriptからSlackとLINEを連携するbotを作る手順を紹介します

GASのコードを概観する

スプレッドシートのデータをUMLの記法に落とし込むにあたり、大まかに下記の流れでコードを設計しています。

1. スプレッドシートから質問、選択肢、結果を取得する
2. 取得したデータをそれぞれ一行ずつUMLの記法に落とし込む
3. 結果を表示して、コピぺで別のところに貼り付けられるようにする

では、コードを見ていきましょう。

main.js
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()

function main() {
  const questions = getQuestions()
  const uml = createUml(questions)
  Logger.log(uml)

  return uml
}

// 1. Spreadsheetから質問・選択肢・結果を取得します
function getQuestions() {
  const lastRow = sheet.getLastRow()
  return sheet.getRange(2, 1, lastRow - 1, 3).getValues()
}

function createUml(questions) {
  let uml = "```uml\\n"

  for (const row in questions) {
    const question = questions[row]
    uml += createUmlLine(question)
  }

  uml += "```"

  return uml
}

function createUmlLine(question){
  const [id, option, next] = question

  return "[" + id + "]-->[" + next + "]:" + option + "\\n"
}

// 2. Spreadsheetを開いたときに実行する関数です
function onOpen(e) {
  const ui = SpreadsheetApp.getUi()
    .createMenu('カスタム機能')
    // 3. メニューをクリックした時に実行する関数を指定します
    .addItem('UML図用のスクリプトを作成', 'showUml')
    .addToUi()
}

function showUml() {
  const uml = main()
  const description = "下記のスクリプトをコピーして\\nesaに貼り付けるとUML図が生成されます。\\n"
    + "--------------------------------------------------\\n\\n"
    + uml

  // 4. メッセージボックスにUML記法を表示します
  Browser.msgBox(description)
}

以下、コードを解説していきます。

1. スプレッドシートから質問・選択肢・結果のデータを取得します

sheetオブジェクトのメソッドgetLastRow()でデータが記述されている最終行を取得します。

そして、getRange()メソッドで、質問・選択肢・結果のデータを持つRangeオブジェクトを取得します。RangeオブジェクトのgetValues()メソッドを使って、スプレッドシートのデータをJavaScriptの配列に変換します。

スプレッドシートから取得したデータは多次元配列になることにご注意ください。

[
  [忙しい?, はい, 頑張ってね],
  [忙しい?, いいえ, コーヒー飲もう],
  [コーヒー飲もう, 社内で, コーヒーマシン],
  [コーヒー飲もう, 遠くで, スタバ],
  [コーヒー飲もう, 近場で, ドトール],
  [コーヒー飲もう, 軽く外で, 自動販売機],
  [コーヒー飲もう, 飲めない, お茶を飲む],
  [スタバ, 甘いのを, フラペチーノ],
  [スタバ, 苦いのを, ブラック],
  [自動販売機, 冷たいのを, ホット],
  [自動販売機, 暖かいのを, アイス]
]

2. onOpen(e)はSpreadsheetを開いたときに実行される関数です

onOpen()は、スプレッドシートを表示した時に実行される関数です。

createMenu(name)でメニューバーに「カスタム機能」という名前のメニューを追加します。

3. メニューから「UML図用のスクリプトを作成」をクリックした時に実行する関数を指定します

同じonOpen()関数内のaddItem(name, callback)で、「UML図用のスクリプトを作成」というアイテムを追加します。このアイテムをクリックするとコールバック関数showUml()が発火するようにします。

すると、下図のようにメニューバーに「カスタム機能 > UML図用のスクリプトを作成」という選択肢が表示されました。

スプレッドシートのメニュー

4. メッセージボックスにUML記法を表示します

「UML図用のスクリプトを作成」をクリックした時に実行するshowUml()の中で、Browser.msgBox(description)を実行しています。

これにより、UML記法の文字列をメッセージボックスを画面に表示できます。

スプレッドシートのメニュー

下記のスクリプトをコピーして
esaに貼り付けるとUML図が生成されます。
-------------------------------

\\\uml
[忙しい?]-->[頑張ってね]:はい
[忙しい?]-->[コーヒー飲もう]:いいえ
[コーヒー飲もう]-->[コーヒーマシン]:社内で
[コーヒー飲もう]-->[スタバ]:遠くで
[コーヒー飲もう]-->[ドトール]:近場で
[コーヒー飲もう]-->[自動販売機]:軽く外で
[コーヒー飲もう]-->[お茶を飲む]:飲めない
[スタバ]-->[フラペチーノ]:甘いのを
[スタバ]-->[ブラック]:苦いのを
[自動販売機]-->[ホット]:冷たいのを
[自動販売機]-->[アイス]:暖かいのを
\\\

\\\はバッククォートが3つ連続していることを表します。)

これをesaに貼り付けると、診断チャートを簡単に可視化できます 🎉

esa

まとめ

一人で図を書くなら紙に書けばいいですが、仕事で図を他の人と共有したい場合はそうはいきません。

スプレッドシートなら誰でも扱うことができます。それに、このスクリプトを使えばUML記法に慣れていない非エンジニアの方でも簡単に診断チャートを作成できます。

ちなみに、この時チームで作ったプロダクトは交通事故被害に遭われた方のためのやること診断というものです。交通事故被害に遭われた方が表示される質問に答えていくと、次にするべきことがわかるという診断です。

以上、スプレットシートからGASを使って診断チャートを作成する方法でした。

Google Apps Scriptesa.io
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer

Next.jsでTailwind CSSを使えるようにする

**Next.jsとはVercelが作成しているReactのフレームワークです。**面倒な設定を書かなくてもすぐに使えるZero Configを標榜しており、実際にwebpackやTypeScriptと一緒にReactを書く際にも特別な準備は不要です。SSRにも対応しており、Reactで開発するならNext.jsかFacebook製のCreate React Appを使うのがスタンダードになっています。

**また、Tailwind CSSとはユーティリティファーストのCSSフレームワークです。**その特徴は、Tailwind CSSによって提供されるクラスを組み合わせてコンポーネントを作り、サイトをデザインしていくところにあります。さらにCSSフレームワークなので、CSSに慣れていない人でも簡単に使うことができます。

関連記事: Tailwind CSSでのフロントエンド開発で素晴らしい開発体験を得よう

**この記事は、Next.jsにTailwind CSSを導入する方法を紹介するものです。**Next.jsもTailwind CSSもフロントエンド界隈でにわかに話題になっていますね。

設定内容はNext.js公式のwith-tailwindcss Exampleを参考にしています。

今回利用するバージョンは以下の通りです。

{
 "dependencies": {
    "next": "9.4.4",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "tailwindcss": "1.4.6"
  }
}

Next.jsのプロジェクトを作成する

まずはNext.jsのプロジェクトを作成しましょう。

$ npx create-next-app nextjs-with-tailwindcss

コマンドを実行すると、まっさらなプロジェクトかExampleを利用するか選択肢が提示されます。

? Pick a template › - Use arrow-keys. Return to submit.
  Default starter app
   Example from the Next.js repo

今回はDefault starter appを選びます。

$ npx create-next-app nextjs-with-tailwindcss
 Pick a template Default starter app
Creating a new Next.js app in /Users/panda/next/nextjs-with-tailwindcss.

Installing react, react-dom, and next using yarn...

yarn add v1.13.0

# ...

Success! Created nextjs-with-tailwindcss at /Users/panda/next/nextjs-with-tailwindcss

Tailwind CSSの設定を解説します

Tailwind CSSをインストールする

プロジェクトが作成できたので、プロジェクトルートに移動します。

$ cd nextjs-with-tailwindcss

Tailwind CSSをインストールしましょう。

$ npm install -D tailwindcss

これでTailwind CSSを導入できました。

tailwind.config.jsを作成する

次にTailwind CSSの設定ファイルtailwind.config.jsを作成します。

$ npx tailwindcss init

tailwindcss 1.4.6

 Created Tailwind config file: tailwind.config.js

バージョン1.4からtailwind.config.jsにpurgeが追加されました。これはPurgeCSSをデフォルトで利用するための項目です。

tailwind.config.js
module.exports = {
  purge: [],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

PurgeCssで不要なCSSを削除する設定をする

PurgeCSSは、コンテンツから使われていないCSSを削除するためのPostCSS製のツールです。

PurgeCSSはHTMLのclassやReactコンポーネントのclassNameで指定されていないCSSを削除します。つまり、実際に使われているCSSしかファイルにバンドルしないのです。

Tailwind CSSのファイルサイズは1996.1kbであり、Gzipで圧縮しても144.6kbもあるので、使わないCSSまで配信してしまうとサイトパフォーマンスに影響してしまいます。

そこで、Reactコンポーネント内で利用しているクラスのみをCSSのビルド対象に含めましょう。

tailwind.config.js
module.exports = {
purge: ['./components/**/*.jsx', './pages/**/*.jsx'],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

また、postcss-preset-envをインストールしてpostcss.config.jsファイルを作成します。

$ npm i -D postcss-preset-env
postcss.config.js
module.exports = {
  plugins: ['tailwindcss', 'postcss-preset-env'],
}

これでPurgeCSSの設定ができました。

なお、Tailwind CSSのバージョン1.3以前は自分でPurgeCSSを導入する必要がありました。かつてpostcss.config.jsに以下のように記述していました。

postcss.config.js
const purgecss = require('@fullhuman/postcss-purgecss')({
  content: ['./components/**/*.jsx', './pages/**/*.jsx'],
  defaultExtractor: content => {
    const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
    const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []
    return broadMatches.concat(innerMatches)
  }
})

module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    ...process.env.NODE_ENV === 'production'
      ? [purgecss]
      : []
  ]
}

以前と比べるとv1.4から設定が楽になりましたね。

pages/style.cssでTailwind CSSのユーティリティを読み込む

次はReactコンポーネント内でTailwind CSSを使えるように設定をしていきます。pages/styles.cssを作成しましょう。

pages/styles.css
@tailwind base;
@tailwind components;

.btn-blue {
  @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}

.hero {
  @apply py-20;
}

.title {
  @apply text-5xl text-center;
  color: #333;
  line-height: 1.15;
}

@tailwind utilities;

カスタマイズしたクラスは、@tailwind components@tailwind utilitiesの間に記述します。

pages/_app.jsxでstyle.cssを読み込む

**Next.jsは_app.jsxでグローバルなCSSを読み込みます。**ファイルの先頭でCSSファイルをimportしましょう。

pages/_app.jsx
import './styles.css'

function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default App

これで、全てのコンポーネントでTailwind CSSのクラスを使うことができます。

pages/index.jsxを作成して試してみましょう。

pages/index.jsx
export default function Index() {
  return (
    <div>
      <div className="hero">
        <h1 className="title">Next.js + Tailwind CSS 🐼</h1>
        <p className="text-center text-teal-500 text-2xl py-4">This is an Example.</p>
      </div>
    </div>
  )
}

開発サーバーを立ち上げ、http://localhost:3000にアクセスします。

$ npm run dev
> with-tailwindcss@1.0.0 dev /Users/panda/next/nextjs-with-tailwindcss
> next "-p" "3000"

ready - started server on http://localhost:3000

index.jsxページ

Next.jsでTailwind CSSを導入できました 🎉

まとめ

Next.jsとTailwind CSSの組み合わせは開発体験がとてもいいです。個人開発でCreepy Nutsのファンサイトを作ったときもこの技術スタックでした。

**ユーティリティを組み合わせることでスタイルを作るTailwind CSSと、コンポーネントを組み合わせることでページを作るReact。**CSSをJavaScriptで分野は異なるものの、互いに思想がリンクするようでとても面白いですね。

以上、Next.jsにTailwind CSSを導入する方法でした。

Next.jsTailwind CSS
プログラミングをするパンダ
プログラミングをするパンダ (@Panda_Program)
Software Engineer