AtomでTypeScriptの開発環境を構築しよう

最近、TypeScriptにハマってます。
TypeScriptでAngularJSを書くと大変読みやすいコードが書けます。
TypeScriptの開発環境はAtomで構築するのが一番だろう。
というわけで、
UbuntuにAtomをインストールして、TypeScriptの開発環境の構築方法を説明する。

なお、ここで想定している環境は下記の通り。
Lubuntu(Ubuntu) 14.04
node.js インストール済み

1. Atomのインストール

参考文献: Ubuntu 14.04 に Atom をインストールして、起動チェックしてから、パッケージ導入するまで

下記のコマンド実行

sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom

インストールが完了したら、スタートメニュー > プログラミング > Atom からAtomを起動できる

2. TypeScriptとtsdのインストール

参考文献: TypeScript の開発環境構築と周辺ツールの紹介

ちなみに、tsdとは型定義ファイルマネージャでTypeScirptの.d.tsファイルを管理するツールである。

下記のコマンド実行

npm install -g typescript
npm install -g tsd

3. AtomにTypeScript開発環境を構築する

下記URL参照
AtomでTypeScriptの環境を構築する

なお、筆者は上記のtsconfig.jsonのうち、下記の設定にカスタマイズしている。

{
  "formatCodeOptions": {
    "indentSize": 2, //自動インデントを2に設定
    "tabSize": 2, //タブサイズを2に設定
  }
}

0

YEOMANを使ってMEAN(MongoDB+Express+AngularJS+Node.js)を作成しよう

YEOMANを使ってMEANを作成する手順と開発チュートリアルを説明する。
MEANとはMongoDB+Express+AngularJS+Node.jsを組み合わせたWebアプリケーションである。
YEOMANとはフレームワークを簡単に作成できるツールで、node.jsのパッケージとして提供されている。
MEANのフレームワークにはangular-fullstakを使う。
※Yeomanで用意されているジェネレーターは沢山有るがMEANを作成できるジェネレーターで一番人気がangular-fullstak
 
ほとんどの手順は下記URLに従う。
YEOMANを使ってMEAN(MongoDB + Express.js + Angular.js + Node.js)のWebアプリケーションを作る
しかし、上記URLはYEOMANのバージョンが古いためか、チュートリアルの内容が現状のバージョンに沿わないため、下記であらためて説明する。
 
なお、ここで想定している開発環境は下記の通り。
Lubuntu(Ubuntu) 14.04
Node.js v4.2.3
yo 1.6.0
MongoDB 3.2.0
※ 上記以外のバージョンではチュートリアルの内容が沿わない場合がある
 

0. 事前準備

まずはangular-fullstakに必要なアプリをインストールする。
 

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

下記URL参照
Ubuntu 14.04 に Node.jsをインストールする
 

0.2. MongoDBのインストール

下記URL参照
Install MongoDB Community Edition on Ubuntu
 

0.3. Rubyとgemのインストール

下記URL参照
UbuntuにRubyGemsをインストールする
※なお、フレームワークにSassを使わない場合はRubyとgemをインストールする必要はない。
 

0.4. Sassのインストール

端末から下記コマンドを実行する

gem install sass

 

0.5. Yeomanのインストール

npm install -g yo grunt-cli bower

※Yeomanとはyo, grunt, bowerを組み合わせたもの
 

0.6. angular-fullstakのインストール

npm install -g generator-angular-fullstack
npm update -g

※yoが1.4.1以上でないとangular-fullstackが動かないのでアップデートすること
 

0.7. node-inspectorのインストール

デバッガを使う場合は下記コマンドを実行する。

npm install -g node-inspector

以上で事前準備は完了した。
次にMEANアプリの開発チュートリアルを説明する。
 

1. MEANアプリの雛形を作成する

yoを使ってMEANアプリを作成する。
アプリを作る場所はユーザーディレクトリの配下にする。
※ちなみにVMWareなどのバーチャルPCでの共有フォルダにはアプリは作れない。アプリ作成時にシンボリックリンクを貼るが、共有フォルダにはシンボリックリンクが貼れないため。
なお、アプリ名はsampleとする。
 

1.1. sampleディレクトリの作成

mkdir sample
cd sample

※ディレクトリ名がそのままアプリ名になる。
 

1.2. MEANアプリを作成する

yo angular-fullstack

なお、アプリの作成はウィザード方式になっており、各設定をダイアログで設定する。
各項目と選択すべき項目を列挙する。(▼が選択すべき項目 ●はチェックボックスの選択状態 ○はチェックボックスの非選択状態)

[?] What would you like to write scripts with? 
▼Babel
 TypeScript

 

[?] What would you like to write makeup with?
▼HTML
 Jade

 

[?] What would you like to write stylesheets with?
 CSS
▼Sass
 Stylus
 Less

※今回のチュートリアルではスタイルシートは弄らないのでSass以外を選んでも問題ない
 

[?] What Angular router would you like to use?
 ngRoute
▼uiRouter

 

[?] Would you like to include Bootstrap? Yes

 

[?] Would you like to include UI Bootstrap? Yes

 

[?] What would you like to use for data modeling?
●Mongoose (MongoDB)
○Sequelize (MySQL, SQLite, MariaDB, PostgreSQL)

 

[?] Would you scaffold out an authentication boilerplate? Yes

 

[?] Would you like to include additional oAuth strategies?
●Google
●Facebook
●Twitter

※今回のチュートリアルでは関係ないので何を選んでも良い
 

[?] Would you like to use socket.io? Yes

 

[?] What would you like to write tests with?
▼Jasmine
 Mocha + Chai + Sinon

 

1.3. 動作確認する

grunt serve

ブラウザが自動的に起動して下図の画面が表示されれば成功である。
yeoman.01.01
 

2. routerを作成する

yoで作られたMEANアプリにメンバー一覧ページを追加するというチュートリアルを説明する。
処理の流れとしては、
1.yoコマンドでrouterの雛形を作成する
2.routerのhtmlを編集する
3.routerのcontrolerを編集する
という流れになる。
 

2.1. ページを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:route member

下記のダイアログは全てデフォルトにする。
[?] Where would you like to create this route? client/app/
[?] What will the url of your route be? member
すると下記のファイルが作成される。
create client/app/member/member.js
create client/app/member/member.controller.js
create client/app/member/member.controller.spec.js
create client/app/member/member.html
create client/app/member/member.scss
 
動作確認する。

grunt serve

http://localhost:9000/memberにアクセスして、下図が表示されれば成功である。
yeoman.01.02
 

2.2. htmlを編集する

client/app/member/member.htmlを下記の通り編集する。

編集前
<div class="col-md-12">
This is the member view.
</div>

 

編集後
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

2.3. ダミーデータを追加する

メンバーが正しく表示されるかを確認するためにダミーデータを追加する。(ダミーデータを後からmongoDBのデータに置き換える)
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.message = 'Hello';
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

2.4. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.03
 

3. clientからserverをGETする

処理の流れとしては、
1.clientからserverの/api/membersをGETで呼び出し
2.serverはcontroller.index関数へルーティング
3.controller.index関数でMongoDBにアクセス
4.MongoDBがデータを返す
という流れになる。
 

3.1. APIを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:endpoint member

下記のダイアログは全てデフォルトにする。
[?] What will the url of your endponit to be? /api/members
すると下記のファイルが作成される。
create server/api/member/index.js
create server/api/member/member.controller.js
create server/api/member/member.model.js
create server/api/member/member.socket.js
create server/api/member/member.spec.js
 

3.2. スキーマを定義する

メンバー一覧に表示する名前を定義する。
server/api/member/member.model.jsを下記の通り編集する。

編集前
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
  info: String,
  active: Boolean
});

export default mongoose.model('Member', MemberSchema);

 

編集後
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
});

export default mongoose.model('Member', MemberSchema);

 

3.3. MongoDBにデータを追加する

MongoDBに初期データを追加する。
server/api/member/member.controller.jsのMember変数定義以降に下記を追加する。

編集前
'use strict';

import _ from 'lodash';
import Member from './member.model';

 

編集後
'use strict';

import _ from 'lodash';
import Member from './member.model';

Member.find({}).remove(function() {
  Member.create({
    name: '田中_2'
  }, {
    name: '鈴木_2'
  }, function(err) {
    console.log('finished populating Members');
  });
});

 

3.4. サーバーにGETが定義されていることを確認する

server/api/member/index.jsに下記の通りgetが定義されていることを確認する。

router.get('/', controller.index);

 
server/api/member/member.controller.jsに下記の通りindex関数が定義されていることを確認する。

// Gets a list of Members
export function index(req, res) {
  Member.findAsync()
    .then(respondWithResult(res))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

3.5. APIに接続する

clientからserverのapiを参照する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

3.6. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.04
 

4. clientからserverへPOSTする

処理の流れとしては、
1.clientからserverの/api/membersをPOSTで呼び出し
2.serverはcontroller.create関数へルーティング
3.controller.create関数でMongoDBにアクセス
4.MongoDBへデータを保存する
という流れになる。
 

4.1. メンバー登録フォームを追加する

メンバーページからメンバーを登録できるようにする。
client/app/member/member.htmlを下記の通り編集する。

編集前
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

編集後
<div ng-controller="MemberCtrl">
  <h2>メンバー登録</h2>
  <form >
    <legend>メンバー登録</legend>
    <label>名前</label> 
    <input type="text" id="name" name="name" ng-model="member.name"> 
    <button class="btn btn-primary" ng-click="createMember()">登録</button>
  </form>

  <h2>メンバー一覧</h2>
  <ul>
    <li ng:repeat="member in members">
      {{member.name}}
    </li>
  </ul>
</div>

 

4.2. 登録ボタンのイベントハンドラーを追加する

ボタンが動作するように、イベントハンドラーを追加する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

4.3. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、下図の通り表示されれば成功である。
yeoman.01.05
 

4.4. 登録データをPOSTする

このままではサーバーにデータが保存されていないので、データをサーバーへPOSTする。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

4.5. サーバーにPOSTが定義されていることを確認する

server/api/member/index.jsに下記の通りpostが定義されていることを確認する。

router.post('/', controller.create);

 
server/api/member/member.controller.jsに下記の通りcreate関数が定義されていることを確認する。

// Creates a new Member in the DB
export function create(req, res) {
  Member.createAsync(req.body)
    .then(respondWithResult(res, 201))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

4.6. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、さらにページ更新して、下図の通り表示されれば成功である。
yeoman.01.05
 

5. socket.ioでページを同期させる

angular-fullstackではsocket.ioをページの同期に使っているようなので、それについても説明する。
処理の流れとしては、
1.clientでページ読み込み時(/api/memberをGETする時)にsocket通知によるデータ配列追加/削除処理を登録しておく。(syncUpdates関数)
2.serverではMongoDBにデータを追加した際にMongoDBのポストホック機能(Member.schema.post関数)からホックしている全てのclientへsocket通知を送信する。
3.clientではsocket通知を受け取ったら、データ配列に追加する。(データは$scope内に定義しているので、angularJSにより自動的にページ更新がかかる。)
という流れになる。
 

5.1. socket.syncUpdates()を呼び出す

client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http, socket) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
      socket.syncUpdates('member', $scope.members);
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.member.name = '';
        });
      }
    };
  });

変更点は、
・/api/membersのGET時にsocket.syncUpdates呼び出し
・socket.syncUpdatesでメンバー一覧の更新が行われるので、createMember関数でのメンバー追加処理は削除する
 

5.2. socket.syncUpdates()の定義を確認する

/client/components/socket/socket.service.jsにsyncUpdates関数が定義されていることを確認する。

      /**
       * Register listeners to sync an array with updates on a model
       *
       * Takes the array we want to sync, the model name that socket updates are sent from,
       * and an optional callback function after new items are updated.
       *
       * @param {String} modelName
       * @param {Array} array
       * @param {Function} cb
       */
      syncUpdates: function (modelName, array, cb) {
        cb = cb || angular.noop;

        /**
         * Syncs item creation/updates on 'model:save'
         */
        socket.on(modelName + ':save', function (item) {
          var oldItem = _.find(array, {_id: item._id});
          var index = array.indexOf(oldItem);
          var event = 'created';

          // replace oldItem if it exists
          // otherwise just add item to the collection
          if (oldItem) {
            array.splice(index, 1, item);
            event = 'updated';
          } else {
            array.push(item);
          }

          cb(event, item, array);
        });

        /**
         * Syncs removed items on 'model:remove'
         */
        socket.on(modelName + ':remove', function (item) {
          var event = 'deleted';
          _.remove(array, {_id: item._id});
          cb(event, item, array);
        });
      },

※上記のコードはangular-fullstackによって自動生成される。
 

5.3. MongoDBのポストフック機能を使ってsocket通知が呼び出されていることを確認する

server/api/member/member.socket.jsにMongoDBへのデータ追加時にsocketからmember:saveが通知されることを確認する。

/**
 * Broadcast updates to client when the model changes
 */

'use strict';

var MemberEvents = require('./member.events');

// Model events to emit
var events = ['save', 'remove'];

export function register(socket) {
  // Bind model events to socket events
  for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
    var event = events[i];
    var listener = createListener('member:' + event, socket);

    MemberEvents.on(event, listener);
    socket.on('disconnect', removeListener(event, listener));
  }
}


function createListener(event, socket) {
  return function(doc) {
    socket.emit(event, doc);
  };
}

function removeListener(event, listener) {
  return function() {
    MemberEvents.removeListener(event, listener);
  };
}

※上記のコードはangular-fullstackによって自動生成される。
 

5.4. socket通知の登録処理が呼び出されていることを確認する

server/config/socketio.jsでregistar関数が呼ばれていること確認する。

// When the user connects.. perform this
function onConnect(socket) {
  // When the client emits 'info', this listens and executes
  socket.on('info', data => {
    socket.log(JSON.stringify(data, null, 2));
  });

  // Insert sockets below
  require('../api/member/member.socket').register(socket);
  require('../api/thing/thing.socket').register(socket);

}

※上記のコードはangular-fullstackによって自動生成される。
 

5.5. 動作確認する

ブラウザを2ウィンドウ開き、http://localhost:9000/memberにアクセスして、一方に任意の名前(例では藤原)を入力して登録ボタンを押して、もう一方に登録内容が反映されれば成功である。
yeoman.01.06
 

6. デバッガ(node-inspector)を起動する

デバッガの起動方法を説明しておく。
下記のコマンドを実行する。

grunt serve:debug

するとブラウザが自動起動してnode-inspectorが開く。
‘–debug-brk’オプション付きで起動されるので、必ずサーバー側のソースコードの先頭でブレイクする。

7. gruntでブラウザ指定する

起動するブラウザを変えたい場合、Gruntfile.jsを下記の通り変更する。

通常起動のブラウザを指定する方法(例ではchromium-browserを指定している)

  grunt.initConfig({
  //省略...
    open: {
      server: {
        url: '<%= express.options.domain %>:<%= express.options.port %>',
        app: 'chromium-browser'
      }
    },

デバッガのブラウザを指定する方法(例ではchromium-browserを指定している)

    nodemon: {
    //省略...
      debug: {
      //省略...
        options: {
       //省略...
          callback: function (nodemon) {
            // opens browser on initial server start
            nodemon.on('config:update', function () {
              setTimeout(function () {
                require('open')('http://localhost:8080/debug?port=5858', 'chromium-browser');
              }, 500);
            });
          }
        }
      }
    },

1

C++11でstd::functionとstd::bindとtemplate functionでCallbackを呼び出す

C++11ではstd::functionとstd::bindとtemplate functionを駆使するとCallbackを呼び出せる。
内部のクラス間のやりとりなら、interfaceを使うよりお手軽。

#include <functional>
#include <iostream>

class ClassA
{
public:
    typedef std::function<void(int a, int b)> Func;

    template<typename T>
    static Func BindFunc(void (T::*func)(int a, int b), T *owner)
    {
        return std::bind(func, owner, std::placeholders::_1, std::placeholders::_2);
    }
 
    void SetFunc(const Func &func)
    {
        m_func = func;
    }
 
    void CallFunc(int a, int b) const
    {
        m_func(a, b);
    } 
private:
    Func m_func;
};
 
class ClassB
{
public:
    ClassB(ClassA &classA)
    {
        classA.SetFunc(ClassA::BindFunc(&ClassB::DoFunc, this));
    }

    void DoFunc(int a, int b)
    {
        std::cout << "ClassB a:" << a << ", b:" << b << "\n";
    }
};
 
class ClassC
{
public:
    ClassC(ClassA &classA)
    {
        classA.SetFunc(ClassA::BindFunc(&ClassC::DoFunc, this));
    }

    void DoFunc(int a, int b)
    {
        std::cout << "ClassC a:" << a << ", b:" << b << "\n";
    }
};
 
int main(int argc, char** argv)
{
    ClassA classA;
 
    ClassB classB(classA);
    classA.CallFunc(1, 2);  //ClassB a:1, b:2
 
    ClassC classC(classA);
    classA.CallFunc(3, 4);  //ClassC a:3, b:4
    return 0;
}

0

node.jsをevennodeにデプロイしよう

evennodeはnode.js専用のPaaSなので、使い方は非常に簡単。
ほとんどブラウザから設定できるので迷わないとは思うが、一応使い方を説明しておく。
ローカル環境はCentOS 6.5で、gitはインストール済み、node.jsのソースコードも作成済みとする。
evennodeのサインアップやログインはHPから行える。
ここではサインアップしてログイン後のコントロールパネルからアプリの作成手順を説明する。
 

アプリの作成

コントロールパネルの「APPS」タブを開き、「CREATE NEW APP」をクリックする。
evennode.01
 

プランの選択

好きなプランを選ぶ。とりあえずお試しならFreeで良いだろう。
evennode.02
 

node.jsの設定

Domainでサブドメイン名を設定する。これは他のevennodeのアプリとかぶらない名前にする必要がある。
Server locationはEuropa(Ireland)かUnited States(lowa)かを選べる。(どちらが日本から速いとかは調べていない)
Custom Domainは独自ドメインを使う場合に設定する。
Select app’s platformはNode.jsかio.jsかを選べる。普通はNode.jsで良いだろう。
Node.js varsionはv0.8.6~最新まで指定可能。
Startup commandは自動かカスタムかを選べる。
以上でアプリが作られる。
evennode.03
 

アプリの管理

さらにアプリの詳細設定を行う。
APPタブで作ったアプリをクリックする。
evennode.04
 

Public keysの管理

Manage public keysをクリックする。
evennode.05
 

Public keysの設定

Public keys for GitにGit用の公開鍵を設定してSaveボタンを押す。
evennode.06
 

環境変数の管理

環境変数の設定が必要な場合は、Environment varsをクリックする。
evennode.07
 

環境変数の設定

Nameに環境変数名、Valueに環境変数の値を入れて、Saveボタンを押す。
evennode.08
以上でevennodeでの作業は終了。
 

app.jsの作成

ここからローカル環境での作業になる。
参考文献:Node.js Hello World app
evennodeではどうもルートディレクトリのapp.jsを起動するようになっているようだ。
(package.jsonの”main”や”scripts”: {“start”}で別のソースコードを起動するよう記述しても認識されなかった)
app.js以外から起動したい場合はapp.js内でrequireで呼ぶ。
例えば、server/app.jsを起動したい場合は、ルートディレクトリにapp.jsを作成して下記を記述する。

require('./server/app.js');

 

Gitのリモートリポジトリを登録する

git remote add evennode git@evennode.com:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git

リポジトリのアドレスはevennodeのコントロールパネルの「Manage app > Git deployment > Repository」に記述してある。
 

デプロイする

gitでプッシュしてデプロイ完了。

git push evennode master

 

その他Tips:bower installが起動しない問題

bower installを起動する術がないので、bower installでインストールされるべきモジュール群もコミットしておく。
 

その他Tips:SSLの設定方法

設定方法は提供されていない。
support@evennode.com にメールしてSSLを設定したい旨を伝えると、SSL certificateとprivate keyを送れと返事が返ってくるので、送ると設定してくれる。
メールは英語でやり取りできる。
 

その他Tips:アプリの削除方法

APPSから削除したいアプリを選び、Settings > DELETE APP から削除できる。
evennode.09


0

node.jsをpivotal.ioにデプロイしよう

pivotal.ioのデプロイはgitを使わず、CF CLIという独自ツールを使うので、ちょっと特殊である。
使い方が分からなかったので備忘録メモ。
ローカル環境はCentOS 6.5とする。
 

CF CLIのインストール

参考文献:Set Up Your PWS Account and Download the CF CLI
上記の参考文献を参考にCentOS用のrpmをダウンロードしてインストール。
 

CF CLIにログイン

CentOSの端末から下記の通り入力する。

$ cf login -a https://api.run.pivotal.io
Email: user@example.com
Password: ••••••••

 

manifest.ymlの作成

参考文献:
Tips for Node.js Applications Application Bundling
PaaS基盤「Cloud Foundry V2」内部で使われるBuildpack、Wardenコンテナの仕組みとは?(前編)

デプロイしたいアプリのカレントディレクトリにmanifest.ymlを作成する。
内容は下記の通り。

---
applications:
- name: my-app
  buildpack: https://github.com/cloudfoundry/heroku-buildpack-nodejs.git
  command: node server/app.js

my-appはアプリ名である。urlにも使われるので他のアプリとかぶらない名前を付ける必要がありそう。
buildpackはこのURLで固定。
commandはnodeで立ち上げたいソースコードを指定。
 

.cfigunoreの作成

デプロイに含めたくないファイルがある場合は.cfigunoreに書き込む。
.gitigunoreの書式と全く同じ。
以下に例を示す。

.git
.sass-cache
.tmp
.idea
/dist
/e2e
/node_modules/bower
/node_modules/*grunt*
/node_modules/open
/node_modules/*jshint*
/node_modules/*karma*
/node_modules/requirejs
/node_modules/supertest
/node_modules/should
/node_modules/*imagemin*
/public
/.buildignore
/.editorconfig
/.gitattributes
/.gitignore
/.travis.yml
/.yo-rc.json
/.cfignore
/Gruntfile.js
/karma.conf.js
/protractor.conf.js
/manifest.yml

 

デプロイ

デプロイしたいアプリのカレントディレクトリから下記の通り入力する。

cf push my-app

下記のように処理が進めば成功。

Using manifest file /home/user/my-app/manifest.yml

Updating app my-app in org user-name / space development as user@exsample.com...
OK

Uploading my-app...
Uploading app files from: /home/user/my-app
Uploading 25.8M, 4613 files
Done uploading               
OK

Starting app my-app in org user-name / space development as user@exsample.com...
-----> Downloaded app package (13M)

...(node_modules内のパッケージなどをインストール)

1 of 1 instances running

App started


OK

App my-app was started using this command `node server/app.js`

Showing health and status for app my-app in org user-name / space development as user@exsample.com...
OK

requested state: started
instances: 1/1
usage: 1G x 1 instances
urls: my-app.cfapps.io
package uploaded: Sun Jan 18 01:10:58 +0000 2015

     state     since                    cpu    memory      disk   
#0   running   2015-01-18 10:11:53 AM   0.0%   77M of 1G   59.1M of 1G   

あとはmy-app.cfapps.ioにアクセスするとアプリが表示される。
 

おまけ:環境変数の設定

環境変数の設定が必要な場合は、pivotal.ioのコントロールパネルからアプリを指定してEnv Variablesタブから設定する。
 


0

node.jsが使えるレンタルサーバー比較

Node Hostingから候補のサーバーのベンチマークをとってみた。
候補条件は独自ドメインでSSLが使えるプランである。
ベンチマークは開発中のサイトグルチャを動かしたときのユーザーページの表示速度である。
ベンチマークの結果は以下の通り。

サーバー プラン 料金(月) ベンチマーク
heroku SSL Addon $20=2334円 8秒
evennode 768MB RAM 3 instance €13=1757円 8秒
お名前.com vps メモリ2GBプラン 1315円(税抜),1420円(税込) 測定していないがめちゃ遅かった
AWS EC2 t2.small $13.14=1531円(バージニア1年間前払いなし) 30秒
Microsoft Azure 西日本Basic A0 1428円 30秒
openshift Bronze 3 Small gears 0円 30秒
bluemix 128MB RAM 1 instance 0円 30秒
Cloud Foundry 128MB RAM 1 instance $2.7=314円 1分30秒
NodeNinja 無料 0円 動かず
MODULUS 396MB RAM $14.4=1680円 動くがリロードで404エラー
Pogoapp 256MB RAM $5=583円 使い方分からず

為替レートは以下の通りの本日のレートで計算した。
$1=116.563702円
€1=135.232545円

サーバー コメント
heroku 独自ドメインSSL無しか、herokuサブドメインでSSL有りなら無料で使えるので最高の選択だろう。ただし、SSL Addonを入れると月$20かかってくる。
evennode 独自ドメインSSL有りでherokuより安くなり、速度もherokuと同等である。グルチャevennodeを使っている。
お名前.com vps 計測していないがめちゃ遅かった。30秒はかかっていただろう。あとSSL使う場合、別途初年度24,000円(税抜)かかるようだ。
AWS EC2 t2.smallでも30秒かかるので遅い。これより一段上のプランであるt2.mediumは3年間全前払いでも$16.86=1968円かかるのでevennodeより高くなるのでベンチマーク対象外とした。
Microsoft Azure 30秒かかった。ちなみに西日本StandardA0(1632円)、A1(5508円)、A7(78336円)も試してみたが速度は同じ。また、Microsoft Azure WebsitesのFREEを試してみたが、ブラウザのコンソールにwebsocketsのサーバー確立エラーが表示される。しかし、Azure Website での Socket.IO を使用する Node.js チャット アプリケーションの構築ではwebsocketが使えると書いてあるし、設定でもwebsocketを有効にしているのだが。
openshift 無料でSSLも使えるので非常に魅力的だったが速度が遅い。一段上のプランであるsmall.highcpuは$18.25=2130円かかるのでevennodeより高くなるのでベンチマーク対象外とした。
bluemix IBMが提供するCloud Foundryと同じシステムのPaaSである。Node Hostingには載ってないが、node.js使えるので使ってみたが、30秒かかるので遅い。
Cloud Foundry 最低$2.7は魅力的だが、めちゃ遅い。1GBも試してみたが同じ速度だった。SSLは使えない。ドキュメントにはCLOUD FLARE使いましょうと書いてあった。無料でSSLが使えるらしい。ってか、heroku + CLOUD FLAREで完全無料でSSL使えるんじゃね?
NodeNinja 無料で魅力的だがグルチャが動かなかった。SSLにできるかも不明。
MODULUS 動くには動くが、トップページの読み込みが異常に長いうえにリロードすると404エラーになってしまう。トップページさえ読み込めれば、他のページは速かった。SSLは無料で使える。
Pogoapp $5でSSLが使えるのでかなり魅力的だったが使い方が分からなかった。ドキュメントのページが404エラーになる。デプロイ用のgitもアドインで入れるっぽい。操作が特殊なので使うのを断念。

なお、各サーバーのデプロイ方法は下記のリンク参照。
node.jsをevennodeにデプロイしよう
node.jsをpivotal.ioにデプロイしよう
AWS EC2にnode.jsサーバーを構築しよう
angular-fullstackをherokuにデプロイする方法

他のサーバーのベンチマークをとらなかった理由は下記の通り。

サーバー 理由
appfog SSL有りが月$50するので高い
Baidu App Engine ホームページが中国語で分からない。中国国家の検閲が入って突然使えなくなっても困るし
clever cloud SSLが使えるか明記されていない。最低でも€14.4かかりevennodeより高いのでベンチマーク取らず
cloudnode ベータ版につき無料っぽいが、BaaSっぽい。SSLは使えるらしい。登録しようとしたが、短縮URLが表示されるだけで登録できない
dotCloud SSL付きだと$30.24するので高い。お試し期間もなさそう
exoscale apps リンク先エラー。PaaSは止めたっぽい
nodejitsu 最低でも月$25と高い。お試し期間もなし
JSApp.US Web IDE?ブラウザでコーディングする必要有り。プロプライエタリとして使うにはセキュリティが心配。
NAE(CN) リンク先エラー。どっちにしろ中国だし
RoseHosting 最低でも月$19.95するので高い。2CPUs 1GB RAM 30GB SSDは魅力的だが。SSL使えるかは不明

0

AWS EC2にnode.jsサーバーを構築しよう

環境は以下の通り。
・サーバーはAWS EC2
・サーバーのOSはAMI Linux
・ローカルのOSはCentOS 6.5
・ローカルにはnode.jsサーバーのソースコードがあるものとする

説明内容は以下の通り。
・サーバーにnode.jsをインストール
・サーバーにgitサーバーを立てる
・サーバーにnode.jsサーバーのソースコードをデプロイする
・サーバーのnode.jsサーバーを起動する

AWS EC2でのサーバー構築方法はsimtterができるまで(サーバー取得と設定)を参照して欲しい。
EC2からのリモートコントロールにはTera Termを使う。
Tera Termの使い方はchat.acができるまで(statusnetをインストール)を参照して欲しい。
ただし、今回インストールするOSはubuntuではなくAMI Linuxとする。
AMI Linuxのユーザー名はec2-userになる。

では、OSまで入った前提で以降を説明する。
 

node.jsをインストール

参考文献:
node.jsをyumでインストールする(centos6.5)
Amazon Linux AMI に関するよくある質問
Tera Termから下記コマンド実行。

sudo yum install nodejs npm --enablerepo=epel

 

gitサーバーを立てる

参考文献:自宅のCentOSにGitサーバを構築してみた
Tera Termから下記コマンド実行。

sudo yum install git git-deamon git-all xinetd
sudo chkconfig xinetd on
sudo vi /etc/xinetd.d/git-daemon

 
内容は

service git
{
        disable         = no
        socket_type     = stream
        wait            = no
        user            = nobody
        server          = /usr/libexec/git-core/git-daemon
        server_args     = --base-path=/home --export-all --user-path=ec2-user --syslog --inetd --verbose
        log_on_failure  += USERID
}

 
xinetd を起動

sudo service xinetd start

 
リポジトリ作成

cd ~
mkdir test.git
cd test.git
git --bare init --shared

 
gitグループを作成してユーザをそのグループに追加

sudo groupadd git
sudo usermod -G wheel,git ec2-user
sudo chown -R root:git ~/test.git

 

node.jsサーバーをデプロイする

ローカルのgitにはすでに何らかのソースコードをコミットしているものとする。
ローカルから下記コマンドを実行する。
 
リモートリポジトリの登録

git remote add origin ssh://ec2-user@[Public IP]:/~/test.git

※ [Public IP]はAWS EC2に割り当てられたIPアドレスを指定する。独自ドメインを設定している場合はドメイン名でも良い。
※ ssh 用の鍵ペアはTera Termでの通信で使う鍵と同じである。
 
リモートリポジトリに push

git push origin master

 

node.jsサーバーを起動する

サーバーで下記コマンドを実行する。
 
クローン作成

cd ~
git clone test.git

 
node.jsサーバー起動

cd test
node app.js

※ app.jsは自分が起動したいソースファイルに適宜読み替えること。


0

画像を縮小してからサーバーへアップロードしよう

サーバーへ画像をアップロードしたい場合、クライアント-サーバー間の通信量軽減のため、クライアント側で画像を縮小してからサーバーへアップロードする方法を説明する。

前提条件は下記の通り。
・環境はherokuのMEANからCloudinaryに画像をアップロードしようで作った環境を流用する。
・画像の縮小にHTML5のcanvasを使う。

処理の流れは下記の通り。
・画像ファイル選択
・FileReaderで画像ファイルを読み込む
・ng-srcにFileReaderで読み込んだ画像ファイルのパスを設定する
・imgタグのonloadコールバックでcanvasを使って元の画像を縮小した画像を生成する
・angular-file-uploadで縮小した画像をサーバーへアップロード
※なお、画像の縮小表示にはCSSの縮小処理を使う。(CSSで縮小した画像は表示にしか使えないので、canvasで画像を縮小してサーバーにアップロードする)

手順は下記の通り。

CSSで縮小表示

参考文献:CSSだけで画像のリサイズを処理する(IE6に対応させて、しかもハイクオリティ)完全版
下記のCSSを作成する。

img {
	-ms-interpolation-mode: bicubic;
}

.image-large {
	max-width: 256px;
	max-height: 256px;
}
* html body .image-large {
	width: expression(this.width >= this.height ? "256px" : "auto");
	height: expression(this.width <= this.height ? "256px" : "auto");
}

 

htmlにimgタグを追加

参考文献:ngSrc | AngularJS 1.2 日本語リファレンス | js STUDIO
herokuのMEANからCloudinaryに画像をアップロードしようのHTMLを下記の通りimgタグを追加する。
ポイントは、
・classにはcssで定義したimage-largeを設定する
・srcはng-srcを使い、controllerでファイルパスを変更できるようにする
・オリジナルのディレクティブimg-onloadを設定する

<div ng-controller="MyCtrl">
  <input type="file" ng-file-select="onFileSelect($files)">
  <img class="image-large" ng-src="{{imgSrc}}" img-onload="ImageToBlob($img)"></img>
</div>

 

ディレクティブを追加

参考文献:
AngularJS 入門 – Directive
AngularJS – Image “onload” event
herokuのMEANからCloudinaryに画像をアップロードしようのjavascriptにディレクティブimg-onloadを追加する。
これによりimgタグのng-srcの値を変更した際のファイルロード完了後にimg-onloadで設定した関数が呼ばれる。
javascript

angular.module('myApp')
  .directive('imgOnload', [ '$parse', function ($parse) {
    return {
      link: function(scope, element, attrs) {
        element.bind("load" , function(e) {
          var func = $parse(attrs.imgOnload);
          func(scope, {$img:this});
        });
      }
    }
  }]);

 

コントローラーに縮小処理を追加

参考文献:
Get image data in JavaScript?
Canvasで描画した画像を送信してサーバに保存する
さらにコントローラーも下記の通りに変更する。

angular.module('myApp')
  .controller('MyCtrl', function($scope, $upload) {
    var imageType = '';
    $scope.onFileSelect = function($files) {
      for (var i = 0; i < $files.length; i++) {
        var file = $files[i];
        imageType = file.type;
        var fileReader = new FileReader();
        fileReader.onload = function(event) {
          $scope.imgSrc = event.target.result;
          if(!$scope.$$phase) {
            $scope.$apply();
          }
        };

        fileReader.readAsDataURL(file);
      }
    };

    var toBlob = function(image, imageType) {
      var canvas = document.createElement('canvas');
      if(canvas && canvas.getContext) {
        canvas.width = image.width;
        canvas.height = image.height;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0, image.width, image.height);
        var base64 = canvas.toDataURL(imageType);
        var bin = atob(base64.replace(/^.*,/, ''));
        var buffer = new Uint8Array(bin.length);
        for(var i = 0; i < bin.length; i++) {
            buffer[i] = bin.charCodeAt(i);
        }

        return new Blob([buffer.buffer], {type: imageType});
      }
      return null;
    };

    $scope.ImageToBlob = function($img) {
      var blob = toBlob($img, imageType);
      $upload.upload({
        url: 'api/myapi', // server側API例
        file: blob
      }).progress(function(evt) {
        console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
      }).success(function(data, status, headers, config) {
        console.log(data);
      });
    };
  });

0

herokuのMEANからCloudinaryに画像をアップロードしよう

前提は下記の通り。
1.yeoman angular-fullstakでMEAN(MongoDB+Express+AngularJS+Node.js)アプリを作成済み
 この方法はYEOMANを使ってMEAN(MongoDB+Express+AngularJS+Node.js)を作成しようを参照すること
2.アプリをherokuにデプロイ済み
 この方法はangular-fullstackをherokuにデプロイする方法を参照すること
 
手順は下記の通り。
 

1.herokuにCloudinaryアドオンを追加

heroku toolbeltから下記コマンドを実行

heroku addons:add cloudinary --app foo

※fooはherokuのアプリ名
 

2.Gruntfile.jsに環境変数CLOUDINARY_URLを設定

herokuのConfig variablesを参照して環境変数CLOUDINARY_URLをGruntfile.jsに設定する。
下記はGruntfile.jsの例。

module.exports = function (grunt) {
  var localConfig;
  try {
    localConfig = //require('./server/config/local.env');
      {
        DOMAIN: 'http://localhost:9000',
        SESSION_SECRET: "foo-secret",

        FACEBOOK_ID: 'app-id',
        FACEBOOK_SECRET: 'secret',

        TWITTER_ID: 'app-id',
        TWITTER_SECRET: 'secret',

        GOOGLE_ID: 'app-id',
        GOOGLE_SECRET: 'secret',

        CLOUDINARY_URL: 'cloudinary://999999999999999:XXXXXXXXXXXXXXXXXXXXXXXXXXXX@xxxxxxxxx',

        // Control debug level for modules using visionmedia/debug
        DEBUG: ''
      };
  } catch(e) {
    localConfig = {};
  }

※本来環境変数は./server/config/local.env.jsに設定すれば良いはずだが、なぜかgruntが読み込んでくれないのでGruntfile.jsに直接記入している。
 

3.npmでcloudinaryをインストール

端末から下記コマンドを実行

npm install cloudinary --save

 

4.bowerでangular-file-uploadをインストール

参考文献:angular-file-upload#Yeoman with bower automatic include
端末から下記コマンドを実行

bower install ng-file-upload --save
bower install ng-file-upload-shim --save

 

5.npmでconnect-multipartyをインストール

参考文献:How to get uploaded file in Node.js Express app using angular-file-upload
connect-multipartyをインストールする。
端末から下記コマンドを実行

npm install connect-multiparty --save

 

6.bower.jsonのangular-file-uploadの読み込み順序を変更

参考文献:angular-file-upload#install
bower.jsonの依存順序を下記通りにする。
bower.jsonの依存順序を変更することでgrunt serve時にclient/index.htmlのbower管理のjavascript読み込み順序も変更される。

{
  "dependencies": {
    "ng-file-upload-shim": "~1.6.12",
    "angular": ">=1.2.*",
    "ng-file-upload": "~1.6.12"
  }
}

 

7.client側ソースコード追加

参考文献:angular-file-upload#usage
client側ソースコードを追加する。
サンプルとほぼ同じだがソースコード例を示す。
HTML例

<div ng-controller="MyCtrl">
  <input type="file" ng-file-select="onFileSelect($files)">
</div>

※scriptの読み込みはclient/index.htmlで行われる。
 
app.js

angular.module('myApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'btford.socket-io',
  'ui.router',
  'ui.bootstrap',
  'pascalprecht.translate',
  'angularFileUpload'  //追加
])

 
javascript例

angular.module('myApp')
  .controller('MyCtrl', function($scope, $upload) {
    $scope.onFileSelect = function($files) {
      for (var i = 0; i < $files.length; i++) {
        var file = $files[i];
        $scope.upload = $upload.upload({
          url: 'api/myapi', // server側API例
          file: file
        }).progress(function(evt) {
          console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
        }).success(function(data, status, headers, config) {
          console.log(data);
        });
      }
    };
  });

 

8.server側ソースコード追加

参考文献:
angular-file-upload node.js example
How to get uploaded file in Node.js Express app using angular-file-upload
Cloudinary using with node.js
server側ソースコードを追加する。
yo angular-fullstack:endpointでserverにmyapiという名前のAPIを追加した場合の例を示す。
ファイル:server/api/myapi/index.js

'use strict';

var express = require('express');
var controller = require('./myapi.controller');

var router = express.Router();

var multipart = require('connect-multiparty');
var multipartMiddleware = multipart();

router.post('/', multipartMiddleware, controller.create);

module.exports = router;

 
ファイル:server/api/myapi/myapi.controller.js

'use strict';

var fs = require('fs');
var cloudinary = require('cloudinary');

exports.create = function(req, res) {
  if(req.files.file) {
    var stream = cloudinary.uploader.upload_stream(function(result) {
      return res.send(200);
    });
    fs.createReadStream(req.files.file.path).pipe(stream);
  } else {
    return res.send(400);
  }
};

 
以上で完成。
clientで画像が選択されるたびにserver経由でcloudinaryに画像ファイルがアップロードされる。
 


3

BootstrapのサンプルNestingのドロップダウンが開かない

BootstrapのサンプルNestingのドロップダウンが開かないので、その修正箇所を説明する。
 
【修正前】
http://getbootstrap.com/components/#btn-groups-nested

<div class="btn-group">
  <button type="button" class="btn btn-default">1</button>
  <button type="button" class="btn btn-default">2</button>

  <div class="btn-group">
    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
      Dropdown
      <span class="caret"></span>
    </button>
    <ul class="dropdown-menu" role="menu">
      <li><a href="#">Dropdown link</a></li>
      <li><a href="#">Dropdown link</a></li>
    </ul>
  </div>
</div>

 
【修正後】

<div class="btn-group">
  <button type="button" class="btn btn-default">1</button>
  <button type="button" class="btn btn-default">2</button>

  <div class="btn-group">
    <div class="dropdown">
      <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
        Dropdown
        <span class="caret"></span>
      </button>
      <ul class="dropdown-menu" role="menu">
        <li><a href="#">Dropdown link</a></li>
        <li><a href="#">Dropdown link</a></li>
      </ul>
    </div>
  </div>
</div>

 
要は

<div class="btn-group">

の直下に

<div class="dropdown">

が必要。
 


0