LaravelでTailwind CSSを使いたい

Laravelでサイトのデザインをするにあたり、Tailwind CSSを使いたい方向けに、セットアップの手順を解説します。

Tailwind CSSとは、Bootstrapのようにクラス名を指定するだけでスタイルを当てることができるツールです。デザインがモダンで、Bootstrapより柔軟にカスタマイズできるのが特徴です。

対象のバージョンは以下です。

Laravel 6.2
Tailwind CSS 1.1.2

関連記事: Tailwind CSSでCSSが苦手なエンジニアでもイイ感じのサイトが作れることを伝えたい

Tailwind CSSをインストールする

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

$ npm install tailwindcss

package.jsonが更新されていれば成功です。

{
    // scriptsなど
    "dependencies": {
        "tailwindcss": "^1.1.2"
    }
}

tailwind.config.jsを作成する

tailwind.config.jsをプロジェクトに追加します。このファイルが、Tailwind CSSのカスタマイズ性を象徴しています。

$ npx tailwind init

初期ではカスタマイズ不要であるため、tailwind.config.jsに下記のコードを追加します。

module.exports = {
  theme: {},
  variants: {},
  plugins: [],
}

詳しい使い方は公式ドキュメントをご覧ください。

Tailwind CSSをapp.scssから呼び出す

resources/sass/app.scssに下記の3行を追記するだけでOKです。

@tailwind base;
@tailwind components;
@tailwind utilities;

この中にTailwind CSSの本体が入っています。CSSにコンパイルすると、クラス名とCSSのスタイルが出力されます。

laravel-mixにTailwind CSSの設定を書く

laravel-mixとscssを使う場合、webpack.mix.js+で記述している行を追加しましょう。

  const mix = require('laravel-mix');
+ const tailwindcss = require('tailwindcss');

 mix.js('resources/js/app.js', 'public/js')
     .sass('resources/sass/app.scss', 'public/css')
+    .options({
+        processCssUrls: false,
+        postCss: [ tailwindcss('./tailwind.config.js') ],
+    });

processCssUrlsは、LaravelがCSSをコンパイルする際に、url関数で指定した画像の相対パスをドキュメントルートからのパスに書き換えてくれる最適化をする機能です。

しかし、laravel-mixでTailwind CSSをビルドする場合、このオプションがonになっているとエラーが出てしまうため、processCssUrlsは必ずfalseにしましょう。

全てのオプションについてはlaravel-mixのMix Optionsをご覧ください。

laravel-mixでビルドする

$ npm run devでアセットファイルが開発用にビルドされます。

 DONE  Compiled successfully in 4372ms  12:50:40 AM

       Asset     Size   Chunks             Chunk Names
/css/app.css  855 KiB  /js/app  [emitted]  /js/app
  /js/app.js  591 KiB  /js/app  [emitted]  /js/app

本番環境で使う際は、$ npm run prodでjs、cssのファイルサイズを縮小してビルドしましょう。

出力されたapp.cssを確認する

public/css/app.cssを確認してみましょう。

/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Render the `main` element consistently in IE.
 */

main {
  display: block;
}
// 以下続く

成功です。

Tailwind CSSを使ってオシャレにラクにスタイリングしていきましょう。

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

Netlifyでリダイレクトの設定をする

ブログのURLを正規化するためにNetlifyでリダイレクトを設定する方法を調べました。ブログの正規化は検索エンジンのために行います。検索エンジンは以下のURLを全て異なるものとして認識するからです。

当サイトではhttpsで/なしで統一(=正規化)しています。

しかし、まだ各ブログ記事のURLは/あり・なしで分かれており、また検索エンジンはAMP対応のページを/amp/posts/post-name/index.htmlと認識してしてしまっていたので、Netlifyでリダイレクトの設定をしました。

Netlifyでリダイレクトを設定する方法は、netlify.tomlにリダイレクトの項目を記述することです。以下で手順をご紹介します。

リダイレクト設定をツールで生成する

Netlifyのリダイレクト設定を生成するツールを使います。

Netlifyのリダイレクト設定を生成するツール

2つ設定をしましょう。/posts/post-name/から最後の/を省くようにします。また、AMPのページではindex.htmlを省略するようにします。

/posts/*/  /posts/:splat  301!
/amp/posts/*/index.html  /amp/posts/:splat  301!

fromtostatus codeの順番で記述します。ステータスコードの後の!は、強制的にリダイレクトさせるという意味です。

また、:splatは、from内の*でマッチした文字列をそのまま持ってくるという意味です。

これで右上の「test rules」というボタンをクリックすると、netlify.tomlに記述するリダイレクトの設定を生成できます。

[[redirects]]
from = "/posts/*/"
to = "/posts/:splat"
status = 301
force = true
[[redirects]]
from = "/amp/posts/*/index.html"
to = "/amp/posts/:splat"
status = 301
force = true

なお、リクエストされたURLは、上から順にマッチするルールを探していくのでルールの順序には気をつける必要があります。

# このルールは/blog/my-old-titleで発火する
/blog/my-old-title   /blog/my-new-title

# 上記のルールにマッチしたため、このルールは発火しない
/blog/my-old-title   /blog/an-even-better-title

さらに詳しい情報はNetlifyの公式ドキュメントに記載されています。

Search Consoleで確認する

Search Consoleでhttps://panda-program.com/posts/gatsby-amp/をチェックしてみましょう。

Search Console

/がない/posts/gatsby-ampで正規化されていますね!

まとめ

いかがでしたでしょうか。Netlifyでのリダイレクトは簡単に設定できて便利ですね。

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

(2020/6/4追記: AtCoderのPHPのバージョンが7.4.4に更新されたので、サンプルコードに型をつけるなどGitHubで公開しているコード同様にアップデートしました)

tl;dt

  • プログラミングコンテストで時間オーバーとなり、解けそうな問題に回答ができなかった
  • コードを実行して結果を確認するプロセスを効率化すれば、ロジックの考察により多くの時間を割くことができる
  • そもそも競技プログラミングでは、所与の入出力を満たすロジックの記述に専念すれば良い
  • ロジックをデバッグするためなら、テストツール(PHPUnit)とデバッガ(Xdebug)を手軽に使える環境があれば良い

AtCoderのコンテストは時間との戦い

AtCoderが開催している競技プログラミングのコンテストに参加しています。

コンテスト本番と過去問での練習は、アルゴリズムを使って問題を解く点では同じです。

両者の違いは、コンテストでは制限時間があることです。

通常のコンテストでは問題は6問出題されるため、制限時間内に解けるだけ問題を解かなければなりません。

さらに、回答を提出するスピードが他の人より早いと、自分のレートが高くなります。

**このため、自分の書いたロジックが正解なのか、間違っているのか、フィードバックを得るスピードが成績に直結します。**間違っているなら、間違っている箇所を特定するスピードが早ければ早いほど高レート獲得に有利です。

PHPUnitとXdebugを導入してデータの処理過程と結果を確認する

プログラミングコンテストの問題形式は、与えられた入力に対する出力が正しいことを確認するものです。

入力と出力をチェックするなら、テストを書いて実行すればいいのです。出力が正しいなら、ロジックはどのようなものでも問われません(もちろんパフォーマンスの良し悪しは問われます)。出力が想定通りではない場合は、ロジックが間違っているということです。

その場合、コードの実行過程を素早くチェックできれば、デバッグは容易になります。

**つまり、PHPUnitとXdebugを使えば正解を出すためのフィードバックループを高速で回すことができるのです。**そこで、Dockerを使ってこの環境を構築することにしました。

コードはGitHubのリポジトリで公開しています。

また、この記事を書いた後、AtCoderの問題を題材にしてTDDを解説する記事を執筆しました。

関連記事: テスト駆動開発(TDD)とは何か。コードで実践方法を解説します

PHPUnitの便利な使い方

ジェネレータ関数とデータプロバイダーで複数パターンの入出力をシンプルに記述する

PHPUnitには@dataProviderというアノテーションがあります。

データプロバイダに指定した関数の返り値を、@dataProvider メソッド名というアノテーションをつけたメソッドの引数として扱う機能です。

<?php
use PHPUnit\Framework\TestCase;

class SampleTest extends TestCase
{
    /**
     * @group 100A
     * @dataProvider DataA
     */
    public function testA($expected, $a, $b)
    {
        $result = $this->solveA($a, $b);
        $this->assertSame($expected, $result);
    }

    /**
     * @return Generator
     */
    public function DataA(): Generator
    {
        // yield "0" => ["出力", "入力1", "入力2"];
        yield "1" => [7, "at", "coder"];
        yield "2" => [11, "php", "language"];
    }

    /**
     * 提出するロジック
     */
    private function solveA($a, $b): int
    {
        return strlen($a . $b);
    }
}

このコードでは、メソッドDataA()の返り値をテストメソッドtestA()の引数として扱っています。

データプロバイダには結果の値を$expectedとして記述しておきます。

こうすることで、データプロバイダDataA()に入出力値を記述し、テストメソッドtestA()にアサーションを記述し、solveAにはロジックを記述できます。

テストにおける役割をメソッドごとに分離することが可能になります。

結果、ロジックのコードを競技プログラミングの回答として提出すればいいことになります。

本来、ロジックはアプリケーションコードとして記述するものですが、簡便のためテストクラスにプライベートメソッドして記述しています。

テストクラスの中にロジックが入っていることに違和感がある方は、/srcディレクトリを作って/src配下のクラスでロジック記述し、TDDで開発できます。

groupアノテーションで実行したいテストを指定する

今回作成した環境では、PHPUnitは下記のコマンドで実行できます。

$ docker run --rm -v (pwd):/home atcoder/php

PHPUnit 9.0.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 00:00.489, Memory: 4.00 MB

OK (2 tests, 2 assertions)

これは、DockerfileにENTRYPOINT ["vendor/bin/phpunit", "tests"]と記述しているため、コンテナを実行するとPHPUnitを実行する仕組みになっているためです。

上記のサンプルのテストケースでは、メソッドtestA()@groupアノテーションを付与しています。

/**
 * @group 100A
 * @dataProvider DataA
 */
public function testA($expected, $a, $b){...}

このため、dockerの実行コマンドに--group=100Aを加え、testAメソッドのみを指定して以下のコマンドを実行しましょう。

$ docker run --rm -v (pwd):/home atcoder/php --group=100A

$ docker run --rm -v $(pwd)/tests:/home/tests atcoder/php --group=100A
PHPUnit 9.0.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 00:00.489, Memory: 4.00 MB

OK (2 tests, 2 assertions)

競技プログラミングのコンテストでは、問題ごとに回答を提出するため、@groupアノテーションを使ってテストメソッドを指定することで、自分が今解いている問題のロジックをテストすることに集中できます。

ジェネレータ関数でイテレータを実装する

データプロバイダにジェネレータ関数を利用しています。

/**
 * @return Generator
 */
public function DataA(): Generator
{
    // yield "0" => ["出力", "入力1", "入力2"];
    yield "1" => [7, "at", "coder"];
    yield "2" => [11, "php", "language"];
}

ジェネレータを利用することで、複数の入出力のパターンをシンプルに記述できます。

AtCoderでは入力・出力のサンプルとして2~3パターンが提示されるため、サンプルの数だけyieldでイテレーションのアイテムを記述しておけば、ロジックの実装に集中できます。

PhpStormのRemote Debugを設定する

Dockerfileとphp.iniの設定と、PhpStormの設定の記事を参考にしました。

AtCoderのPHP環境を構築するためのコードをGitHubで公開しています

PHP7 + PHPUnit + XDebugの環境をDockerで作成できるようにGitHubでコードを公開しています。

AtCoderのPHP7系のバージョンは7.0.15のみです。

(追記)2020年にAtCoderの言語のアップデートがあり、現在はバージョン7.4.4を使えるようになりました。

これに対応するため、Dockerのコンテナイメージとしてphp:7.4.4-alpine3.11を利用しています。

併せて、PHPUnitのバージョンを9系に更新しました。

なお、Dockerfileはローカル向けなので、GitHubで公開しているDockerfileは決して本番環境で使わないようにお願いします。

READMEを読みながら、ぜひトライしてみてくださいね。

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

コピペですぐに使えます。これ結構使えるんですよね。自分で何度も参照してます。

祝日を判定する

function isHoliday() {
  const today = new Date();
  const calendars = CalendarApp.getCalendarsByName('日本の祝日');
  const count = calendars[0].getEventsForDay(today).length;
  return count > 0;
}

isWeekday()の返り値は祝日ならtrue、祝日でなければfalseです。

解説

getCalendarsByName(name)で「日本の祝日」という名前のカレンダーを取得します。返り値はカレンダーの配列です。

getEventsForDay(today)で今日の日付のイベントを取得します。カレンダーに登録されたイベントの個数を返す関数なので、返り値は祝日でなければ0、祝日であれば1です。

休日を判定する

function isWeekend () {
  const today = new Date();
  const day = today.getDay();
  return (day === 6) || (day === 0);
}

isWeekend()の返り値は休日ならtrue、休日でなければfalseです。

休日と祝日を判定する

function main() {
  const today = new Date();
    if (isWeekend(today) || isHoliday(today)) {
        return;
    }

  // 何らかの処理
}

function isWeekend (today) {
  const day = today.getDay();
  return (day === 6) || (day === 0);
}

function isHoliday(today){
  const calendars = CalendarApp.getCalendarsByName('日本の祝日');
  const count = calendars[0].getEventsForDay(today).length;
  return count > 0;
}

その他、やりたいことがあれば、カレンダーのAPIを参照してみてください。

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

2018年LISPアドベントカレンダー5日目の投稿です。

LISPの悟り

新人プログラマが学ぶべきプログラミング言語を説く解説記事は星の数ほどあります。

そのどれかに記述があったのでしょうか、いつしか「LISPという難解な言語を触ると悟りが得られる」と知りました。

では、プログラミングの見方が変わる「LISPの悟り」=「LISPを学ぶことの効用」とは一体なんでしょうか。気になってネットの記事を読んでみました。すると、下記の一文に目がとまりました。

この悟りは、コンピュータを利用する上で「データは人間の為だけのものじゃない」ことを実感するような経験によることでしょう。 「Lispの悟りが分かっちゃう新春ポエム」

この内容、初読では理解できませんでした。平易な日本語なのに内容を理解できないのはどこか居心地が悪いものです。そしてこの悟りとは何だろうという好奇心が自分を後押しして、LISPを実際に触ってみることにしました。

以下では、プログラマ1年生の自分が、「Land of Lisp」というLisp学習のバイブル本を読んで得たLISPへの理解、引いてはプログラミングに対する見方が変わった点を紹介したいと思います。

LISPのコードとデータ

LISPにはコードモードとデータモードがあります。 コードは関数を実行するもの(コマンド・命令)。データは文字通り情報です。

累乗を計算するexpt関数で例を見てみましょう。

;; コードモード
(expt 2 3) ;; 2^3 = 8

;; データモード
'(expt 2 3) ;; (expt 2 3)

コードモードでは、リストの先頭の要素exptがコマンドで、リストの残りの要素はこの関数の引数です。全体を通して、「2を3乗する」という処理になっています。

一方、データモードではリストの先頭に'(シングルクオート)をつけます。このように記述すると、LISPではシングルクオート以降のリストはデータとして扱われるため、コードが実行されることはありません。

プログラミングに対する悟り

プログラミングはデータのインプットとアウトプットをするアプリケーションを作成することです。アプリケーションの内部ではデータのCRUD、つまり作成・読み取り・更新(加工)・削除といった処理を行なっています。プログラミングパラダイムが手続き型であれ、オブジェクト指向型であれ、関数型であれ、この点は同じだと思います。

このように考えると、プログラミングは、このコマンド(=データ処理)の組み合わせだと考えることができます。

オブジェクト指向の世界

一方、オブジェクト指向では、クラスというものは一般的に下記のように説明されます。

「クラスは、データと振る舞いをカプセル化したものである」 参考:プロパティ?フィールド?メンバー?C#のクラス構造のおさらい

オブジェクトはメソッドとプロパティで構成されています。LISPの考え方を援用すると、メソッドはコマンド(振る舞い)、プロパティはデータと言い換えられるのではないか。そう思い至った時、オブジェクト指向の見方が変わりました。

オブジェクトのメソッドの役割はデータを処理するだけなのだ考えると、プログラマがメソッド内で文字列やマジックナンバーをローカル変数に格納することを避ける理由がわかりました。そのオブジェクトで持つにふさわしいデータはコンストラクタで受け取り、プロパティに持たせるべきです。

メソッドの引数にデータを入れると、返り値としてデータが返ってくる。メソッドの中身はコマンドです。オブジェクト指向ではプロパティをメソッド内で参照することがあります。この時「メソッドは引数以外のデータ、しかもそのクラス内で存在しているデータを参照しているのだ」と意識するようになりました。

データはオブジェクトのプロパティに、あるいは定数としてオブジェクトの外に持たせるべきである、と思うに至りました。

通り一遍にオブジェクト指向を学べば、入門書にも書いてある内容でしょう。しかし、「classは型で、objectはモノ。例えばclassは車の型(車体)で、プロパティは車の色」と書かれたような入門書で、この理解にたどり着くことは不可能です。

LISPを学ぶことで初めて、クラス、メソッド、そしてプロパティの関係を深く理解することができました。

また、プログラミングで処理するデータソースは多種多様です。**JSONはデータの塊、CSV,TSVも拡張子こそ違えど、抽象化した視点から見れば等しくデータの塊です。**どこから読み取ってもデータはデータ。このデータの中には、データ自身を操作するプログラムが入り込む余地はありません(例外としては、エクセルはデータとシート内のデータを操作する関数を持っていますね)。

つまり、オブジェクト指向は、データを取得してプロパティに格納し、メソッドでデータを操るプログラミングです。

(もちろんオブジェクト指向はこれだけではありません。オブジェクト指向はオブジェクト同士のメッセージの伝達により、処理を行うプログラミングの方法です。ここでは特にプロパティとメソッドについて記述をしました)

データとコマンドはプロパティとメソッドに対応している。そして、プログラミングはデータとデータ処理のことだということがプログラミングに対する悟りです。

LISPの世界

一方、LISPにはオブジェクトもプロパティはありません(CLOSの議論は省きます)。

LISPの関数は第一級(first-class function)です。第一級の関数は、文字列や数値と同じように関数の引数として使用したり、配列に格納することができます。

つまり、LISPの関数(コマンド)はデータとして扱うことができます。

先ほど紹介した記事は最後にこのように締め括られています。

Lispは色々なパラダイムと組み合わせられますが、何がLispかといえば、 プログラム = データ であることに尽きるのではないでしょうか。

関数すらデータとして扱うことのできるLISPでは、データこそがプログラムなのです。

コンピュータの「演算」に対する悟り

次はデータに対する演算について考えていきます。

算術演算子と論理演算子

データは人間のためだけのものではありません。コンピュータがデータを処理するにあたって、データに型(type)を与え、コンピュータで扱えるものにする必要があります。

例えば、人間には同じように見えるけれども、文字列型と数値型で計算の結果が異なることは普通のプログラマなら誰でも知っていることです。 (以下はJavaScriptのコードです)

 const result1 = 1 + 1; // int型
 const result2 = "1" + "1"; // string型

 console.log(result1); // 2
 console.log(result2); // 11

float型とint型もややこしいですね。人間から見れば11.0も同じ1。でもコンピュータから見れば別物なのです。

では、論理型の計算はどうでしょうか。論理型には論理和、論理積という計算方法があります。この計算のために、多くの言語では論理演算子に||, &&, !が定義されています。

const now = new Date();
const day = now.getDay();
const isSevenOClock = now.getHours() === 7;
const isWeekDay = [1,2,3,4,5].includes(day);

if (isSevenOClock && isWeekDay) {
    // 朝起きる
}

このプログラムを実行するタイミングが月曜日の朝7時である時、if (true && true)に見えますか?また、これがコンピュータによるboolean型の計算であるように見えますか?

&&は演算子です。コンピュータから見れば、1 + 1+と同じ役割なのです。しかし、人には+&&は直感的には同じような演算子という認識を持つことは難しいでしょう。

しかし、LISPなら状況は違うのです。

LISPの論理演算

以下は足し算をする処理です。

(+ 1 1)

ご想像の通り、結果は2です。 次は値を比較してみましょう。

(eq (+ 1 1) 2)

このコードは(eq 2 2)として評価されます。結果はT(true)です。 次は論理演算子を使ったコードをみてみましょう。

(and (eq 1 1)
     (eq 2 2))

このコードは(and T T)として評価されるため、結果はT(true)です。

&&andは同じ役割をしています。が、LISPの方がより一層「演算」という役割が明確にされています。コンピュータでは数値も文字列も真理値も演算することができます。

人間にはtrue && falsefalseに見えますが、コンピュータは1 and 00というように見えています。1 plus 11 and 11 + 11 && 1。同じ計算を指していても、コンピュータと人間の間で記号が違うだけなのです。

ここまで読んでくださった方は、これから条件式を記述するために&&||!と打ち込むときに、「今自分は論理の計算をしているのだ」と意識することになるでしょう。それが演算に対する悟りです。

(論理演算について詳しく知りたい方は「あなたは論理演算がわかりますか?」を一読してみてください)

次なる悟りへ

ここまで読んだあなたは、本稿を読む前に比べてプログラミングに対する、またコンピュータに対する見方が変化したのではないでしょうか。 他にも再帰関数やマクロ、コンスセル(リスト)というデータの格納方法など、LISPにはプログラミングの見方を変えてくれる要素がたくさんあります。

この文章を通して、データとは何か、functionとは何か、演算とは何か、今まで見てきたものが、全く違う見方になれば幸いです。

あなた自身のさらなる悟りを得るために、ぜひLISPに入門してみてください。

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