【決定版】中級猫でもわかる正規表現再入門

正規表現

初めまして!技術部サーバーチームのダリエンと申します。正規表現は役に立つ知識なので、勉強してみました。
では、正規表現について調べた知識をシェアしたいと思います。

こちらは Tech Kayac Advent Calendar 2017 の25日目の記事になります。

イントロダクション

定義

  • 検索パターンを象る文字列
  • 文字列の集合を一つの文字列で表現する(ja.wikipedia.org)
  • 英語:Regular Expression(s) / Regex

    何のために使いますか?

  • ウェブブラウザーエクステンションやIDEやコードなどで:
    • 文字をそのまま検索 (⌘/Ctrl+Fと全く同じ)
    • パターンの一致

      ⌘/Ctrl+Fは簡単でしょう?

      そうです。しかし、パータン一致は? 下のテキストを検討してください:

JPY152
JPY40501
USD501
IDR1261
JPY999

上のテキストで、3桁JPYしか絞り込みたくない時、⌘/Ctrl+Fでできませんね。
正規表現の(?<=JPY)\d{3}$で、152と999を取得することができます。

さらに、プログラムの中に⌘/Ctrl+Fを実行することができませんね。正規表現ならできます。

結局、正規表現を使えるようになるためにはこのようなパターンがわからないといけない: ^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$ 難しそうですが、できたら本当に役に立ちますから、勉強甲斐があると思います!
例えば、上のパターンで、どんな浮動小数点数でも検索できます。便利ですね。 なので、正規表現を勉強しましょう!

量指定子

定義

正規表現と文字列が合致しないといけないという個数。

種類

  • {N,N} : 範囲の量指定子 (range quantifier)
    例:a{2,4} ~ aaとかaaaとかaaaaとか
  • * : 0以上の量指定子
    例:(ab)c* ~ ab, abc, abcc, abccc, …
  • + :1以上の量指定子
    例:(ab)c+ ~ abc, abcc, abccc, …
  • ? : 0または1の量指定子
    例:(ab)c? ~ abまたはabc

    注意:量指定子は、量指定子の演算子の左の「文字一個・グループ一つ」だけに影響があります。

量指定子の大切な概念:最長一致

一般的に、量指定子は最長一致という挙動を持っています。それで、できるだけ一番多い結果を戻してみます。
例:abcccccc は、abc* で合致されると、abccccccを戻します。(ab,abc, abcc, abccc, … じゃありません)。
この案件に、*と言う量指定子は 0 から6まで ’c’ を戻せるんですが、最長一致に処理しますから、一番多い結果(6)を戻します。

最短一致

できるだけ一番少ない結果を戻してみます。
例:abcccccc は、abc*? で合致されると、abを戻します。(abc, abcc, abccc, …, abcccccc じゃありません)。
この案件に、*?と言う量指定子は 0 から6まで ’c’ を戻せるんですが、ものぐさ的に処理しますから、一番少ない結果(0)を戻します。

最短一致を合致しみる量指定子シンタックス:
{パターン量指定子}? 例:abc*?abc+?abc??abc{2,5}?

グループ

後方参照グループ (Capture Group)

括弧に入れられる正規表現は後方参照グループと言われます。

後方参照グループは二つのことに使います: - 綺麗な正規表現を書けるように(色々なパターンがグループに分けられた正規表現は見やすい) - backreferences(後方参照)で呼ばれるようになります。

例:(Tarzan*)がある場合は、\1Tarzan*という意味があります。 解説:(Tarzan*)は後方参照グループ、\1 は後方参照です。 (Tarzan*)は第一のグループなので、そのグループの参照のために、\1で書きます。

後方参照 (Backreference)

後方参照グループを参照するため正規表現です。
シンタックス:\{集合グループ数}\1, \2, \3, …)
例:HelloBelleHelloBelleImGaston は、適当な正規表現で表現:(Hello)(Belle)\1\2ImGastonです。
解説:Helloは第一の集合グループ、Belleは第二の集合グループなので、\1Helloを参照して、\2Belleを参照します。

名前をつけるグループ (Named Capturing Group)

名前をつけるグループは数で参照しなくて、名前で参照します。それで、グループは名前をつけなければなりません。
RubyやPerl (SublimeTextの正規表現エンジン)でこのシンタックスを使うことができます: グループ:(?<名前>パターン)
参照:\k<名前>
使い方は普通の集合グループと全く同じです。

非後方参照グループ (Non-Capturing Group)

普通のグループですが、参照されることができないというグループ。
例:(?:Tarzan*)はどこにいるか\1 → エラー
解説:(?:Tarzan*)は後方参照グループじゃないので、第一集合グループはまだ存在していません。それで、\1は存在していない 後方参照グループを参照してみますから、エラーになります。
例:(?:Tarzan*)(Belle)がある場合は、どこかに\1があったら、その\1Belleを参照します。
解説:(?:Tarzan*)は 非後方参照グループなので、(Belle)は第一 後方参照グループです。 

アトミックグループの導入:バックトラッキングの概念 (Backtracking)

正規表現は、パターンを合致する処理にバックトラキンッグという処理を使います。
バックトラッキングとは:失敗された合致する処理の対応のため挙動です。

量指定子と関係がある大切な概念。

量指定子があるregexが失敗した時、その量指定子で合致した文字は一個除いて、全ての合致する処理を繰り返します。

最短一致量指定子の場合は、一個除かなくて、一個追加します。

例:abccddab(.+)ddで検索されるとします。

  1. regexのabは文字列のabを合致しました。
  2. regexの.は最長一致にccddを合致しました。
  3. 全ての文字列の文字が合致されたので、regexに残るddはなにも合致できないので、regexは失敗でした。
  4. バックトラック:.は全ての残っている文字を合致するのはなくて、一個少ない文字を一致します(ccddからccdになります)。
(1)&(2)を繰り返した後、文字列にまだ合致しいなかった文字は:d
  5. regexに残るddは文字列の残っている文字を合致しみて、regexの第一のdを合致したけど、最後のdがまだ合致しませんでした。まだ失敗でした。
  6. バックトラック:. はもう一回一個少ない文字を一致します(ccdからccになった)
(1)&(2)を繰り返した後:文字列にまだ合致しいない文字は:dd
  7. regexに残るddは文字列の残っている文字を合致しみます。
今回、全ての文字列に残っている文字(dd)が合致したので、regexは成功でした(abccddを返します)。

独占的量指定子 (Possessive Quantifier)

バックトラッキングじゃない量指定子です。
シンタックス:{パターン量指定子}+ー 例:abc*+abc++abc?+abc{2,5}+

アトミックグループ (Atomic Group)

普通な後方参照グループと同様ですが、 Atomicグループを使うと、失敗する時にBacktrackをしません。つまり、合致する処理の時にグループが固めます。
シンタックス:(?>{パータン}) 例:
バックトラッキングの例の復習:abccddは正規表現で検索されるとしますが、 ab(.+)ddで検索される代わりに、ab(>.+)ddで検索されるとします。
1. regexのabは文字列のabを合致しました。
2. regexの.は最長一致にccddを合致しました。
3. 全ての文字列の文字が合致されたので、regexに残るddはなにも合致できないので、regexは失敗でした。
4. (>.+)はアトミックグループなので、正規表現の.の部分がバックトラッキングされません。ですから、合致処理は完了です。regexは失敗でした。

条件 (Conditional)

If...elseのような挙動を持っている正規表現です。
シンタックス:(?(A)B|C)
基本的には、Aは後方参照。Aが参照されるグループが合致した場合は、Bに続きます。合致しなかった場合は、Cに続きます。
例:

FOObar
FOObaz
foobaz

は、(FOO)?(?(1)bar|baz)で検索される場合は、戻した文字列は:FOObarbazです。
解説:
第一の文字列 (FOObar) の合致する処理:FOOが合致したので、barに続けました。それで、FOObarを戻します。
第一の文字列 (FOObaz) の合致する処理:FOOが合致したので、bazに続くのは間違えます。それで、なにも戻しませんでした。
第一の文字列 (foobaz) の合致する処理:FOOは合致しなかったので、bazに続けました。それで、bazを戻します。

オア (OR)

またはのロジック。(a|b)の意味は、aとかbとかもOKです。だけど、両方abのはダメです。
例:

gray
grey
graey

は:gr(a|e)yで合致する場合は:graygreyを戻します。

特別な文字

下述文字は特別な意味を持っています。ですから、regexに入れると文字通りに扱われません。
\d : 一桁の数字
\w : 文字(アルファベット、数字、Underscore)
\s : ホワイトスペース (スペース、横/縦タブ、新しいライン、キャリアッジ・リターン、等)
. (dot) : \n以外、任意の文字
\t : 横タブ
\h : スペース
\v : 縦タブ
\r : キャリアッジ・リターン
\n : 新しいライン(Windows以外、キャリアッジ・リターンも含めています)
普通な . (dot)を使いたい時、エスケープ(\.)が必須です。
\d, \w, \sの否定:\D, \W, \S
例:
\Dの意味は数字以外(アルファベットとか、ホワイトスペースとか)
\Wの意味はアルファベット以外(ホワイトスペースとか)

文字クラス (Character Class)

定義

いくつかある文字から一つ文字しか合わせない条件を作りたい時、文字クラスを使います。
シンタックス:[文字]
例:gr[ae]ygraygreyが戻せます(graeyは戻さない)

メタ文字 (Meta Character)

文字クラスの中では、], \, ^, - 以外は文字通りに扱われます。それで、後方参照グループとか量指定子とかは意味がなくなります。 [ , ], -, \ とかはメタ文字と呼ばれます。これらをクラスに入れたい時、エスケープしないといけません(\[, \], \-, \\ になります)。

メタ文字:^(キャレット ー Caret)

^ は、[]の中に意味が切り替えます。[^{文字}]の意味は:文字の否定。
例:
gr[^ae]ygraygreyが戻せない(aとeが断れたので)。ですが、他の文字が戻せるようになります。grby, grcy, …
^ は文字通りに扱われたい時に、後ろに置かないといけません。例:gr[ae^]y

メタ文字:-(ハイフン ー Hyphen)

-は範囲の意味を持っています。
例:[A-Za-z0-9]の意味は A から Z まで、 a から z まで、0 から 9までです。それで、abcdefghijklmnopqrstuvwxyz0123456789を書くのは必要じゃなくなります。  

ハイフンで文字クラスの減算も実装できます。
文字クラスの減算:[{クラス}-[{減算}]]
例:[a-z-[aiueo]] = [b-df-hj-np-tv-z] = 子音を戻します。

量指定子で文字クラスを繰り返す

[…]+は、合致した文字を繰り返さなくて、すべての字クラスを繰り返す。それで、[0-9]+は繰り返す数しか表示されなくて、なんでも数字が表示されます。

  • 解決は:([…])\1+
  • 例:
    [0-9]{5}の意味は:[0-9][0-9][0-9][0-9][0-9]
    ([0-9])\1{4}の意味は:
    もし、[0-9]の結果は5だったら、regexは(5)\1{4}になりますから、regexの意味は55555です。
  • 解説:文字クラスの結果はグループに入れて、グループの中は \1で参照されて、最後はその後方参照が+ で繰り返します。

アンカーと境界

アンカー:定義

regexエンジンの位置をアサートするのに使います・regexのポインタの現在の位置はどこにあるかをチェックします。
アンカーでチェックできる位置: - 文字列の冒頭:^, \A - 文字列の終了:$, \z

さらに、(?m)という修飾語がある場合は、^$の意味の変更があります: - ラインの冒頭:^ - ラインの終了:$

アンカーの種類

  • ^(キャレット):文字列の冒頭アンカー (?m)を使うと、ライン(文字列じゃない)の冒頭になる
  • $ :文字列の終わりアンカー *(?m)を使うと、ライン(文字列じゃない)の終了になる
  • \A:文字列の開始。 ^との違い:\A(?m)に、(?m)を使うと意味が変えられない(^が変わります)
    例: Apple\nApricot\AAを使うとAppleしか戻しません(Apricotは新しい文字列じゃない)
    (?m)^A を使うとAppleApricotを戻す(Apricotは別のラインにあるので)  
  • \z$と同様ですがラインの終わりじゃなくて、文字列の終わり(\Aと^の関係と同じ)  

境界

\b:ワード境界
\bの前とか後とか、ASCII/Unicode文字とか数字とかunderscoreとかあるかどうかをチェックします - 合致する条件は文字がありません。
\bの否定:\Bです。
例:
\bcat\b

  • bobcatを合致する場合:なし(catの前bがあります)
  • catfishを合致する場合:なし(catの後fがあります)
  • his cat eatsを合致する場合:catを戻します(catの前と後は文字じゃなくて、ホワイトスペースです)

修飾語

定義・種類

Regexの挙動を変えるのに使います。

種類

  • (?i) - 大文字と小文字の区別じゃないのに使います
  • (?s) - DOTALLモード (.\r\nも合致します)
  • (?m) - 複数行モード (^$の挙動の変更に使います)
  • (?x) - FreeSpacingモード (Regexを読みやすいために、regexを書く時スペースと新しいラインが使えます)

    修飾語の書き方

  • 複数修飾語は一緒に含めていることができます。
    例:(?ismx)

  • どこにも書けます。修飾語の効果は修飾語の後に開始です。
    例:

caseSensitive(?ix)whOcAreSaBoutCases
asLonG
asImAlive

説明: (?ix)の前に、文字大小はチェックして、すべてのregexは同じラインにかかないといけないんですが、(?ix)の後には文字大小を無視して、複数行regexも書けます。

  • 修飾語の効果を制止したい時、(?-{文字})を打ちます。
    例:
    caseSensitive(?i)whOcAreSaBoutCases(?-i)nowicare

エスケープ文字

説明

特別な意味がある文字は普通な文字としたい時、その文字をエスケープするのが必要です。
例:. を打つ時、普通な(dot)じゃなくて、「\n以外、任意の文字」という意味が持っていますから、普通な(dot)としたい時、エスケープが必要です。

シンタックス:

  • \{特別な文字}
    例:\. ; \\ ; \+ ; \* ; \? など
  • 文のエスケープシーケンス:\Q{エスケープされたい文}\E
    例:\Q 3*4+5 = 6 \E → 量指定子に扱われなくて、普通の数学演算子に扱われます。
    上記のシンタックスを使うと、全て文字は文字通りに扱われます。

先読み・後読み言明 (LOOKAROUNDS)

定義

「先読み・後読み言明」は、アンカーと境界とそっくりですが、「先読み・後読み言明」はregexポインターの位置を一致する代わりに、文字を一致してみます。
例:
^文字は、文字に「ライン・文字列の開始」を一致してみる
(?<={パターン}){文字}は、{文字}に「パターン」を一致してみる

幅0のアサーション・Zero-Length Assertion

アンカーと境界と同じですが、「先読み・後読み言明」にはっきり見えます。

Zero-Length Assertion:

Regexは文字を合致してみるが、合致する場合は、合致した文字を集合しなくて、放置されます。
普通の処理は、Regexは文字を合致した後、regexのポインタは次の文字列の文字に進みますが、アンカーと「先読み・後読み言明」には、合致した後、ポインタが進みません。それで、次の処理はまだその文字を合致してみます。
例:
q(?=u)it ー quitを合致しない。
解説:
qの後ろはuということは正しいですから、後読みは成功でした。
ですが、regexのポインタが進まないので、次の処理はregexのiが文字列のuを合致してみます。合致しないので、regexが失敗です。quitを戻したい場合は、正しいパターンは q(?=u)uitです。

種類

基本的には、

  • 肯定先読みアサーション : 文字(?=パターン)
    文字の後ろ、パターンを合致してみる。合致する場合は正しい

  • 否定先読みアサーション : 文字(?!パターン)
    文字の後ろ、パターンを合致してみる。合致しない場合は正しい

  • 肯定後読みアサーション : (?<=パターン)文字
    文字の前、パターンを合致してみる。合致する場合は正しい

  • 否定後読みアサーション: (?<!パターン)文字
    文字の前、パターンを合致してみる。合致しない場合は正しい

下のテキストを検討してください:

123JPY #(1)
4567JPY #(2)
890IDR #(3)

\d{3}(?=JPY)で合致する場合は、(1)の123と (2)の567を戻します。 \d+(?!JPY)で合致する場合は、(1)の12、(2)の456、と (3)の890を戻します。

\d{3}(?=JPY)の説明:

  • 123JPY -> 123
    1. \d{3}123を合致します。
    2. 123の後ろにJPYがあるかどうか(?=JPY)で検証します。123の後ろにJPYが実際にあるので、検証は成功します。
    3. ですが、後読みは合致された文字列を集合しませんから、JPYが集合されません。
    4. それで、合致の結果は以前に集合した123です。
  • 4567JPY -> 567
    1. \d{3}456を合致します。
    2. 456の後ろにJPYがあるかどうか(?=JPY)で検証します。456の後ろにJPYがないので、検証は失敗します。それで、合致処理が失敗します。
    3. バックトラッキングで、\d{3}の合致を繰り返します。
      1. 今回、456を合致しなくて、567を合致します。
      2. 567の後ろにJPYがあるかどうか(?=JPY)で検証します。567の後ろにJPYがあるので、検証は成功します。
      3. ですが、後読みは合致された文字列を集合しませんから、JPYが集合されません。
    4. それで、合致の結果は以前に集合した567です。
      2番目の例に567を戻したくない場合は:
    5. \d{3}(?=JPY)の冒頭に ^を追加します。
    6. 文の中に検索する場合は、\d{3}(?=JPY)の前後に\bを追加します。

\d+(?!JPY)の説明:

  • 890IDR -> 890
    1. \d+890を合致します。
    2. 890の後ろにJPYがないかどうか(?!JPY)で検証します。890の後ろにJPYがないので、検証は成功します。
    3. ですが、後読みは合致された文字列を集合しませんから、JPYが集合されません。
    4. それで、合致の結果は以前に集合した890です。
  • 4567JPY -> 456
    1. \d+4567を合致します。
    2. 4567の後ろにJPYがないかどうか(?!JPY)で検証します。4567の後ろにJPYがあるので、検証は失敗します。それで、合致処理が失敗します。
    3. バックトラッキングで、\d+の合致を繰り返します。
      1. 今回、4567を合致しなくて、456を合致します。
      2. 456の後ろにJPYがあるかどうか(?!JPY)で検証します。456の後ろにJPYがないので、検証は成功します。
      3. ですが、後読みは合致された文字列を集合しませんから、7JPYが集合されません。
    4. それで、合致の結果は以前に集合した456です。
  • 123JPY -> 上と全く同じ解説で、12を戻します。

私の期待は\d+(?!JPY)で合致すると、4567JPY123JPYから何も戻したくないんですが、期待がミスしてしまいました。
このような挙動があるので、先読みを使う時、注意しなければなりません。

最後に

正規表現を勉強する前に、大きな一覧に多い項目の編集が必要の時に、パターンがわかるのに、正規表現がわからないので一個ずつ編集しなければなりません。それはすごく面倒でした。
正規表現の基本的な特性を生かせるようになったので、そのようなタスクは一つコマンドだけでやり遂げることができます。本当に便利です! さらに、正規表現でテキスト検証(正規表現を使わなければすごく難しくなります)を実装することができるようになりした。

12/1から25日間にわたって更新してきた Tech Kayac Advent Calendar 2017 もこれが最後の記事となります。お楽しみいただけましたでしょうか。 来年もまたよろしくお願いします! メリークリスマス!🎄

カヤックでは最新鋭の技術で開発したいエンジニアを募集しています!

中〜大規模なSPAを開発する時に抑えておきたい10のポイント

こんにちは。カヤックのSPAおじさんこと島津です。

今年はReactとVueを使ったSPA開発プロジェクトをいくつか担当してきたので、そこで得た知見の総まとめをしたいと思います。

※ ここでのSPAとはすべてのViewをJavaScriptで書くWebアプリのことを指します。サーバーサイドMVCを主軸にViewの一部をReactやVueで書くこともありますが、今回はそのケースではありません。

1. フレームワーク

数年前とは事情が変わり、 フレームワークを使わないという選択肢は昨今だともう無いでしょう。丸腰のJSでDOMを弄っていた時代に比べると、かなり安定したフロントエンドの開発ができるようになりました。

人気フレームワークの台頭になっている

React + Redux

Vue + Vuex

をこの1年使ってきましたが、書き方は違えどFluxアーキテクチャ・仮想DOM・コンポーネント志向という大枠含め共通点はかなり多いです。

特にVueのv2はReactより後発のため、Reactで面倒だったあれこれが改善されていて、学習コスト・開発コストが大幅に低くなっているという印象です。

その影響もあり、カヤックの新規プロジェクトでSPAを導入する場合は Vue + Vuexを採用することが多くなってきています。

2. 開発環境

特別な事情がなければwebpackやgulp等タスクランナーの設定を書くコストが浮くので、公式が提供しているボイラープレートを使用するのがおすすめです。

Reactの時は、create-react-app

Vueの時は vue-cli

を利用していました。設定不要で

npm installnpm start

だけですぐに開発がスタートできます。

3. クライアントサイドルーティング

これもスクラッチで開発するメリットはほぼないので、公式のルーターを使いましょう。

react-router vue-router

4. CSS

CSSフレームワークを導入する場合

Bootstrapなど多くのCSSフレームワークは一部の挙動がjQueryに依存しているため、そのまま導入すると結構バグりました。CSSフレームワークに限らずですが、jQueryとVirtualDOMはまぜると危険ですね。

React ComponentやVue Componentとして提供されているもの

react-bootstrap

bootstrap-vue

を使うか、JS依存のないフレームワーク

bulma

Picnic CSS

などを利用するのがSPAとの相性を考えると吉です。

ちなみにVueのマテリアルデザインフレームワーク Vuetify を実際のプロジェクトで導入しましたが、コンポーネントも豊富で使い心地良かったです。

Scoped CSS

独自に書くCSSは、コンポーネント単位でのCSSを書けるScoped CSSを導入するのがおすすめです。グローバルスコープが汚れず衝突の心配もなく、末永く安心してCSSが書けます。

Reactでは、 (react-css-modules)https://github.com/gajus/babel-plugin-react-css-modules を使っていました。

Vueはデフォルトで <style scoped=""> 〜 </style> の中に書くだけでOKなので、Reactと比較して超楽ちんになったと感じた機能の一つです。

5. ユーザー認証

APIとクライアントが疎結合なため、以下のような認証フローが必要になります。

image

表示するビューの制御については、 認証前後のコンポーネントを切り替えます。 Reactの便宜で簡単に示すと以下のようなものです。

<App>
  <Signin /> // → 未ログイン状態のとき表示
  <Mypage /> // → ログイン済みのとき表示
</App>

実装の詳細はrouterの公式マニュアルに記載されている方法が参考になります。

react-router

https://reacttraining.com/react-router/web/example/auth-workflow

vue-router

https://router.vuejs.org/ja/advanced/meta.html

6. 環境変数

APIのルートURLなど、開発環境と本番環境で変えたいオプションは必ず出てきます。

以下のbabel pluginを使うことで、Node.jsではおなじみの process.env.HOGEHOGE が クライアントサイドでも参照できるようになります。

babel-plugin-transform-inline-environment-variables

※ 実際はprocess.envに環境変数が入るわけではなく

ソース

if (process.env.NODE_ENV === 'development') {
  // 開発環境のみ実行するコード
}

NODE_ENV=development npm run build でビルド後

if ('development' === 'development') {
  // 開発環境のみ実行するコード
}

のように変換される仕組みになっています。 これにより、envがすべてクライアントサイドに丸見えになってしまうという危険性はなく安心して使えます。

7. ディレクトリ構成とFluxアーキテクチャ

Reduxも、Vuexも堅牢なFluxアーキテクチャを提供してくれるフレームワークではありますが、ディレクトリ構成が決められているわけではありません。規模に応じて柔軟に構成を変えられるメリットがあるのですが、設計に迷ってしまう部分でもあります。

規模が大きくなる場合、関心事を適度に分離するよう "モジュール" という概念でディレクトリを括るというのを実践してきましたが、これは導入して正解だと思ったので紹介します。

React + Reduxでは、Ducksと呼ばれる構成方針があり、導入していました。 簡単にいうと、reducer, actionを一つのファイルにまとめるというものです。

参考記事: Mediumのブログ https://medium.freecodecamp.org/scaling-your-redux-app-with-ducks-6115955638be

Vuexにはデフォルトでmoduleという機能がデフォルトで備わっているので、 こちらを使うと良いでしょう。 https://vuex.vuejs.org/ja/modules.html

8. ビルド&デプロイシステム

本番運用するためには、デプロイとビルドは同時に自動実行するよう仕組みを作っておくべきです。

ビルド後に生成するファイルは以下のようなものが一般的だと思います。(webpack利用の場合)

public/
 index.html → ルートのHTML
 script.js → ビルドしたJS(CSSも含む)
 static/**/* → 画像など

上記のファイルを、npm run build 等のコマンド出力できるようにしておきます。

自動化には

  • CircleCIやTravisCIなどgithubと連動したCIサービスを使う
  • gitのwebhook + ビルドとデプロイを実行するコマンドAPIを用意する

等の方法があります。 CIサービスを使ったほうが、コンテナを使ったビルドを行っていて 冪等性が確保されいるのでオススメです。

CircleCIからAWS S3へコードをビルド & デプロイするサンプルはこちら jshimazu/circlci-s3deploy-example

9. サーバーサイドレンダリング (※ 以下SSR)

SEOが必要なアプリに関してはこの対応が必須になってきます。 簡単に解決できるベストプラクティスはあまりなく、力技を要します。

選択肢としては、以下の3つになります。

  1. 何もしない
  2. サーバーサイドアプリレイヤーでSSR処理を実装する
  3. ヘッドレスブラウザを利用したPrerendering

1. 何もしない

というのは実は怠惰ではなく、Googleのクローラはここ数年で進化してきているので、ある程度JSでレンダリングしたものをインデックスしてくれています。(実際にインデックスされているのを確認しました)ただ、レンダリングするタイミングよってはコンテンツがインデックスされなかったり、100%の精度とはまだ言えないようなので、確実なSEOを求める場合は、SSRを検討した方が良いです。

また、Google以外のクローラの多くはSSRの状態しか読み取らないので、 シェア画像をURLごとに最適化したいケースなどは、この方法は使えないでしょう。

2. サーバーサイドアプリレイヤーでSSR処理を実装する

Isomophic (Universal) Javascriptと呼ばれるものです。 expressなどでクライアント側で利用しているコンポーネントをサーバーサイドレンダリングする仕組みを作ります。クライアント固有のオブジェクト(例えばwindow)などは利用できないので、そのあたりを最適化する必要があります。レンダリング前にAPIから必要なデータを取得して表示するというのも、コツコツ実装していきます。

実装していて冗長感が否めないところですが、 フレームワーク・言語問わず「どこまでクライアントサイドとファイルを共有できるか」というのがポイントとなります。 実装言語はJSに限らず、各言語でSSRをサポートしてくれるライブラリがあり、Ruby on Railsを利用していた時には reactjs/react-rails などがありました。

いまから新規開発するなら SSRのためのライブラリも成長してきているので、

Reactなら Next.js

Vueなら Nuxt.js

を導入してみたいところです。

3. ヘッドレスブラウザを利用したPrerendering

ヘッドレスブラウザはレイテンシが大きく、キャッシュして返す仕組みが必須なため、スクラッチで作ると結構大掛かりです。メリットとしては、2のSSRのように冗長な実装が不要なところです。

(prerender.io)https://prerender.io/ のようなPrerenderを代行してくれるSAASもあるので、予算との兼ね合いがありますが利用するのも実装コストを抑える手段の一つとしてありそうです。

10. サーバー構成

4のSSRが必要無い場合は性能限界の心配がない

CDN + ファイルストレージ

がおすすめです。

(AWSだと、Cloudfront + S3)

この場合、どのURLでアクセスしてもindex.htmlをレンダリングするような設定が必要です。 AWS Cloudfrontを利用していたので、Custom Error Pageの仕組みを使って ファイルが存在しなかった場合index.htmlを返すよう設定していました。

参考: Cloudfront Custom Error ページの設定 http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/custom-error-pages.html#custom-error-pages-cache-behavior

image

SSRが必要な場合は、以下のようにAPIとは別のアプリケーションサーバーが必要になります。

image

まとめ

半年前くらいまではReactおじさんと名乗っていましたが、 今はすっかりVue使いとなっています。

そして数年後にはVueでもReactでもないフレームワークが現れてもおかしくない状況ですよね。

SPAはWebをより快適にするために必要な技術なので きっと今後も破壊的な進化を続けていくでしょう。

そんな時代を生き抜くためには、フレームワークの栄枯盛衰に一喜一憂せず、 本質的なところを抑えておくことが大切だと思っています。

  • SPA環境構築
  • Fluxアーキテクチャ
  • コンポーネント志向

この辺りは必ず今後も生き残っていくものになるはず。

ということで、この記事が誰かの今後のSPA開発の参考になれば幸いです。

KayacではSPAをガシガシ開発してみたいエンジニアを募集しています。