ReactJS の環境構築(2)

前回「ReactJS の環境構築(1)」の続きです。

ReactJS アプリの作成

ReactJS のチュートリアルでとても分かりやすい記事がありましたので、そちらに沿って環境構築させて頂きました。

<参考文献>
フォルダ構成

ディレクトリ構成は以下の通りにしました。

myApp/
 ├ .babelrc
 ├ package.json
 ├ webpack.config.js
 ├ development.js
 ├ dist/
 │ └ js/
 └ src/
   ├ index.html
   ├ js/
   └ css/

プロジェクトフォルダを作成します。

$ cd ~
$ mkdir myApp; cd $_
$ npm init -y
Babel の設定

ES6 でコードを書くために Babel を使います。(参考 Babel
Babel をインストールします。

$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react

.babelrc を以下の内容で作成します。

{ "presets": [ "es2015", "react" ] }
Webpack の設定

Webpack をインストールします。

$ npm install --save-dev webpack

webpack-dev-server をインストールします。

$ npm install --save-dev webpack-dev-server html-webpack-plugin

また、SASS(SCSS) を利用するためのツールもインストールしておきます。

$ npm install --save-dev webpack style-loader css-loader sass-loader

webpack.config.js と development.js を作成します。

// webpack.config.js

require('babel-core/register');
module.exports = require('./development');
// development.js

import path from 'path'
import HtmlWebpackPlugin from 'html-webpack-plugin'

const src = path.resolve(__dirname, 'src')
const dist = path.resolve(__dirname, 'dist')

export default [
  {
    entry: {
      bundle: src + '/js/index.jsx'
    },
    output: {
      path: dist + '/js',
      filename: '[name].js'
    },
    module: {
      loaders: [
    {
          test: /\.css$/,
          loader: 'style!css'
        },
    {
        test: /\.scss$/,
        loader: 'style!css!sass'
        },
        {
          test: /\.jsx$/,
          exclude: /node_modules/,
          loader: 'babel'
        }
      ]
    },
    resolve: {
      extensions: ['', '.js']
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: src + '/index.html',
        filename: 'index.html'
      })
    ]
  }
]
React のインストール【追記 2017/04/01】

React のインストールを行います。一番重要な内容を書き忘れていました。

$ npm install --save-dev react react-dom
アプリの作成

さて、いよいよアプリを作成します。
React の使い方はまだよく分かっていなので、引き続き上記のチュートリアル本家サイトを参考に作ってみました。

とりあえず「ボタンを押すと表示がかわる」を目指して簡単なものができました。
完成したアプリが以下です。ボタンを押すと「Hello!!!」の文字の色が変化します。
f:id:ashiris:20170205211701p:plain

index.html の内容。

// index.html


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>React Test</title>
  </head>
  <body>
    <div id="app" />
  </body>
</html>

myApp/src/js/index.jsx を作成。

import React from 'react'
import {render} from 'react-dom'

import CSS from '../css/sample.scss'

class Sample extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      style: {color: "black"},
      ind: 0,
    }
  }
  changeColor(i) {
    const colors = ['black', 'crimson', 'aqua', 'orange', 'lime', 'navy', 'silver']
    let ind = (i+1) % colors.length
    this.setState({
      style: {color: colors[ind]},
      ind: ind,
    })
  }
  render () {
    return (
      <div>
        <p style={this.state.style}>Hello!!!</p>
        <button className="change" onClick={() => this.changeColor(this.state.ind)}>
          CHANGE
        </button>
      </div>
    )
  }
}
render(<Sample />, document.getElementById('app'))

myApp/src/css/sample.scss を作成。

body {
  font: 14px "Century Gothic", Futura, sans-serif;

  .change {
    font-weight: bold;
    background-color: slateblue;
    color: white;
    padding: 5px 20px;
  }
}
アプリの起動

webpack-dev-server を起動しアプリの動作確認を行います。

$ ./node_modules/.bin/webpack-dev-server

ブラウザから web サーバーにアクセスすると画面が表示されました。

後書き

分からないなりに環境設定から行いましたが何とか ReactJS のアプリ作成までできました。 今回もそうですが、React についての記事がたくさんありますので困ってもググれば大体解決できます。 まずは、本家サイトのチュートリアルから学習していこうと思います。

ReactJS の環境構築(1)

ReactJS の勉強しようと思いたち、kvm で仮想化した CentOS に ReactJS の環境構築しました。その時のメモです。

環境

設定の流れ

  1. nodebrew のインストール
  2. NodeJS のインストール
  3. Nginx の設定
  4. ReactJS アプリの作成

(本記事には、1.〜3.を記載しています。4.は次記事に記載します。)

nodebrew のインストール

nodebrew は NodeJS のバージョン管理を行うツールです。

<参考文献>

curl コマンドでダウンロードしてインストールします。

$ curl -L git.io/nodebrew | perl - setup

nodebrew にパスを通すために、bash_profile を編集する。

$ vim ~/.bash_profile

ファイル最下行に下記を追加する。

export PATH=$HOME/.nodebrew/current/bin:$PATH

設定を反映し、インストールの確認をする。

$ source ~/.bash_profile
$ nodebrew -v
nodebrew 0.9.6
...

NodeJS のインストール

nodebrew で最新安定版の NodeJS をインストールします。

$ nodebrew install-binary stable
$ nodebrew list
v7.4.0
$ nodebrew use v7.4.0
$ node -v
v7.4.0
$ npm -v
4.0.5

Nginx の設定

web サーバには、Nginx を使用します。

<参考文献>

Nginx をインストールします。

$ sudo yum install -y http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
$ sudo yum -y install --enablerepo=nginx nginx

設定ファイルを編集します。

$ sudo vim /etc/nginx/conf.d/default.conf

ブラウザからアクセスしたら、起動しているReact アプリ表示するように編集する。

# 追加 ---------------------------------
upstream myApp {
    ip_hash;
    server 127.0.0.1:8080;
}
# --------------------------------------

server {
    listen       80;
    server_name  localhost;

    # 追加 ------------------------------------------------------------
    proxy_redirect                          off;
    proxy_set_header Host                   $host;
    proxy_set_header X-Real-IP              $remote_addr;
    proxy_set_header X-Forwarded-Host       $host;
    proxy_set_header X-Forwarded-Server     $host;
    proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
    # -----------------------------------------------------------------

    # location の設定を編集
    location / {
        proxy_pass http://myApp/;
    }

...

web サーバにアクセスできるように SELinux を無効にしておきます。

$ setenforce 0

次回起動時からも SELinux が無効になるように設定を編集します。

$ sudo vim /etc/selinux/config

以下のようにファイルを編集する。

...
# SELINUX=enforcing   # コメントアウト
SELINUX=Permissive    # 追加
...

次記事に続きます。

Amazon の自動購入プログラミングをやってみる(NodeJS)

先日インストールした CasperJS を使って Amazon サイトの自動購入処理を行うプログラミングを作成してみました。

環境

  • Arch Linux
  • PhantomJS
  • CasperJS

参考文献

プログラム

普段加湿器のアロマで使用しているオイルを自動購入するプログラムを作成します。

ソースコード
// onlineShopping.js

var AZ_EMAIL = 'XXXXXXXX';      // Amazon ID
var AZ_PASSWD = 'XXXXXXXX';     // Amazon password

var casper = require('casper').create({
        pageSettings: {
                loadImages: false,
                loadPlugins: false
        },
        waitTimeout: 30000,
        verbose: true
});

// iPhone 画面としてブラウザ操作する。
casper.userAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OSX) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53');

// ログイン画面へアクセスする。
casper.start('https://www.amazon.co.jp/ap/signin?_encoding=UTF8&openid.assoc_handle=jpflex&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.pape.max_auth_age=0&openid.return_to=https%3A%2F%2Fwww.amazon.co.jp%2F%3Fref_%3Dnav_signin', function() {
        this.echo(this.getTitle());
        this.capture('capture1.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('form[name="signIn"]', function() {
                this.evaluate(function(email, passwd) {
                        document.querySelector('#ap_email').value = email;
                        document.querySelector('#ap_password').value = passwd;
                }, AZ_EMAIL, AZ_PASSWD);
        });
});

// ログインする。
casper.then(function() {
        this.capture('capture2.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('form[name="signIn"]', function() {
                this.evaluate(function() {
                        document.querySelector('#signInSubmit').click();
                });
        });
});

// 商品の画面へアクセスする。
casper.then(function() {
        this.capture('capture3.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('#nav-logo', function() {
                this.open('https://www.amazon.co.jp/dp/B000FQR2BO/');
        });
});

// カードに入れる。
casper.then(function() {
        this.echo(this.getTitle());
        this.capture('capture4.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('#add-to-cart-button', function() {
                this.evaluate(function() {
                        document.querySelector('#add-to-cart-button').click();
                });
        });
});

// レジへ進む。
casper.then(function() {
        this.capture('capture5.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('#a-autoid-2-announce', function() {
                this.evaluate(function() {
                        document.querySelector('#a-autoid-2-announce').click();
                });
        });
});

// 注文を確定する。
casper.then(function() {
        this.echo(this.getTitle());
        this.capture('capture6.png', {top:0, left:0, width:1280, height:720});
        this.waitForSelector('input[name="placeYourOrder1"]', function() {
                this.echo('click click');
                this.evaluate(function() {
                        document.querySelector('input[name="placeYourOrder1"]').click();
                });
        });
});

casper.then(function() {
        this.echo(this.getTitle());
        this.capture('capture7.png', {top:0, left:0, width:1280, height:720});
});

casper.run();
コード解説

「getTitle」と「capture」を使用している箇所は、処理が正常に進んでいるかの確認のためなので主処理には無関係です。

「userAgent」を使用するとブラウザ指定や端末の種別指定ができます。ユーザーエージェントの指定によって画面の構成などが変わります。こちらのサイトでユーザーエージェントの一覧が参照できます。
いろいろと試した結果、今回は iPhone 端末上でオンライン注文する操作にしました。

「waitForSelector」を呼び出すと、第1引数で指定したセレクタが機能できるまで画面描画を待った後、第2引数で指定した処理を行います。指定したセレクタが画面上に存在しない場合はタイムアウトエラーになります。操作を確実に行うためにボタン操作はこの関数を使って行います。

後書き

今回は注文操作のために CasperJS を利用してプログラムを作成しましたが、使ってみてこのツールの便利さがわかりました。画面のキャプチャやセレクタの情報をログに履いたりとテストツールとして本当に素晴らしいです。CasperJS の公式サイトを見ると他にいろいろとできそうですのでたくさん試してみたいです。

CasperJSを使ってみる

CasperJS というツールを使ってみたので今回記事にしました。
CasperJS は PhantomJS というヘッドレスブラウザ(GUIなしのブラウザ)の拡張ライブラリで、ブラウザの操作を行ったりキャプチャをとったりできます。主にウェブアプリの自動テストに使用したりするそうです。
参考情報: Wikipedia

環境

ノートPC(OS: Arch Linux)

<余談>
Raspberry Pi に環境を構築する予定でしたが、ラズパイ用 PhantomJS のパッケージがないのでソースからビルドする必要があるらしいです。長時間がかかるそうなので後日設定してみようと思います。

作業手順

  1. PhantomJS のインストール
  2. ChasperJS のインストール

PhantomJS のインストール

<参考文献>
PhantomJSをインストールする

公式サイトよりパッケージをダウンロード。

$ cd /usr/local/src/
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2

ダンロードファイルを展開し配置する。

$ sudo tar xvf phantomjs-2.1.1-linux-x86_64
$ sudo ln -s phantomjs-2.1.1-linux-x86_64 phantomjs
$ sudo ln -s /usr/local/src/phantomjs/bin/phantomjs /usr/local/bin/phantomjs

インストールの確認。

$ phantomjs -v
2.1.1

CasperJS のインストール

<参考文献>

WEB

公式サイト

書籍

JS+Node.jsによるWebクローラー/ネットエージェント開発テクニック

公式サイトの手順に従ってインストールする。

$ cd /usr/local/src
$ git clone git://github.com/casperjs/casperjs.git
$ ln -s casperjs/bin/casperjs /usr/local/bin/casperjs

インストールの確認。

$ casperjs --version
1.1.3

動作確認

グーグルトップページの全アンカータグのURLを取得するプログラムを作成してみます。

ソースコード
// getAnchors.js

var casper = require('casper').create();

// グーグルにアクセスする。
casper.start('http://www.google.com', function() {
    // ページのタイトルを表示する。
    this.echo(this.getTitle());
});

//アクセス後、ページの全アンカータグのURLを取得する。
casper.then(function() {
    // 全アンカータグを取得
    var hrefs = this.evaluate(function() {
        var alist =  document.querySelectorAll("a");
        var hrefList = [];
        for (var i=0; i<alist.length; i++) {
            hrefList.push(alist[i].href);
        }
        return hrefList;
    });
    // アンカータグのURLを表示する。
    for (var i in hrefs) {
        this.echo(hrefs[i]);
    }
});

casper.run();
プログラム実行
$ casperjs getAnchors.js
Google
https://plus.google.com/?gpsrc=ogpy0&tab=wX
https://www.google.co.jp/webhp?tab=ww
...

プログラム解説

「start(URL, 処理)」で「URL」のページへアクセスします。

そして、アクセス後の処理は「then(処理)」を使って作成します。
「casper.then(); casper.then(); ...」と実装していくことで、ブラウザ操作を順次実行することができます。

DOM操作を実装するには、「evaluate(処理)」を使います。evaluate関数内にコードを書き、関数の戻り値からセレクタの情報を拾います。

上記のようなコードが、CasperJSの基本的な実装のようです。

後書き

今回はノートPC環境で CasperJS の実装を行いましたが、Raspberry Pi で実行できるように環境を整えて行きたいです。

Raspberry Pi でスイッチを使ったLED点灯をしてみた。(Node.js)

今回は、Node.js をインストールした Raspberry Pi を使って、
LED点灯の制御を行ってみました。

概要

スイッチが押されている間だけLEDが点灯するプログラムを作成する。

参考文献

WEB
  1. 第9回「ラズベリーパイで電子工作!Lチカ…の前にLピカ!」
  2. Raspberry PiとNode.jsでLチカ(LEDをチカチカ)させてみよう!
  3. ラズベリーパイのGPIOを、Node.jsで操作してLED光らせてみた!
  4. タクトスイッチを使ってLEDを点灯してみる
  5. Raspberry Pi タクトスイッチの入力を扱う その1
書籍
  1. Raspberry Piで学ぶ電子工作 超小型コンピュータで電子回路を制御する

環境

回路の作成

以下の図の通りに配線を繋ぎます。 f:id:ashiris:20170109205400p:plain 回路の解説は割愛させて頂きます。
参考文献のWEB 5.書籍 1.で詳しい解説が掲載されていますので、
詳細を知りたい方はそちらを参照ください。

プログラム作成

ソースコード
var fs = require('fs');

var GPIO_DIR = '/sys/class/gpio';
var GPIO_PIN_DIR = GPIO_DIR + '/gpio';
var PIN_LIST = [];

//使用したGPIOポートの開放
var cleanUp = function() {
    for (var i=0; i < PIN_LIST.length; i++) {
                fs.writeFileSync(GPIO_DIR + '/unexport', PIN_LIST[i]);
        }
};

//使用するGPIOポートの準備設定
var setUp = function(pin, io) {
        try {
                fs.writeFileSync(GPIO_DIR + '/export', pin);
                PIN_LIST.push(pin);
                fs.writeFileSync(GPIO_PIN_DIR + pin + '/direction', io);
        } catch (e) {
                console.log(e);
                cleanUp();
        }
};

//GPIOの入力状態を取得
var getInput = function(pin) {
        try {
                var cntxt = fs.readFileSync(GPIO_PIN_DIR + pin + '/value');
                return cntxt.toString().split('\n')[0];
        } catch (e) {
                console.log(e);
                cleanUp();
        }
};

//GPIOの出力状態を設定
var setOutput = function(pin, value) {
        try {
                fs.writeFileSync(GPIO_PIN_DIR + pin + '/value', value);
        } catch (e) {
                console.log(e);
                cleanUp();
        }
};

try {
        //GPIO 7を入力に設定する。
        setUp(7, 'in');
        //GPIO 8を出力に設定する。
        setUp(8, 'out');

        //0.01毎にGPIO 7の入力状態を確認し、
        //電流が流れている(スイッチが押されている)場合
        //LEDを点灯させる。
        var moniter = setInterval(function() {
                var value = getInput(7);
                if (value == '1') {
                        //スイッチが押された状態
                        setOutput(8, '1');
                } else {
                        //スイッチが離された状態
                        setOutput(8, '0');
                }
        }, 10);
} catch (e) {
        console.log(e);
        cleanUp();
}

process.on('SIGINT', function() {
        cleanUp();
        process.exit(0);
});

process.on('end', function() {
        cleanUp();
        process.exit(0);
});
コード解説

GPIOの操作について、基本的な内容を簡潔に書くと以下のとおりです。

  • /sys/class/gpio/export にGPIOの番号を書き込むと、その番号のGPIOが使用可能になる。
  • /sys/class/gpio/unexport にGPIOの番号を書き込むと、その番号のGPIOが使用不可能になる。
  • /sys/class/gpio/gpioX/direction (XはGPIO番号)に、
    • 「in」と書き込むと入力としてGPIOが機能する。
    • 「out」と書き込むと出力としてGPIOが機能する。
  • /sys/class/gpio/gpioX/value (XはGPIO番号)の内容が、
    • 「0」である場合は電流が流れていない。(正確には電位が低い)
    • 「1」である場合は電流が流れている。(正確には電位が高い)

Node.js では、ファイルの読み書きは「fs」モジュールを扱う。
読み込みは「readFileSync(ファイル名)」関数を使用し、
書き込みは「writeFileSync(ファイル名, 書き込み内容)」関数を使用する。

注意点は、GPIOの使用後は必ず使用したGPIOを使用不可能状態に戻すことです。
開放したままそのGPIOを使用可能に設定しようとすると、エラーが発生してしまいます。

LED点灯の主要部分は、setInterval内の記述です。
スイッチが押された状態ではGPIO 7の電位が高くなるので、
0.01秒間隔でGPIO 7の状態を確認し電位が高ければ、
GPIO 8から電流が流れるようにしています。
スイッチを離された状態は、その逆に設定すれば良いだけです。

後書き

スイッチの状態を判断する処理をsetIntervalではなく、
できれば Node.js っぽくイベントを拾って行いたかったのですがうまくいきませんでした。
「fs.watch」関数でファイル変更イベントが拾えませんでした。

いろいろ調べていたら「Node.jsでRaspberryPiのGPIOを良しなにする方法」という記事を見つけました。
どうやら方法はあるみたいです。
時間ができたらそちらを学習してみようかな。

Node.js のプロセス処理を少し学ぶ

IoT をする前に Node.js のプロセス処理について
簡単に学習することにした。

概要

プログラム開始後にキー入力待機状態にし、
入力ごとに以下のように処理を実行させる。
* 「end」と入力するとプログラムを終了する。
* それ以外の場合、「Input: XXXXX」(XXXXX は入力値)と表示する。

<参考文献>

プログラムの内容

ソースコード
// eventsPra.js

var fs = require('fs'); 

// キーの入力待ち状態にする。
process.stdin.resume();
process.stdin.setEncoding('utf8');

// 標準入力終了時のイベント処理
process.stdin.on('end', function() {
    console.log('END!!');
});

// 入力された1行を読み込んだ時のイベント処理
process.stdin.on('data', function(inputData) {
    // 末尾の改行を取り除く。
    var input = inputData.slice(0, -1);
    if (input == 'end') {
        // end が入力された場合、プロセスを終了する。
        process.exit(0);
    } else {
        console.log('Input: ' + input);
    }
});

// Ctrl + C が入力された場合のイベント処理
process.on('SIGINT', function() {
    console.log('Ctrl+C!!');
    // プロセスを終了する。
    process.exit(0);
});

// プロセス終了時のイベント処理
process.on('exit', function() {
    console.log('EXIT!!');
});
挙動確認

プログラムを実行してみる。

//入力
abc
end
//出力
Input: abc
EXIT!!

次に、プログラム実行中に「Ctrl+C」と入力すると、

^CCtrl+C!!
EXIT!!

と表示される。
また、プログラム実行中に「Ctrl+D」と入力すると、

END!!
EXIT!!

と表示される。

考察

標準入力からの入力待ちにするには、
process.stdin.resume() の呼び出しが必要です。
そして標準入力のイベント処理は、
「process.stdin.on(イベント名, コールバック関数)」で設定します。
キー入力はイベント名「data」で、
入力値はコールバック関数の第1引数に渡されるようです。
また、「Ctlr+C」と入力するとプロセスに「SIGINT」のシグナルが送られるので、
「Ctrl+C」の入力はこのイベントを拾うことで設定できます。
ただし、「SIGINT」のリスナの登録を行うとプログラム終了の処理が
働かなくなるので、明示的にプロセスを終了させる必要があります。

後書き

今回の内容を踏まえて、次回はLEDの点灯制御を行っていこうかと思います。

Raspberry Pi に Node.js をインストール

概要

先日設定した Raspberry Pi に Node.js をインストールしてみる。

環境

目次

  1. Node.js のインストール
    <参考文献>
  2. 動作確認

1. Node.js のインストール

公式サイトよりインストールする。
Node.js のバージョンを確認し、以下の通りにイントールを行う。

wget https://nodejs.org/dist/v4.7.1/node-v4.7.1-linux-armv7l.tar.gz
tar -xvf node-v4.7.1-linux-armv7l.tar.gz
sudo cp -R node-v4.7.1-linux-armv7l/* /usr/local/

バージョンの確認をする。

node -v

「v4.7.1」と表示され、インストールできたことを確認。

2. 動作確認

動作確認のために、標準出力とファイル書き込みを行うコードを作成。

// test.js

console.log('start!');

var fs = require('fs');

// test.txt ファイルに書き込みを行う。
fs.writeFileSync('test.txt', 'This is a test.');

console.log('end!');

作成したコードを実行する。

node test.js

コンソールに「start!」「end!」が表示される。
また、test.txt が作成され、「This is a test.」と書き込まれる。

後書き

特に問題なく Node.js の環境が整いました。
今後は Raspberry Pi と Node.js を使った IoT を行っていこうかなと思います。