【Riot.js v3対応】Riot.js x Firebase で超お手軽にWebアプリを作ってみる

  • 61
    いいね
  • 0
    コメント

まえがき

この記事でできること

最近評判のreact風超軽量ライブラリ「Riot.js」と、Googleが買収したMbaasサービス「Firebase」。
この2つのイケてるサービスを使って、超お手軽にWebアプリを作ってみるチュートリアルです。

RiotとFirebaseを組み合わせてる例があまりなかったので試してみました。
この記事ではユーザー登録/認証まわりを実装しています。

またデザインには、Bootstrapよりモダンでイケてると噂のCSSフレームワーク「SemanticUI」を使っています(※当社調べ)。
SemanticUIももっと流行ってほしい。

*** 2016年12月16日追記 ***
Riot.jsのv3がリリースされたので、現時点の最新版v3.0.4対応に書き換えました。
v2からけっこう色々変わっていますのでご注意ください。
*** 追記おわり ***

完成版イメージ

こんな画面になります。トップページとログイン/新規登録だけのシンプル仕様。

top.png

なんでこの構成にしたの?

  • Rails&HerokuでよくWebアプリ作ってたけど、Herokuの無料枠が減ってそろそろしんどい
  • Firebaseが超絶便利そうで、無料枠もそれなりに大きいので使ってみたい
  • Hostingも考えるとjsでアプリ作るのがよさそうだけど、フレームワーク戦争に巻き込まれるのは避けたい
  • riot.jsが評判よくて学習コストも低そうなので、手を出してみても後悔しないですみそう
  • しかしRiotxFirebaseの例があまりないので、自分で試してみる(←いまここ)

環境

  • Riot.js v3.0.4
  • Firebase v3.5.0
  • SemanticUI v2.2.4

SemanticUIでページを用意する

まずはベースになるページを、SemanticUIを使ってコーディングしておきます。まだ完全に静的。

今回用意するのは、以下の2画面だけです。

  • ホーム画面(index.html)
  • ログイン/新規登録画面(auth.html)

ホーム画面(index.html)

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>Riot x Firebase x SemanticUI example.</title>
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.3/semantic.min.css">
  </head>
  <body>
        <!-- Header -->
    <div class="ui grid">
        <div class="ui fixed inverted borderless menu">
          <a href="#" class="header item">
            <h4>
              <i class="anchor teal icon"></i>
              Riot Example
            </h4>
          </a>
          <div class="right menu">
            <div class="item">
                <a href="#signin" class="ui button pink" >Login</a>
            </div>
          </div>
        </div>
    </div>

        <!-- Content -->
    <div class="ui fluid very padded basic container segment" style="margin-top:70px;max-width:500px!important;">
      <div class="ui center aligned very padded basic segment">
        <h1 class="ui icon header">
          <i class="anchor teal icon"></i>
          <div class="content">
            Hello world!
          </div>
        </h1>
        <div class="sub header">
          Riot x Firebase x SemanticUI example.
        </div>
      </div>
    </div>

        <!-- Footer -->
    <div class="ui inverted attached footer center aligned very padded segment">
        <a href="#" class="ui inverted header">
          <h4>
            <i class="anchor teal icon"></i>
            Riot Example
          </h4>
        </a>
        <div class="ui horizontal inverted small divided link list">
          <a class="item" href="#">運営</a>
          <a class="item" href="#">利用規約</a>
          <a class="item" href="#">プライバシーポリシー</a>
          <a class="item" href="#">お問い合わせ</a>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.4/semantic.min.js"></script>
  </body>
</html>

自分でCSSを書いてるのは、containerに直書きしてる部分だけです。
あとは用意されてるclassを付与するだけでいい感じにスタイルが適用されます。いい感じ。

footerのリンクはただの雰囲気です。

ログイン/新規登録画面(auth.html)

ログインと新規登録の画面は、同じHTMLを使用して中で表示を切り替えます。
(この時点では固定でログイン用の表示になっています)

このあとriot.jsを導入するときに、index.htmlのContent部だけ切り替えるように実装するので、
ここではContentに使用する部分のみ抜粋して掲載します。

この時点で表示確認したい場合は、index.htmlをコピーしてContent部のみ下記のソースで置き換えてください。
表示確認しない場合は、独立したHTMLファイルを作成する必要はありません。

auth.html
        <!-- Content -->
    <div class="ui middle aligned center aligned grid">
      <div class="column">
        <h2 class="ui header">
            Sign In
        </h2>
        <form class="ui large form">
          <div class="ui segment">
            <div class="field">
              <div class="ui left icon input">
                <i class="user icon"></i>
                <input type="text" name="email" placeholder="E-mail address">
              </div>
            </div>
            <div class="field">
              <div class="ui left icon input">
                <i class="lock icon"></i>
                <input type="password" name="password" placeholder="Password">
              </div>
            </div>
            <div class="ui fluid large teal submit button">
                Login
            </div>
          </div>

          <div class="ui error message"></div>
        </form>

        <div class="ui message">
          <p>New to us? <a href="#signup">Sign Up</a></p>
        </div>
      </div>
    </div>

こんな感じになります。

signin.png

この画面のHTMLは、公式のサンプルを参考にさせてもらいました。

Login Example - Semantic

Riot.js導入

riotlogo.png

導入

ここからいよいよriot.jsを使っていきます。

まずはindex.htmlでriot.jsを読み込みます。
body最下部にscriptの読み込みを追加するだけ。

index.html
  ...
    <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/3.0.4/riot+compiler.min.js"></script>
    <script src="https://cdn.jsdelivr.net/riot-route/3.0.2/route.min.js"></script>

シンプルにするため、コンパイラがセットになったのを使ってブラウザ表示時にコンパイルされるようにします。

【v3対応】

v3からrouterが本体から切り出されて別ライブラリになりました。ので別で読み込んでます。

Router · Riot.js

ファイルの分割

riot.jsではhtmlをコンポーネント単位でtagファイルに分割します。

tagファイル作成

まずはindex.htmlを、コンポーネントごとにtagファイルとして切り出しておきます。

header.tag
<header>
    <div class="ui grid">
        <div class="ui fixed inverted borderless menu">
          <a href="#" class="header item">
            <h4>
              <i class="anchor teal icon"></i>
              Riot Example
            </h4>
          </a>
          <div class="right menu">
            <div class="item">
                <a href="#signin" class="ui button pink" >Login</a>
            </div>
          </div>
        </div>
    </div>
</header>

footerも同様なので省略。

footer.tag
<footer>
  ...
</footer>

content部分はページによって違う内容を表示するので、<content>でなくホーム画面用の<home>として作成します。

home.tag
<home>
  ...
</home>

ログイン/新規登録画面用に、auth.tagも作成しておく。
中身はさきほどauth.htmlとして書いていたものを<auth>タグの中に入れてください。

auth.tag
<auth>
  ...
</auth>

独自タグ化

次はindex.htmlから、各tagファイルをmountするようにしてみます。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    ...
  </head>
  <body>
    <!-- Header -->
    <header></header>

    <!-- Content -->
    <content></content>

    <!-- Footer -->
    <footer></footer>

    <script src="header.tag" type="riot/tag"></script>
    <script src="auth.tag" type="riot/tag"></script>
    <script src="home.tag" type="riot/tag"></script>
    <script src="footer.tag" type="riot/tag"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.4/semantic.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/2.6.4/riot+compiler.min.js"></script>
    <script>
      riot.mount('header')
      riot.mount('content', 'home')
      riot.mount('footer')
    </script>
  </body>
</html>

もともとhtmlを書いていたところを<header>などの独自タグに置き換え、
ページ最下部で、tagファイルの内容を独自タグにmountしています。

contentのみ、<content>タグにhome.tagをmountするため書き方が違うので注意。

ローカルで表示確認

riot入れると、表示確認するのにWebサーバの起動が必要になります。
ここで手間取りたくはないので、このあたりを参考にさくっと動かすのがおすすめです。

お手軽なWebサーバーの立て方 - Qiita

Chromeアプリ便利。

ルーティング実装

つぎはルーティングを実装してみます。今回はこんな感じの設定を目指します。

url title content
/ ホーム home.tag
/#signin ログイン auth.tag
/#signup 新規登録 auth.tag

デフォルトだと上のような#つきURLになります。
route.base('/')とすることで、#なしURLでの設定も可能です。
(が、ローカルでの検証がめんどくさくなるのでここではこのままで)

index.html修正

これを実装するように修正したindex.htmlがこちら。

index.html
    ...
    <script>
        riot.mount('header');
        riot.mount('footer');

        route('/', function() {
          riot.mount('content','home');
        });

        route('/signin', function(){
          riot.mount('content', 'auth', {type: 'signin'});
        });
        route('/signup', function(){
          riot.mount('content', 'auth', {type: 'signup'});
        });

        route.start(true)
    </script>

ページ下部のscriptタグでmountしてたところを、routeを使って書き直しています。
header/footerはどのページでも表示するので、routingの外でmountしています。

"/#signin""/#signup"は同じタグなので、
タグ内で表示を切り替えれるように、3つ目の引数でパラメータを渡しています。

この時点で、ホーム画面のログインボタンを押すと表示が切り替わることが確認できるはずです。

【v3対応】

routerが別ライブラリになり、呼び出し方法も変わっています。
頭のriotが不要になりました。

riot.route(..); //旧
route(..); //新

authタグの表示切替

次はsignin/signupのパラメータに応じて、authタグの表示を切り替えてみます。
mount時に持たせたパラメータに、opts.typeでアクセスしています。

auth.tag
<auth>
    <div class="ui middle aligned center aligned grid">
      <div class="column">
        <h2 class="ui header">
            { opts.type=='signup' ? 'Sign Up' : 'Sign In'}
        </h2>
        <form class="ui large form">
          <div class="ui segment">
            <div class="field">
              <div class="ui left icon input">
                <i class="user icon"></i>
                  <input ref="email" type="text" name="email" placeholder="E-mail address">
              </div>
            </div>
            <div class="field">
              <div class="ui left icon input">
                <i class="lock icon"></i>
                  <input ref="password" type="password" name="password" placeholder="Password">
              </div>
            </div>
            <div class="ui fluid large teal submit button" onclick={ opts.type=='signup' ? signup : signin }>
                { opts.type=='signup' ? 'Register' : 'Login'}
            </div>
          </div>
        </form>

        <div class="ui message">
          <p if={ opts.type=='signup' }>Already user? <a href="#signin">Sign In</a></p>
          <p if={ opts.type=='signin' }>New to us? <a href="#signup">Sign Up</a></p>
        </div>
      </div>
    </div>

    <script>
      signin() {
        //console.log('sign in')
      }
      signup() {
        //console.log('sign up')
      }
    </script>
</auth>

三項演算子や、<div if={}>で表示制御するあたりがriotの文法ですね。シンプルで覚えやすい。
チートシート もあるでよ。

これでログイン/新規登録画面それぞれから、画面下のリンクで行き来できるはず。
ちゃんと表示が切り替わりますね。

signin/signup処理は、firebase導入後に実装します。

Firebase導入

lockup.png

というわけでいよいよ次はFirebaseを入れてみます。

Firebaseはmbaasとして必要な機能を色々提供してくれていますが、
今回はユーザー認証まわりのみ使用します。

導入

Firebaseに登録して新規プロジェクト作成して、とかはこのあたりの記事を参考に準備してください。

Firebaseの始め方 - Qiita

今回はシンプルにメール/パスワード認証を使用するので、ダッシュボードの「Auth」→「ログイン方法」から、「メール/パスワード」を有効化しておきます。

プロジェクトが作れたらindex.html内で初期化設定。

index.html
    ...
    <script src="https://www.gstatic.com/firebasejs/3.5.0/firebase.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.4/semantic.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/2.6.4/riot+compiler.min.js"></script>
    <script>
        //Firebase init
        var config = {
            apiKey: "XXXX",
            authDomain: "XXXX.firebaseapp.com",
            databaseURL: "https://XXXX.firebaseio.com",
            storageBucket: "XXXX.appspot.com",
            messagingSenderId: "XXXX"
        };
        firebase.initializeApp(config);
        ...

scriptタグを1行追加してfirebase.jsを読み込み、configを設定してinitializeしております。
firebaseのダッシュボードで出てくるのをそのまま貼り付けるだけですね。

apiKeyをさらしてるのが不安でしょうがないですが、ダッシュボードでセキュリティ設定をちゃんとすればこれで問題ない、、はず。

ユーザー登録/ログイン

ベーシックなユーザー登録

まずは超シンプルにユーザー登録を実装してみます。

auth.tag
  ...
  <script>
    var that = this

    signup() {
        firebase.auth().createUserWithEmailAndPassword(this.refs.email.value, this.refs.password.value).then(
            function() {
                console.log("signup success!")
            },
            function(error) {
                console.log(error.message)
            }
        )
    }

    signin() {
      //console.log('sign in')
    }
  </script>
</auth>

実際の登録メソッドはFirebaseが用意してくれてるのをそのまま呼び出してるだけです。
パラメータで入力したemail/passwordを渡してます。

これだけで、ちゃんと入力すればユーザー登録できるはず。ダッシュボードでユーザーが追加されてるのを確認してください。

  • 登録ボタンにonclick=signupが設定されてるはずなので、クリックするとこのsignup()が呼ばれます
  • <input ref="email">としておくと、this.refs.email.valueで該当する要素の値にアクセス可能(v3からの文法)。

【v3対応】

以前はthis.emailって書くと、nameやidが"email"の要素を見に行っていたけど、refが導入されたのでより明示的に要素を指定できるようになりました。

validationエラーの表示

Firebaseの登録メソッドを呼び出すと、値のvalidationも自動でしてくれます。

メールのフォーマットになってないとちゃんと弾くし、パスワードもデフォルトで6ケタ以上じゃないと弾くみたいです。
(しかしどこで設定変えれるんだろ)

というわけで次はvalidationエラーが出たときに、いい感じに画面にフィードバックするようにします。

auth.tag
     ...
     <div if={ error_message } class="ui visible error message">{ error_message }</div>
   </form>
   ...
    signup() {
        firebase.auth().createUserWithEmailAndPassword(this.refs.email.value, this.refs.password.value).then(
            function() {
                console.log("signup success!")
            },
            function(error) {
                that.error_message = error.message
                that.update()
            }
        )
    }

html部分に一行追加して、error_messageがあるときだけ表示されるようにします。
あとは失敗したときのcallbackで受け取った文言をそのままerror_messageにセットしてるだけ。

注意点としてはupdate()しないと画面に反映されないとこくらいですかね。

今回はシンプルにFirebaseのエラーメッセージをそのまま出してますが、
実際にはErrorCodeで場合分けして自分でいい感じの文言をセットしてください。

これでエラーが出るとこんな感じ。

validation.png

いい感じですね。

ログイン処理

次はログイン側も実装しますが、メソッドが変わるくらいでほぼ一緒です。

auth.tag
        signin() {
            firebase.auth().signInWithEmailAndPassword(this.refs.email.value, this.refs.password.value).then(
                function() {
                    console.log("signup success!")
                },
                function(error) {
                    that.error_message = error.message
                    that.update()
                }
            )
        }

validationエラー時も同じ仕組みを使いまわし。

ヘッダーにログイン状態を反映する

新規登録/ログインはさくっとできましたが、まだ画面上では何も状態が変わりません。
なので次はヘッダーでログイン状況に応じて表示が変わるようにしてみます。

header.tag
<header>
    <div class="ui grid">
        <div class="ui fixed inverted borderless menu">
          <a href="#" class="header item">
            <h4>
              <i class="anchor teal icon"></i>
              Riot Example
            </h4>
          </a>
          <div class="right menu">
            <div class="header item" if={user}>
                <i class="icon user"></i>
                { user.email }
            </div>
            <div class="item">
                <a if={user} class="ui button" onclick={signout}>Logout</a>
                <a if={!user} class="ui pink button" href='/#signin'>Login</a>
            </div>
          </div>
        </div>
    </div>

    <script>
        var that = this
        firebase.auth().onAuthStateChanged(function(user) {
            that.user = user
            that.update()
        })

      signout() {
        firebase.auth().signOut()
        riot.route('/')
      }
    </script>
</header>

firebase.auth().onAuthStateChangedでログイン状態を監視できるので、その値に応じて表示を切り替えます。
ついでにログアウト処理も実装したので、これでログイン・ログアウトを何回でも試せますね。

ログイン前

not_login.png

ログイン後

signedin.png

かんたん!

リダイレクト処理

あとはログイン/新規登録したらトップページにリダイレクトされるようにします。
ついでにログイン済みの状態で/#signin /#signupに来た時も同様にトップにリダイレクト。

auth.tag
    <script>
        firebase.auth().onAuthStateChanged(function(user) {
            if(user) { riot.route('/') }
        });
        ...

これを追加するだけ。
ログイン状態になったら自動でここでひっかかってリダイレクトされるので、
ログイン/新規登録成功時のコールバックをいじる必要はなさそうです。

しかしfirebaseのobserverをあちこちに書いちゃってるので、これはもっといい方法ありそう。。

読み込み中にLoaderを表示する

これでひととおり認証まわりの実装はできましたが、
firebaseへの呼び出しが返ってくるまでの待ち時間がちょっと気になります。

SemanticUIを使えば簡単にいい感じのLoaderを表示できるので、これを使ってみます。

Loader - Semantic

index.html
  ...
  <body>
    <header></header>
    <div class="ui fluid very padded basic container segment" style="margin-top:70px;max-width:500px!important;">
        <content></content>
    </div>
    <footer></footer>
    <div class="ui dimmer"><div class="ui huge text loader { dimmerState }">Loading</div></div>

bodyの直下に、dimmer(loader)を追加。この状態ではまだ非表示なので、jsで表示を制御します。

auth.tag
        signup() {
            that.dimmerState = 'active'
            firebase.auth().createUserWithEmailAndPassword(this.email.value, this.password.value).then(
                function() {
                    //success
                },
                function(error) {
                    that.error_message = error.message
                    that.update()
                }
            ).then(function(){
                that.dimmerState = ''
            })
        }

新規登録時に、firebaseにリクエストを投げると同時にdimmerのclassにactiveを追加して画面表示。
成功でも失敗でも、結果が返ってきたら再度activeを外して非表示にしています。

小さい修正だけどユーザー体験はかなり改善しますね。
ログインの方も同じように書けばOKです。

応用編としてはobservableなんか使うともっとよいと思います。

Riot.jsでobservableを使って簡易的な通知メッセージを表示する - Qiita

まとめ

というわけでriot.jsとFirebaseを使ったWebアプリの認証まわりを実装してみました。
riotの使い方おぼえるのにちょっと時間かかりましたが、シンプルにできたんじゃないかと思います。

しかしriotもfirebaseもまだまだよくわかってないので、もっといい書き方あるとかフィードバックあれば大歓迎です。

どっちもまだまだドキュメント少なくて辛いけど、かなりイケてると思うのでもっと使う人が増えてくるとうれしいです。

おしまい。