StorybookでReactJSコンポーネントのレビュー環境を作る

f:id:anoChick:20161230123929p:plain

https://getstorybook.io/

StorybookとはWeb開発におけるUIコンポーネントを作る環境のこと。

インストール

まずは導入するReactJSプロジェクトを用意する。

mkdir react-sample
cd react-sample
npm init
npm i --save react react-dom

次にStorybookを導入するためのコマンド getstorybook をいれる。

npm i -g getstorybook
getstorybook
 getstorybook - the simplest way to add a storybook to your project.

 • Detecting project type. ✓
 • Adding storybook support to your "React" app. ✓
 • Preparing to install dependencies. ✓

yarn install v0.17.6
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
✨  Done in 21.14s.

 • Installing dependencies. ✓

To run your storybook, type:

   yarn run storybook

For more information visit: http://getstorybook.io

このようになったら導入成功。 yarnの利用が推奨されてるらしい。

storiesというディレクトリが作れていて、中に

  • Welcome.js
  • Button.js
  • index.js

3つのファイルが生成されている。

WelcomeとButtonはサンプルコンポーネントで,indexはコンポーネントをStorybookにつなぐためのもの。

使う

早速 yarn run storybookで実行してみる。

http://localhost:6006/にStorybookが表示される。 f:id:anoChick:20161230125638p:plain

import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import Button from './Button';
import Welcome from './Welcome';

storiesOf('Welcome', module)
  .add('to Storybook', () => (
    <Welcome showApp={linkTo('Button')}/>
  ));

storiesOf('Button', module)
  .add('with text', () => (
    <Button onClick={action('clicked')}>Hello Button</Button>
  ))
  .add('with some emoji', () => (
    <Button onClick={action('clicked')}>😀 😎 👍 💯</Button>
  ));

storiesOfでストーリーを定義出来る。 コンポーネントの視覚的なテストのようなイメージ。 addでprops等を入れていく。 試しに small というpropertyがつくとボタンが小さくなるようにする。

index.js

.add('with small', () => (
    <Button onClick={action('clicked')} small>Small Button</Button>
  ))

を追加する。

import React from 'react';


export default class Button extends React.Component {

  constructor(props){
    super(props);
    this.styles = {
      border: '1px solid #eee',
      borderRadius: 3,
      backgroundColor: '#FFFFFF',
      cursor: 'pointer',
      fontSize: 15,
      padding: '3px 10px',
      margin: 10,
    };
  }

  render() {
    if(this.props.small){
      this.styles.fontSize = 10;
    }

    return (
      <button
        style={this.styles}
        onClick={this.props.onClick}
      >
        {this.props.children}
      </button>
    );
  }
}



Button.propTypes = {
  children: React.PropTypes.string.isRequired,
  onClick: React.PropTypes.func,
};

Buttonコンポーネントをこんな感じに書き換える。

f:id:anoChick:20161230155419p:plain

リアルタイムで見た目が変わった。

共有する

作成したStorybookプロジェクトはStorybookHUBで共有できる。

Storybook Hub - The Perfect Place To Develop & Review User Interfaces

まずStorybookにコメント機能を付ける。

// To get built in addons.
npm i -D @kadira/storybook-database-cloud
npm i -D @kadira/storybook-addon-comments

↑を実行した後`.storybook/addons.jsを記述する

import '@kadira/storybook/addons';

import '@kadira/storybook-database-cloud/register';
import '@kadira/storybook-addon-comments/register';

コミットしてGitHubにプッシュする。

Storybook Hub - The Perfect Place To Develop & Review User Interfaces StorybookHUBのサイトにAppを登録する。 いまプッシュしたGitHubリポジトリを対象にする。

f:id:anoChick:20161230171741p:plain

自動でビルドされて、StorybookのURLが生成される。 ↓こんな感じ

React Storybook

publicで公開することもできれば、privateでAppを作成して招待するという事もできる。

f:id:anoChick:20161230172150p:plain Storyに対してコメントが出来る。

ちなみにPublicリポジトリの場合は無料で利用できるけれど、 Privateリポジトリはコラボレータを増やすのにお金がかかる。

クローリングアプリを作る Part.1

今年得た知識を集めたらいい感じのクローリングアプリが作れそうな気がしたのでやってみる。

作りたい物

Architect

f:id:anoChick:20161229051026p:plain

こんな感じ。 非技術者でも簡単に利用できるような、 アプリ単体でも稼働するネイティブアプリケーション。

ポイントとしてはサーバとDBはちゃんと分離して、外に出せるようにしたい。 サーバは一般的なWebサーバとして実装しておけば外に出したとき接続先の変更だけで対応出来る。 DBは一工夫要りそう。

とりあえずは単体で完結するようなものを作る。

digdagを使う際に覚えておくべき事メモ

f:id:anoChick:20161223112454p:plain

タスクの状態が以下のいずれかになったときにタスクが開始される。

  • 依存するタスクが無い
  • 依存するタスクが全て完了している

実行のしかたは3つ

ローカル

digdag run piyo.dig

コマンドを叩いて実行

サーバ

digdag server -o digdag-server

サーバーが立つ http://127.0.0.1:65432 で管理画面

on Docker

docker:
  image: ubuntu:14.04

digファイルに↑のように記述されているとdockerで動く

バージョン管理

  • プロジェクトという単位でスクリプトやコンフィグを管理している。
  • リビジョンという単位でバージョンを管理している
    • サーバはアップロードされた過去のリビジョンを保持している

タスクとオペレータ

+piyoとかはタスク

yaki>: tori とかでアクションが実行できる

AWS上にサーバレスな汎用クローラを展開するぞ。

サーバレスな汎用スクレイパーを作った。 - あのにのに

前回はAPIGatewayとLambdaで、指定したURLの指定した位置にあるデータを抜き出すAPIを作った。

今回はサイト内探索をするようなシステムをAWS上に構築しようと思う

注意:クローラは用法用量を守って、相手方のサイトに迷惑がかからないように十分な配慮を徹底しましょう。

今回作るもの

f:id:anoChick:20161211213223p:plain

こんな感じの構成をイメージしてる。

DynamoDBの1レコードがサイトへの1リクエストに常に対応するものとし、内部リンクのURLを新たにDynamoDBのテーブルに追加していく。

得られたデータは対応レコードに格納される。

URLがテーブルに追加されると、DynamoDB Streamsに流され、Lambdaで実行される。

というような繰り返し。

DynamoDBのデータスキーム

1.Request先URL :target_url

例) https://example.com/items/1

2.巡回対象サイトのrootURL :root_url

例) https://example.com/

3.対象データのセレクタ :selector

例) .score

4.対象データのスクレイピング結果 :result

例) 4.2

f:id:anoChick:20161211214901p:plain

プライマリキーをtarget_urlに設定。

f:id:anoChick:20161211215707p:plain

ストリームに流れるのは"新しいイメージ"のみで良いはず。

SERVERLESS FRAMEWORKの用意

SERVERLESS FRAMEWORKが最高すぎる - あのにのに SERVERLESS FRAMEWORKを使う。 DynemoDBへのレコード追加時に、Lambdaに渡されるJSONのサンプルがほしいので一旦デプロイして動かしてみる。

functions:
  spide:
    handler: handler.spide
    events:
      - stream:
          arn: ####
          batchSize: 1
          startingPosition: LATEST

とりあえずデータを取得(スクレイピング

console.log(event);

ってやるとCloudWatchの方に吐き出されるので、そのデータをevent.jsonに入れてローカルで動かしてみる。

'use strict';
var client = require('cheerio-httpcli');

module.exports.spide = (event, context, callback) => {
  var url = event.Records[0].dynamodb.NewImage.target_url['S'];
  var selector = event.Records[0].dynamodb.NewImage.selector['S'];
  client.fetch(url, {}, function(err, $, res) {
    console.log($(selector).text());
  });
};

これで目的のデータは得られた。

結果をDynamoDBに格納

DynamoDBに入れておく。 DynamoDBを操作するために

GitHub - noradaiko/vogels: DynamoDB data mapper for node.js

をいれる。

var vogels = require('vogels'),
  Joi = require('joi');
var BasicSpider = vogels.define('BasicSpider', {
  hashKey: 'target_url',
  timestamps: true,
  schema: {
    target_url: Joi.string(),
    root_url: Joi.string(),
    selector: Joi.string(),
    result: Joi.string(),
  },
  tableName: 'BasicSpider'
});
BasicSpider.update({
  target_url: url,
  result: $(selector).text()
}, function(err, acc) {
  console.log('update account', acc);
});

でresultが追加されるはず。 ただ、今の状態だとUPDATEでもリクエストが飛んでしまうため、レコード追加時のみスクレイピング処理が走るようにする。

  if (!newImage.target_url || !newImage.selector || !newImage.root_url ||
    newImage.result || newImage.updatedAt)
    return;

一旦デプロイして動かしてみる。

f:id:anoChick:20161211233459p:plain

レコードを追加してちょっと待つと...

f:id:anoChick:20161211233520p:plain

入った!!!

DynamoDBに対象URLと対象要素のセレクタを入れると、resultにデータを保存する仕組み

が出来たことにななる。

これはこれでなんか良さそう。

内部リンクを巡る

過去に訪問したページ → UPDATE処理になる為実行されない。(無限ループはない)

対象データが存在しないページ → resultのないレコードとして残る。

とりあえず内部リンクを拾うって訪問したことのないページをレコード追加する。

$('a').each(function(i, e) {
  var link = ($(e).attr('href') || '').replace('rootURL','');
  if (link[0] == '/') {
    var target_url = rootURL + link;
    BasicSpider
      .query(
        target_url)
      .exec(function(err, acc) {

        if (acc.Count) return;

        BasicSpider.create({
          target_url: target_url,
          root_url: rootURL,
          selector: selector
        }, function(err, post) {
          console.log("create:" + target_url);
        });
      });
  }
});

後は諸々調整、timeoutとかmemorysizeとか。

実行

f:id:anoChick:20161212015117p:plain

取れてる。

ただ並列数がどうなるのかが気になる。 あまり同時に叩きすぎると対象サイトに迷惑がかかるのでコントロールしたい。

つづく

React.js + firebaseでリアルタイムチャットアプリ作った。

f:id:anoChick:20161211183939p:plain

https://nekojima.anochick.com

でーきた。 特徴としては↓

  • リアルタイムチャット(勝手に更新が反映される)
  • チャンネルの作成が自由にできる。
  • 無限にふぁぼれる
  • 完全匿名

です。

f:id:anoChick:20161211190537g:plain

ふぁぼり放題です。

作り方の解説

anoninoni.hateblo.jp

こっちでも雑に書いてはいたんですが、もうちょっと丁寧に記述します。

react-boilerplateを使う。

github.com 開発環境を整えるのが大変なので、ボイラープレートを使いました。 詳細はリンク先みていただくとわかるかと思うんですが、

  • React.js
  • Redux
  • ES6
  • webpack
  • ServiceWorker
  • Mocha
  • enzyme
  • eslint
  • CLI

などなど、モダンなフロントエンド開発に使うものがいっぱい入ってます。

ただ、Angularと比べると非公式なものなので、結構バギー。 私の場合webpackのcss生成の記述がおかしくなってて手動で対応したりしてました。 あとeslintがめちゃめちゃ厳しいです... サンプルプログラムの時点で結構eslint:disabledの記述があったので、付き合い方が難しそう。

Material UIを使う

www.material-ui.com

UIframeworkをなににしようか悩んだんですが、やっぱり王道のMaterial-UIにしました。理由としては

  • バイルフレンドリーに書きやすい
  • 今回作るものがシンプル
  • よく使われているから安心

という感じです。

グリッドシステムも特に使わず、できるだけ純粋なMaterial-UIで書きました。

firebaseのリアルタイムDBを使う(reactfire)

これを使ってみたくてこのチャットを作り始めたみたいなところあります。 "データベース"のくせにリアルタイム更新と、プッシュ機能も持ってるのですごいです。

チャンネル内の情報なんかは

const ref = firebase.database().ref('channels/default');
this.bindAsArray(ref, 'channel');

と記述するだけで、そのコンポーネントのstate.channelにバインドされます。 バインドされた状態のstateにsetState()で変更を加えるだけで、DBに更新が走り、 バインドしている全クライアントに対してプッシュされます。 すごい!

普通にjavascriptで使う場合は

  firebase.database().ref('channels/default').on('eventname', function(snapshot) {
    var mes = $('<span>',{class:'message'}).text(snapshot.val().message);
    $('#message-box').append(mes);
  });

のような記述で動きます。

ちなみに、このリアルタイムDBはユーザ認証を用いたパーミッション機能があるのですが、 今回は認証なしで動いているため、誰でも自由に書き換えができます

一応サニタイズは行われるぽいのですが、バリデーションのかけようがないのが気になってる。

雑感(やってみて)

  • やっぱりSPAは夢があって楽しい
  • サーバ側はどうしようかもうちょっと考えたい
    • GraphQLとかちょっと手を出してみる予定
  • firebaseDB面白いけど使う場面がかなり限られる
  • とはいえfirebaseのホスティングhttpsカスタムドメイン対応しているのですごく良い!

あとServiceWorkerについてなんですが、キャッシュとしての効力が強すぎるのと、扱い方が難しそうだったのでオフにしてます。また別途勉強したい。

1日でリアルタイムチャットを作る。

作ります。後から加筆していく。

10:30 Start!

まずは大まかな要件

  • チャンネルの概念がある(slackのような)
  • 1チャンネル1000ポストまで(2chのような)
  • 匿名で投稿
  • 無限ふぁぼ( 超重要!

11:00

スーパーでネギトロを買ってネギトロ丼を食べる。 アニメを見る。 "ユーリ!!! on ICE"と"ガーリッシュナンバー"。よい。

11:30

少し細かく

firebaseのリアルタイムDB使う

  • channel_indexes:Array
    • (name):String
  • channels:Array
    • (channel):Object
      • id
      • message:Text
      • posted_at:Datetime
      • fabo:Integer こんなもんで良さそう

ルーティングとか

/chennel_name#messageID

UIコンポーネント

www.material-ui.com

これ使う。

12:00

プロジェクトを作り始める プロジェクト名はもちろんtimes-nakajima Nekojimaで。

github.com

やるぞ。 firebaseのほうのプロジェクトも作る。

DBを軽くつくっておく。

{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}

ルールは一旦これでいいや。 f:id:anoChick:20161203122359p:plain

DBもこんなもんで。

frameworkもいれる

github.com

これつかう。

12:30

とりあえず何かしらが公開されている状態にする。 FirebaseのHostingを使う。 react-boilerplateでビルドした後の静的ファイルを公開する形にする想定。 でもいったんてきとーに内容書いてデプロイ。 あとはカスタムドメインを通す。

React.js Boilerplate

f:id:anoChick:20161203124717p:plain もちっと時間がかかる。

boilerplateの初期設定

$ npm run setup
$ npm run clean

これで初期化される。

あとは試しにビルドしてみる

$ npm run build

出来たけど/buildに吐き出された。 firebaseのデプロイ対象ディレクトリは/publicなので、これを変える。

{
  ...
  "hosting": {
    "public": "build"
  }
}

サイドdeployする。

f:id:anoChick:20161203125620p:plain

よしよし。 そんなこんなでSSL通ったぞ。 DNSレコード書き換えて... レコード設定ミスっていたのでもう少し時間がかかりそう。30分ぐらい?

13:00

UIframeworkと、firabaseを差し込んでいく。 <MuiThemeProvider>を全体に適応させる。 f:id:anoChick:20161203132802p:plain

はいった。

$ npm install reactfire firebase react-mixin@2 --save  

firebaseとreactfireをいれる。 react-mixinはReactコンポーネントにfirebaseリアルタイムDBとのバインディング機能をアドオンするために使う。

つながってるか確認するためのデータをRealtime Databaseに入れる。 f:id:anoChick:20161203134833p:plain

フロント側も適当な場所で

    var ref = firebase.database().ref("home_message");
    this.bindAsObject(ref, "homeMessage");

とかする。 f:id:anoChick:20161203134921p:plain ほいきた!

とかやってたらドメイン設定完了したっぽい ビルド&デプロイしよう。

React.js Boilerplate

できt.. てない!反映されない!Nannde!

14:00

デプロイで躓く わからん!反映に時間かかるのかな。 ちょっと休憩しよう。

16:00

この時点でもう結構色んな問題が起こっていることに気づいた。 少しづつ直していく。

17:45

materialUIがうまく扱えなくてどうしよう。一旦寝るという選択肢。

19:00

起きた。 github.com

寝たらすぐに解決した。よかった。

20:30

再開 チャンネル切り替えと追加の機能をつける。

だめでしたー!!!!

思いの外バグ踏んだのと、firebaseの扱いがうまくいかなかったので未完。 とりあえずURLだけ貼っておきます。

https://nekoijma.anochick.com

サーバレスな汎用スクレイパーを作った。

anoninoni.hateblo.jp

以前SERVERLESS FRAMEWORKを紹介したんですが、 今回はSERVERLESS FRAMEWORKを使ってスクレイパーを作りました。

github.com

使い方

serverless deploy --stage production

AWSにdeployして使います。

例: githubリポジトリのページからStar数を取ってくる。

/scrape?
url=https://github.com/anoChick/basicscraper&
query=.social-count.js-social-count&
datatype=int

urlで対象ページを指定し、queryで対象のhtml要素を指定します。 datatypeをつかうとトリムされたり小数点を丸めたり出来ます。

f:id:anoChick:20161128025823p:plain response:

{
  "datetime": "2016-11-27 05:56:14",
  "url": "https://github.com/anoChick/basicscraper",
  "result": {
    "query": ".social-count.js-social-count",
    "value": 0
  }
}

同一サイトに対して複数の要素を取得したい場合は、パラメータを配列にして渡すことも出来ます。 また、labelをつけることも可能です。

f:id:anoChick:20161128025747p:plain response:

{
  "datetime": "2016-11-27 05:54:51",
  "url": "https://github.com/anoChick/basicscraper",
  "result": [
    {
      "index": "0",
      "query": ".social-count.js-social-count",
      "value": 0,
      "label": "Star数"
    },
    {
      "index": "1",
      "query": ".author",
      "value": "anoChick",
      "label": "Author"
    }
  ]
}



## 次にやること
```/scrape```でスクレイピングの即時実行する機能が提供できました。

```/schedule```などでジョブのスケジューリングなんかもやれたら良いなって思ってます。

ご利用は自己責任でお願いします。