<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="http://organizeseries.com/"
	>

<channel>
	<title>Node.js &#8211; HTML5Experts.jp</title>
	<atom:link href="/tag/node-js/feed/" rel="self" type="application/rss+xml" />
	<link>https://html5experts.jp</link>
	<description>日本に、もっとエキスパートを。</description>
	<lastBuildDate>Sat, 07 Jul 2018 03:14:05 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.7.19</generator>
	<item>
		<title>Node.jsでSlack Command Botをつくってみよう</title>
		<link>/girlie_mac/22535/</link>
		<pubDate>Fri, 03 Mar 2017 00:00:22 +0000</pubDate>
		<dc:creator><![CDATA[Tomomi Imura]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ECMAScript]]></category>
		<category><![CDATA[ES6]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Slack]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[bot]]></category>
		<category><![CDATA[海外]]></category>

		<guid isPermaLink="false">/?p=22535</guid>
		<description><![CDATA[こんにちは。ごぶさたしています。以前の執筆から１年ちょっとになるのですが、その当時はInternet of Things(IoT)について書いたのですが、最近では市場がある程度まで到達したからでしょうか、それとも脆弱性の...]]></description>
				<content:encoded><![CDATA[<p>こんにちは。ごぶさたしています。以前の執筆から１年ちょっとになるのですが、その当時はInternet of Things(IoT)について書いたのですが、最近では市場がある程度まで到達したからでしょうか、それとも脆弱性の問題を問われることが多くなったせいでしょうか、話題は少し落ち着いてきたかに思われます。さて今ホットな話題は何でしょうか、ということで今回はChat Botsについて書いてみようと思います。</p>

<h3>E-Commerceから Conversational Commerceへ</h3>

<p>ここ最近話題になることが多いAIやBotsですが、私の周りではConversational interface、Conversational UXなどという言葉が去年からたびたび使われるようになっているようです。</p>

<p>これはAmazon Alexaなどのデバイスや、Facebook Messengerなどチャットアプリケーションなどの対話型テクノロジーをいかに活用しその使い勝手をよくするか、ということなのですが、必ずしもアプリケーションのUIデザインそのものを述べているわけではなく、既存のサービスを延長することを指していることも多いでしょう。例えば、今まではモバイル上のアプリケーションのみで車を呼べていたUberが、<a href="https://newsroom.uber.com/messengerlaunch/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Facebook Messenger のチャットからも車をを呼べるような機能</a>を加えたり、Slack上でTaco Bellからタコスをオーダーできる<a href="https://www.tacobell.com/feed/tacobot" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">TacoBot</a>、というのもが挙げられます。</p>

<h3>Slack Botを書いてみよう</h3>

<p>さて、というわけで何かBotを書いてみたいと思いませんか？ここはNode.jsでSlack botを作成する方法を紹介したいと思います。</p>

<p>このチュートリアルでは、ディベロッパー向けのHTTPステータスコードのルックアップができるスラッシュ・コマンドを作ってみます。ここでは私が５年ほど前に何気なく作って、Mashableなどで紹介され思わぬ反響を得てしまった<a href="http://http.cat/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">HTTP Status Cats</a>を使ってみます。具体的には、Slack上でユーザが、<code>/httpstatus [code]</code> （例えば <code>/httpstatus 404</code>）と入力すると、そのステータスコードの意味と猫が一緒に表示される、という簡単なbotです。</p>

<p>まず試してみたい方は、<a href="http://www.girliemac.com/slack-httpstatuscats/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">HTTP Status Cats command for Slack</a>を自分がアドミン権限のあるのチャットチームにインストールしてみてください。</p>

<p><img src="/wp-content/uploads/2017/02/slack-httpstatuscats.gif" alt="slack-httpstatuscats gif animation" width="640" height="437" class="aligncenter size-full wp-image-22558" /></p>

<p>さて、このチュートリアルは２つのパートに分けられます。</p>

<ol>
<li>スラッシュ・コマンドを書いて、自分のSlackチームにインストールする
<li>OAuthを使ってボットを<a href="https://slack.com/apps" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Slack&#8217;s App Directory</a>などで誰もがインストールできるようにする</ol>

<p></ol></p>

<p>とりあえず動くbotを書いてみたい、と思う方は１だけ試してみてで十分ですが、botをみんなにシェアしたい方は２も読んでみてください。</p>

<p><a href="https://github.com/girliemac/slack-httpstatuscats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ソースコード</a>と実際のボットのインストールボタンは両方GitHubにあります。では始めましょう！</p>

<h2>1 プライベートなスラッシュコマンドボットの作成</h2>

<p>ここで作るのは、Slackの公式な用語でいうところの<a href="https://api.slack.com/custom-integrations" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Custom Integrations</a>というもので、自分のチャットグループ専用のプライベートなbot、もしくはいわゆるAppとして発表する前にドライ・ランを行うことを指します。アカウントを持っていない方はまずサインアップしてから始めましょう。</p>

<h3>1.1 スラッシュコマンドの設定</h3>

<p>ログインして、<a href="https://my.slack.com/services/new/slash-commands" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">my.slack.com/services/new/slash-commands</a>でコマンドを選びます。ここでは<code>/httpstatus</code>と入力し<strong>Add Slash Command Integration</strong>ボタンをクリックして次のステップへ進みます。</p>

<p>Tokenなどの欄がありますが現時点では、(1) Command、 (2) URL、 (3) Method、の３つが必要になります。</p>

<p><img src="/wp-content/uploads/2017/02/slack-config-custom-integration.png" alt="slack-config-custom-integration" width="431" height="640" class="aligncenter size-full wp-image-22561" srcset="/wp-content/uploads/2017/02/slack-config-custom-integration.png 431w, /wp-content/uploads/2017/02/slack-config-custom-integration-202x300.png 202w, /wp-content/uploads/2017/02/slack-config-custom-integration-139x207.png 139w" sizes="(max-width: 431px) 100vw, 431px" /></p>

<p>(1)には、<code>/httpstatus</code>、(3)には、<code>POST</code>、そして(2)のURLは次のように設定してください。</p>

<p>開発中に使用するURLを取得するには<a href="https://ngrok.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ngrok</a>を使ってみましょう。いろいろなツールがあるのですが、これは自分のローカルホストをパブリックURLとしてトンネルできるというとても便利なツールなので私のイチオシです。開発途中にデプロイすることなく、Webhookが手軽に使えます。自分のローカルホスト、たとえば  <code>http://localhost:3000/</code> をつかったままOAuthのテストもできるのです。（注：よく聞かれるのですが、ngrokはあくまでも開発ツールですのでプロダクションには適していません。デプロイメントに関しては最後の章を読んでください）</p>

<p><a href="https://ngrok.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://ngrok.com/</a>から自分のマシンにngrokをインストールしたら、ターミナルで自分の使いたいポート番号（このチュートリアルでは 3000）を設定します。</p>

<p></p><pre class="crayon-plain-tag">$ ngrok http 3000</pre><p></p>

<p>すると下のスクリーンショットのように、Forwardingアドレスが取得できるので、そのURL（例えば<a href="https://71f03962.ngrok.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://71f03962.ngrok.io/</a>)をSlackセッティングの、上のスクリーンショットで示された(2)の欄で使います。</p>

<p><img src="/wp-content/uploads/2017/02/ngrok.png" alt="ngrok" width="640" height="349" class="aligncenter size-full wp-image-22538" srcset="/wp-content/uploads/2017/02/ngrok.png 640w, /wp-content/uploads/2017/02/ngrok-300x164.png 300w, /wp-content/uploads/2017/02/ngrok-207x113.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>すべての設定が終えたらSaveボタンを押します。&#8221;Your settings have been saved!&#8221;のメッセージが画面上部に現れるのを確認してください。</p>

<h3>1.2 Node.js を使ってレスポンスを書く</h3>

<p>基本的にbotは、ユーザがSlackインターフェイス上でコマンドを実行した際HTTP POST（または設定次第では GET)によって指定先のURLにメッセージが届け、プログラムでその応答をユーザに返す、という作業になります。</p>

<p>たとえばそのユーザが<code>/httpstatus 302</code>というコマンドを送信した場合、指定URLに送られるデータは次のようになります。</p>

<p></p><pre class="crayon-plain-tag">command=/httpstatus
text=302
response_url=https://hooks.slack.com/commands/1234/5678
…</pre><p></p>

<p>Botは、これに対する応答をユーザに返します。この場合はユーザが尋ねているステータス302の定義と<a href="https://http.cat/302" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">この猫</a>を返しましょう。</p>

<p>ではそのコードを書いてみましょう。</p>

<p>まず、<strong>Express.js</strong>と<strong>body-parser</strong>をインストールします。</p>

<p></p><pre class="crayon-plain-tag">$ npm install express body-parser --save</pre><p></p>

<p><strong>index.js</strong>で、<code>express</code>のインスタンスを作り、先ほどngrokで設定したポート番号、3000でサーバを始動します。</p>

<p></p><pre class="crayon-plain-tag">'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const server = app.listen(3000, () =&gt; { 
console.log('Express server listening on port %d in %s mode', server.address().port, app.settings.env);});</pre><p></p>

<p>次はHTTP POSTルートメソッドで、コマンドを扱います。</p>

<p></p><pre class="crayon-plain-tag">app.post('/', (req, res) =&gt; {
 let text = req.body.text;
 // ここでbotを書きます
});</pre><p></p>

<p>ここで<code>text</code>の値を取得します。HTTP Status botの場合、<code>/httpstatus</code> コマンドの値、例えば&#8221;404&#8243;が <code>text</code>の値になります。同時に、ユーザが数字以外を入力した際にエラーメッセージを送るなどのエラーチェックもしておきましょう。</p>

<p></p><pre class="crayon-plain-tag">if(! /^\d+$/.test(q.text)) { // not a digit
 res.send('Error: enter a valid status code, such as 200');   
 return;
}</pre><p></p>

<p>このエラーは、ユーザだけにプライベートに送信されるメッセージでチャットそのものには表示されません。</p>

<p>エラーがない場合は、コマンドに対する応答をJSONとしてレスポンスします。</p>

<p></p><pre class="crayon-plain-tag">let data = {
 response_type: 'in_channel', 
 text: '302: Found',
 attachments:[{
   image_url: 'https://http.cat/302.jpg'
 }]
};
res.json(data);</pre><p></p>

<p><code>response_type</code>を<code>in_channel</code>とすることで応答はチャットメンバー全員に見えるように送信されます。デフォルトはその逆の<code>ephemeral</code>で、コマンドを送ったユーザのみに表示されます。</p>

<p>このコマンドと応答は次のようになります。</p>

<p><img src="/wp-content/uploads/2017/02/slack-command.png" alt="slack-command" width="640" height="491" class="aligncenter size-full wp-image-22540" srcset="/wp-content/uploads/2017/02/slack-command.png 640w, /wp-content/uploads/2017/02/slack-command-300x230.png 300w, /wp-content/uploads/2017/02/slack-command-207x159.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>このサンプルコードでは、応答をわかりやすくハードコードで示してありますが、実際はストリングなどは別のファイルに定義しています。下のスクリーンショットのように存在しないHTTPステータスに対してのエラーメッセージも定義しましょう。実際のコードは<a href="https://github.com/girliemac/slack-httpstatuscats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ソースコード</a>を参照してください。</p>

<p><img src="/wp-content/uploads/2017/02/slack-command-private.png" alt="slack-command-private" width="640" height="60" class="aligncenter size-full wp-image-22539" srcset="/wp-content/uploads/2017/02/slack-command-private.png 640w, /wp-content/uploads/2017/02/slack-command-private-300x28.png 300w, /wp-content/uploads/2017/02/slack-command-private-207x19.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>ディスプレイはボーダー色などのカスタマイズが可能です。詳しくはSlackドキュメンテーションの<a href="https://api.slack.com/docs/message-formatting" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Basic message formatting</a>を参照してください。</p>

<p>次のステップでは、このボットを自分のチャットグループ以外に配布するために必要な認証とコードのデプロイについてです。</p>

<h2>2. Slack Botのディストリビューション</h2>

<p>この「Custom Integration」をインストール可能な「App」にするには、コードのデプロイをして他のチャットにもインストールできるようにせねばならないのですが、そのためにはあといつくかのステップが必要になります。</p>

<h3>2.1 Appセットアップ</h3>

<p>まず、自分のAppを申請し、クライアントIDやシークレットなどのクレデンシャルを取得します。<a href="https://api.slack.com/apps" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://api.slack.com/apps</a>で<strong>Create an App</strong>ボタンをクリックしてください。</p>

<p><img src="/wp-content/uploads/2017/02/slack-create-app.png" alt="slack-create-app" width="614" height="640" class="aligncenter size-full wp-image-22542" srcset="/wp-content/uploads/2017/02/slack-create-app.png 614w, /wp-content/uploads/2017/02/slack-create-app-288x300.png 288w, /wp-content/uploads/2017/02/slack-create-app-199x207.png 199w" sizes="(max-width: 614px) 100vw, 614px" /></p>

<p>このフォームにはいくつもの欄があり少しわかりづらいのですが、スラッシュコマンドのbotには次の3つが最低必要になります。</p>

<ul>
<li><strong>Basic Information</strong> (at <a href="https://api.slack.com/apps/YOUR_APP_ID/general%29" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://api.slack.com/apps/YOUR_APP_ID/general)</a></li>
<li><strong>OAuth &amp; Permissions</strong> (at …/YOUR_APP_ID/oauth)</li>
<li><strong>Slash Commands</strong> (at …/YOUR_APP_ID/slash-commands)</li>
</ul>

<h3>2.1.1 API Keyを.envファイルに保管</h3>

<p>ここで取得した<code>Client ID</code>、<code>Client secret</code>、<code>Verification token</code>は <strong>.env</strong> ファイルに別に保管してbotのメインのコードから切り離すことを推奨します。gitを使う場合は、このファイルを <strong>.gitignore</strong> ファイルに付け加えるのを忘れずに。</p>

<p></p><pre class="crayon-plain-tag">SLACK_CLIENT_ID=12345XXXXX.09876XXXXX 
SLACK_CLIENT_SECRET=535d2f9....
SLACK_VERIFICATION_TOKEN=42P829U...</pre><p></p>

<h3>2.1.2 Foremanを使う</h3>

<p>他にも手段はありますが、<a href="https://heroku.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Heroku</a>にデプロイするために私は<a href="http://strongloop.github.io/node-foreman/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node Foreman</a>を使っています。Foremanを使うには、npmを使ってglobalフラッグでインストールしてください。</p>

<p></p><pre class="crayon-plain-tag">$ npm install -g foreman</pre><p></p>

<p>アプリケーションの Root に <code>.procfile</code> を作成し、この一行を加えます。</p>

<p></p><pre class="crayon-plain-tag">web: node index.js</pre><p></p>

<p>index.jsを実行するには <code>node index.js</code>の代わりに次のコマンドを使います。</p>

<p></p><pre class="crayon-plain-tag">$ nf start</pre><p></p>

<h2>2.2 ユーザの認証</h2>

<p>Slackは認証には<a href="https://oauth.net/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">OAuth</a>を使っています。実際には自分でOAuthを実装しなくても、<a href="https://api.slack.com/docs/slack-button" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Slack ボタン</a>を使えば簡単に認証できるようになっています。</p>

<p>公式のドキュメンテーションのダイアグラムに手を加えて、流れをわかりやすくするためにGIFアニメーションにしてみました。</p>

<p><img src="/wp-content/uploads/2017/02/slack-oauth-1.gif" alt="slack-oauth gif animation" width="640" height="387" class="aligncenter size-full wp-image-22559" /></p>

<p>ここでの実際のフローは次のようになります。</p>

<ol>
<li>ウェブページを作成し、認証ボタンを置く。ユーザがボタンをクリックするとパラメータがSlackに送信される(ユーザは認証ページにリダイレクトされる)。
<li>Node appには、SlackからGETで10分だけ有効な仮のコードが送られる。
<li>アクセストークンを得るために <a href="https://api.slack.com/methods/oauth.access" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">oauth.access</a> API使い認証コードをPOSTする。Node app側から`200 OK`を受け取り次第、このプロセスが完了。
<li>オプションとして、このトークンを使ってSlackの他のAPIにもアクセス。例えば、認証後、ユーザをhttps://team-name.slack.comにリダイレクトするなど。/
</ol>

<h3>2.2.1 Slack ボタンの設定</h3>

<p>Slackボタンを使うには、まずウェブページを作成してください。私の場合はこのNode Appとは切り離した別のHTMLページを作成し、<a href="http://www.girliemac.com/slack-httpstatuscats/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">GitHub Pages</a>にホストしました。</p>

<p>次にボタンを設定しましょう。 <a href="https://api.slack.com/docs/slack-button" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://api.slack.com/docs/slack-button</a> に行き、<strong>Add the Slack button</strong> までスクロールして下さい。</p>

<p><img src="/wp-content/uploads/2017/02/slack-generate-button.png" alt="slack-generate-button" width="640" height="268" class="aligncenter size-full wp-image-22543" srcset="/wp-content/uploads/2017/02/slack-generate-button.png 640w, /wp-content/uploads/2017/02/slack-generate-button-300x126.png 300w, /wp-content/uploads/2017/02/slack-generate-button-207x87.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>このボタン作成ツールの<strong>Commends</strong>のチェックボックスをチェックします。</p>

<p>上で示したフローの４を実行したい場合は、このGETパラメータを下のように変更します。</p>

<p></p><pre class="crayon-plain-tag">&lt;a href="https://slack.com/oauth/authorize?scope=commands+team%3Aread&amp;client_id=your_client_id"&gt;</pre><p></p>

<p>ここで<code>scope</code>に着目してみてください。<code>commands</code>の他に<code>team:read</code>(コロンは<strong>%3A</strong>とエスケープ)が必要になります。詳しくは<a href="https://api.slack.com/docs/oauth-scopes" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">OAuth scopes on the Slack API docs</a>で。</p>

<p><img src="/wp-content/uploads/2017/02/slack-button.png" alt="slack-button" width="139" height="40" class="aligncenter size-full wp-image-22548" /></p>

<h3>2.2.2 トークンの発行</h3>

<p>さて、Nodeコードに戻りましょう。仮のコード(<code>req.query.code</code>)をGETで取得するためにまたExpress.jsを使います。</p>

<p>何でもよいのですがここでは<code>/slack</code> routeを使いましょう。この場合ngrokのURL は <code>http://71f03962.ngrok.io/slack</code> のようになります。Slack App設定ページ（https://api.slack.com/apps/YOUR_APP_ID/oauth）の、<strong>OAuth &amp; Permissions</strong> セクションにある、<em>Redirect URL</em>の欄にはこのURLを設定してください。</p>

<p>仮の<code>code</code>を取得されたら、それを自分のAPIクレデンシャルとともにPOSTで送って、トークンと交換します。POSTするためにここではNode.jsのHTTPリクエストクライアントである、<code>Request</code>を使いましょう。</p>

<p></p><pre class="crayon-plain-tag">$ npm install request --save</pre><p></p>

<p>仮の<code>code</code>を取得し、それを<code>token</code>と交換するコードが下になります。</p>

<p></p><pre class="crayon-plain-tag">const request = require('request');

app.get('/slack', function(req, res){
 let data = {form: {
   client_id: process.env.SLACK_CLIENT_ID,
   client_secret: process.env.SLACK_CLIENT_SECRET,
   code: req.query.code
 }};

 request.post('https://slack.com/api/oauth.access', data, function (error, response, body) {
   if (!error &amp;&amp; response.statusCode == 200) {
     // おしまい！
     // ここからはオプションでチーム情報を取得
     let token = JSON.parse(body).access_token; // Auth token
   } ...</pre><p></p>

<h3>2.2.3 オプショナル： ユーザをチームURLにダイレクトする</h3>

<p>認証が済んだらそこで終えてもよいのですが、この画面でユーザを置き去りにするのはあまりよいUXとはいえないので、チームページにリダイレクトしてみましょう。</p>

<p>リダイレクトURLのサブドメインとなるチーム名は<a href="https://api.slack.com/methods/team.info" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">team.info</a>APIで取得できます。</p>

<p>このAPIを使うにはトークンが必要なので前述のコードでの、トークンにアクセスする箇所に下のコードを追加します。</p>

<p></p><pre class="crayon-plain-tag">...
request.post('https://slack.com/api/team.info', {form: {token: token}}, function (error, response, body) {
 if (!error &amp;&amp; response.statusCode == 200) {
   let team = JSON.parse(body).team.domain;
   res.redirect('http://' +team+ '.slack.com');
 }
});</pre><p></p>

<p>これで API からチーム名(<code>team.domain</code>)が返されました。最終的にこれを使ってチームURLにリダイレクトしてできあがり！</p>

<p>このチュートリアルでは簡素化したコードを使いましたが、<a href="https://github.com/girliemac/slack-httpstatuscats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">全ソースコードはGitHubで</a>見てみてください。</p>

<h2>2.3 サーバにデプロイする</h2>

<p>最後にデプロイしておしまいです。APIクレデンシャルの<strong>env vars</strong>設定を忘れないように！私はHerokuを使っているのですが、Herokuの場合、<code>heroku config</code>コマンドを使います。例えば、<code>heroku config:set API_KEY=123456</code>というふうに設定してください。</p>

<p>Slackの設定で画面で指定したngrok URLを、デプロイ先のURLに変更するのもお忘れなく。</p>

<p>さて、プロセスが少し面倒ですが、コード自体は簡単だったと思います。もし何か面白いボットを作った際にはぜひ教えてくださいね！</p>

<p><img src="/wp-content/uploads/2017/02/slack-worked.png" alt="slack-worked" width="200" height="200" class="aligncenter size-full wp-image-22546" srcset="/wp-content/uploads/2017/02/slack-worked.png 200w, /wp-content/uploads/2017/02/slack-worked-150x150.png 150w" sizes="(max-width: 200px) 100vw, 200px" /></p>
]]></content:encoded>
			</item>
		<item>
		<title>Electronプログラミング入門 — インストールからミニブラウザ構築まで</title>
		<link>/yosuke_furukawa/20841/</link>
		<pubDate>Thu, 15 Sep 2016 00:00:50 +0000</pubDate>
		<dc:creator><![CDATA[古川陽介]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Chromium]]></category>
		<category><![CDATA[Electron]]></category>
		<category><![CDATA[Node.js]]></category>

		<guid isPermaLink="false">/?p=20841</guid>
		<description><![CDATA[連載： Web技術でアプリ開発2016 (6)Electronとは、デスクトップクライアントを作るためのフレームワークです。クロスプラットフォームで動作することをサポートしているため、Electronで作ったアプリケーシ...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-based-apps-2016/" class="series-391" title="Web技術でアプリ開発2016" data-wpel-link="internal">Web技術でアプリ開発2016</a> (6)</div><div id="attachment_20845" style="width: 650px" class="wp-caption aligncenter"><img src="/wp-content/uploads/2016/09/electron-eyecatch-640x361.png" alt="Electron" width="640" height="361" class="size-large wp-image-20845" srcset="/wp-content/uploads/2016/09/electron-eyecatch.png 640w, /wp-content/uploads/2016/09/electron-eyecatch-300x169.png 300w, /wp-content/uploads/2016/09/electron-eyecatch-207x117.png 207w" sizes="(max-width: 640px) 100vw, 640px" /><p class="wp-caption-text">Electron</p></div>

<p>Electronとは、デスクトップクライアントを作るためのフレームワークです。クロスプラットフォームで動作することをサポートしているため、Electronで作ったアプリケーションはMac、Windows、Linuxの環境でも動作します。Atomと呼ばれる GitHub社製のエディタがあります。 ElectronはAtomを作る際にフレームワークとして作られました。以前はAtomShellと呼ばれていましたが、Electronとして名前を変更し、2016年にはversion 1.0がリリースされるまでに成長しました。</p>

<p>ElectronはJavaScript / HTML / CSSを使ってクライアントアプリケーションを作成します。中のアーキテクチャはChromiumとNode.jsで作られており、Web開発の技術を使ってデスクトップアプリケーションを構築することが可能です。</p>

<p>ElectronはCheng Zhao氏 (以降zcbenz)が開発したフレームワークですが、zcbenz氏は実際Electronの開発前にNW.js(旧 NodeWebkit) と呼ばれるフレームワークのコントリビューターでした。ElectronはNW.jsと非常によく似たフレームワークですが、いくつか技術的に異なるポイントが有ります。決定的な違いは、Chromiumの組み込み方の違いです。ElectronはライブラリとしてChromiumを組み込んでいるのに対して、NW.jsはChromiumをforkしたプロジェクトを使っています。Chromiumは非常に進化が早いプロダクトなので、forkして持つよりもアップデートを考えると効率的です。</p>

<p>とはいえ、NW.jsとElectronの技術的な部分以外は違いはそこまでありません。Atom開発当初にNW.jsが多少不安定だったために新しいプロダクトとして立ち上げたという側面もあるそうです。詳しくは<a href="https://github.com/electron/electron/blob/master/docs/development/atom-shell-vs-node-webkit.md" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">AtomShellとNodeWebkitの違い</a>に詳しく記載されています。</p>

<h2>Electronの特徴</h2>

<p>いくつか特徴があるので紹介します。</p>

<h3>Electronのランタイム</h3>

<p>Electron そのものはただのランタイムライブラリです、Node.jsにおける<code>node</code>コマンドのようなもので、<code>electron</code>コマンドでエントリポイントとなるJavaScriptを実行します。</p>

<p><code>electron</code>コマンドは<a href="http://electron.atom.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">公式サイト</a>からダウンロードすることもできますが、Node.jsのパッケージモジュールである、<code>npm</code>コマンドを使ってインストールすることも可能です。</p>

<p></p><pre class="crayon-plain-tag">$ npm install electron -g</pre><p></p>

<p>※ 以前までは <code>npm install electron-prebuilt</code>からインストールする必要がありましたが、最近は<code>npm install electron</code>でinstallできるようになりました。</p>

<p>これで<code>electron</code>コマンドが有効になるので、そのコマンドを使ってデスクトップアプリケーションを起動させます。アプリケーションを実際に書くのは後述します。</p>

<h3>Electron / Browser間でモジュールを共有</h3>

<p>Electronアプリを構築すると、シームレスにブラウザからNode.jsのコードを呼ぶことが可能です。そのため、下記のようなコードを実行することもできます。　</p>

<p></p><pre class="crayon-plain-tag">// script tag から
&lt;script&gt;
// 自分のローカルファイル読みこんだり
const fs = require(‘fs’);
fs.readFile(‘foo/bar/baz’, (err, data) =&gt; {
  console.log(data);
});
&lt;/script&gt;

&lt;script&gt;
// 外部プロセスを呼んだり
const cp = require(‘child_process’);
cp.exec(‘ls -l’, (err, stdout) =&gt; {
  console.log(stdout);
});
&lt;/script&gt;</pre><p></p>

<p>ただし、この方法を使った場合、DOM-based XSSが発生すると、任意のコマンドだったり、ファイルが操作できてしまう結果になるため、プロダクションでElectronを活用する場合は注意が必要です。</p>

<p><a href="http://utf-8.jp/public/2016/0307/electron.pdf" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">参考資料: Electronの倒し方</a></p>

<h2>Hello World</h2>

<p>一旦Electronを起動してみましょう。Electronを動かすだけなら Node.jsはbuilt-inされているので不要ですが、npmがある方が便利なのでNode.jsをインストールしておきましょう。Node.jsは <a href="https://nodejs.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">公式サイト</a>からダウンロードできます。Macであれば、<code>brew</code>でもインストール可能です。</p>

<p>Node.jsがインストールされたらElectronをインストールしてみましょう。下記の通りです。</p>

<p></p><pre class="crayon-plain-tag">$ npm install electron -g</pre><p></p>

<p>適当なフォルダを作成し、<code>package.json</code>、<code>main.js</code>、<code>index.html</code>を作成します。
package.jsonは下記の通りに作成します。</p>

<p></p><pre class="crayon-plain-tag">{
  "name": "electron-intro",
  "version": "0.0.1",
  "main": "main.js"
}</pre><p></p>

<p><code>main.js</code>を記述します。</p>

<p></p><pre class="crayon-plain-tag">const {app, BrowserWindow} = require('electron');

// window objectがGCされないようにするために、globalに定義する
let win;

function createWindow () {
  win = new BrowserWindow({width: 800, height: 600});

  win.loadURL(`file://${__dirname}/index.html`);

  win.on('closed', () =&gt; {
    // windowがクローズされたら null にして削除
    win = null;
  });
}

app.on('ready', createWindow);

app.on('window-all-closed', () =&gt; {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () =&gt; {
  if (win === null) {
    createWindow();
  }
});</pre><p></p>

<p>最後に表示するための<code>index.html</code>を記述します。</p>

<p></p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Hello Electron!&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello Electron!&lt;/h1&gt;
    Node version: &lt;script&gt;document.write(process.versions.node)&lt;/script&gt;,
    Chrome version: &lt;script&gt;document.write(process.versions.chrome)&lt;/script&gt;,
    Electron version: &lt;script&gt;document.write(process.versions.electron)&lt;/script&gt;.
  &lt;/body&gt;
&lt;/html&gt;</pre><p></p>

<p>これだけでHello Worldは一旦完成です。 下記のようなファイル構成になっていることを確認してください。</p>

<pre><code>.
├── index.html
├── main.js
└── package.json
</code></pre>

<p>Electronを起動させてみましょう。</p>

<p></p><pre class="crayon-plain-tag">$ electron .</pre><p></p>

<p>下記のようなウィンドウが出たら完成です。</p>

<div id="attachment_20847" style="width: 650px" class="wp-caption aligncenter"><img src="/wp-content/uploads/2016/09/hello_world-640x477.png" alt="Hello, World実行結果" width="640" height="477" class="size-large wp-image-20847" srcset="/wp-content/uploads/2016/09/hello_world.png 640w, /wp-content/uploads/2016/09/hello_world-300x224.png 300w, /wp-content/uploads/2016/09/hello_world-207x154.png 207w" sizes="(max-width: 640px) 100vw, 640px" /><p class="wp-caption-text">Hello, World実行結果</p></div>

<h2>PhotonKitを使ってミニマムブラウザを作る</h2>

<p>これだけだと味気ないので、PhotonKitを使ってミニマムブラウザを作ってみましょう。ブラウザを作ると言っても、 Chromiumを内包しているElectronであれば、Chromiumの機能を借りてくるだけなのでそこまで難しくはないです。</p>

<p>PhotonKitはCSSフレームワークの1つです。BootstrapやMaterial Design Liteのようなclass setを持っています。Mac のクライアントのようなアプリケーションを作るためのデザインテンプレートになっています。</p>

<div id="attachment_20849" style="width: 650px" class="wp-caption aligncenter"><img src="/wp-content/uploads/2016/09/photon-640x353.png" alt="Photon" width="640" height="353" class="size-large wp-image-20849" srcset="/wp-content/uploads/2016/09/photon.png 640w, /wp-content/uploads/2016/09/photon-300x165.png 300w, /wp-content/uploads/2016/09/photon-207x114.png 207w" sizes="(max-width: 640px) 100vw, 640px" /><p class="wp-caption-text">Photon</p></div>

<p>まずは<code>package.json</code>を作りましょう。</p>

<p></p><pre class="crayon-plain-tag">{
  "name": "electron-mini-browser",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "electron index.js"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "electron": "^1.3.5"
  }
}</pre><p></p>

<p><code>npm start</code>でElectronを起動できるようにしておくことと、<code>dependencies</code>に<code>electron</code>をインストールしておきましょう。下記の方法でpackage.jsonに記述しつつ、インストールさせることが可能です。　</p>

<p></p><pre class="crayon-plain-tag">$ npm install electron --save</pre><p></p>

<p>次に<code>index.js</code>を作成します。</p>

<p></p><pre class="crayon-plain-tag">'use strict';
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

let mainWindow = null;

app.on('window-all-closed', function() {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('ready', function() {
  mainWindow = new BrowserWindow({
    width: 800, 
    height: 600,
  });
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});</pre><p></p>

<p>実際のページ(index.html)を作成します。</p>

<p></p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;link rel="stylesheet" href="./css/photon.css"&gt;
    &lt;script src="./js/main.js"&gt;&lt;/script&gt;
    &lt;title&gt;Hello Electron!&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class="window"&gt;
    &lt;header class="toolbar toolbar-header"&gt;
    &lt;h1 class="title"&gt;Hello Electron!&lt;/h1&gt;
    &lt;div class="toolbar-actions"&gt;
     &lt;!-- リロードボタン --&gt;
     &lt;button id="reload" class="btn btn-default"&gt;
       &lt;span class="icon icon-arrows-ccw icon-text"&gt;&lt;/span&gt;
       Reload
     &lt;/button&gt; 
     &lt;!-- 戻るボタン --&gt;
     &lt;button id="back" class="btn btn-default"&gt;
       &lt;span class="icon icon-left icon-text"&gt;&lt;/span&gt;
       Back
     &lt;/button&gt; 
     &lt;!-- 進むボタン --&gt;
     &lt;button id="forward" class="btn btn-default"&gt;
       &lt;span class="icon icon-right icon-text"&gt;&lt;/span&gt;
       Forward
     &lt;/button&gt; 
     &lt;!-- URL バー --&gt;
     &lt;input type="text" id="urlbar" class="form-control" placeholder="URL" value="https://github.com/"&gt;
     &lt;!-- お気に入りボタン --&gt;
     &lt;button id="favorite" class="btn btn-default"&gt;
       &lt;span class="icon icon-star icon-text"&gt;&lt;/span&gt;
       Favorite
     &lt;/button&gt; 
    &lt;/div&gt;
    &lt;/header&gt;
      &lt;div class="window-content"&gt;
        &lt;div class="pane-group"&gt;
          &lt;div class="pane-sm sidebar"&gt;
            &lt;!-- お気に入りリスト --&gt;
            &lt;ul id="fav-list" class="list-group"&gt;
            &lt;/ul&gt;
          &lt;/div&gt;
            &lt;!-- Webページ表示領域 --&gt;
          &lt;webview class="pane" id="webview" src="https://www.github.com/" autosize="on" style="height:100%;"&gt;&lt;/webview&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;footer class="toolbar toolbar-footer"&gt;
    &lt;h1 class="title"&gt;Footer&lt;/h1&gt;
    &lt;/footer&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre><p></p>

<p>ページの中のボタンに動きを与えるため、<code>js/main.js</code>を作成します。</p>

<p></p><pre class="crayon-plain-tag">document.addEventListener('DOMContentLoaded', () =&gt; {
  const webview = document.getElementById('webview');
  const reloadButton = document.getElementById('reload');
  const backButton = document.getElementById('back');
  const forwardButton = document.getElementById('forward');
  const favoriteButton = document.getElementById('favorite');
  const urlbar = document.getElementById('urlbar');
  const favList = document.getElementById('fav-list');

  // webview表示の時にurlbarの値を変える
  webview.addEventListener('load-commit', ({ url, isMainFrame }) =&gt; {
    if (isMainFrame) {
      urlbar.value = url;
    }
  });

  // urlbarでEnterキーを押したら遷移する
  urlbar.addEventListener('keypress', (e) =&gt; {
    if (e.key === 'Enter') {
      webview.setAttribute('src', urlbar.value);
    }
  });

  // 更新ボタンをクリックしたらwebviewをリロードする
  reloadButton.addEventListener('click', () =&gt; {
    webview.reload();
  });

  // 戻るボタンをクリックしたらwebviewを戻る
  backButton.addEventListener('click', () =&gt; {
    webview.goBack();
  });
  
  // 進むボタンをクリックしたらwebviewを進ませる
  forwardButton.addEventListener('click', () =&gt; {
    webview.goForward();
  });
  
  // お気に入りボタンをタップしたらリストにURLを追加する
  favoriteButton.addEventListener('click', () =&gt; {
    const listItem = document.createElement('li');
    const listContent = document.createElement('p');
    listItem.setAttribute('class', "list-group-item");
    listItem.setAttribute('data-url', urlbar.value);
    listContent.textContent = urlbar.value;
    listItem.appendChild(listContent);
    favList.appendChild(listItem);
    listItem.addEventListener('click', () =&gt; {
      const url = listItem.getAttribute('data-url');
      webview.setAttribute('src', url);
    });
  });
});</pre><p></p>

<p>最後にPhotonKitのCSSとFontセットを <a href="http://photonkit.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ダウンロード</a>しておきます。
下記のようなディレクトリ構成になります。</p>

<p></p><pre class="crayon-plain-tag">.
├── css
│   ├── photon.css
│   └── photon.min.css
├── fonts
│   ├── photon-entypo.eot
│   ├── photon-entypo.svg
│   ├── photon-entypo.ttf
│   └── photon-entypo.woff
├── index.html
├── index.js
├── js
│   └── main.js
└── package.json</pre><p></p>

<p>全てを終えたら、<code>npm start</code>コマンドで起動します。下記のようなブラウザが出現できたら完成です。</p>

<div id="attachment_20846" style="width: 650px" class="wp-caption aligncenter"><img src="https://github.com/yosuke-furukawa/electron-intro/raw/master/img/electron-mini-browser.gif" alt="Electron+Photonで作ったミニブラウザ" width="640" height="374" class="size-large wp-image-20846" /><p class="wp-caption-text">Electron+Photonで作ったミニブラウザ</p></div>

<h2>プラットフォーム/ライブラリの特徴</h2>

<table>
<thead>
<tr>
  <th>項目</th>
  <th>説明</th>
</tr>
</thead>
<tbody>
<tr>
  <td>対応プラットフォーム</td>
  <td>Windows, Linux, OS X</td>
</tr>
<tr>
  <td>コードベースは（ほぼ）完全に統一できるか？</td>
  <td>ほぼ統一できる</td>
</tr>
<tr>
  <td>UIを記述する言語</td>
  <td>JavaScript, HTML, CSS</td>
</tr>
<tr>
  <td>UIはネイティブかウェブか</td>
  <td>基本はウェブだが、OSネイティブの機能(タスクバー等)は一部利用できる</td>
</tr>
<tr>
  <td>パフォーマンス</td>
  <td>基本ウェブなのでウェブページと同等、ただし自分でオフラインキャッシュの仕組みを持てるので、改善可能</td>
</tr>
<tr>
  <td>ネイティブな機能を呼び出せるか？</td>
  <td>Node.jsからOSの機能を呼び出せる。</td>
</tr>
</tbody>
</table>

<p>以下に、上の表を補足します。</p>

<h3>対応プラットフォーム</h3>

<p>Electronの公式サポートはWindows, Linux, OS Xの３つのみです。今のところはデスクトップアプリケーションのためのフレームワークなので、モバイル対応は全く考えられていません。</p>

<h3>コードベースは (ほぼ) 完全に統一できるか？</h3>

<p>ほぼ統一できます。ただし、OS X用の機能であったり、Windows専用の機能は用意されていて、プラットフォームによって呼び出し可能なAPIや受信可能なEventが若干異なります。クロスプラットフォームでOSの専用の機能を利用する上で、気になる方は一度<a href="http://electron.atom.io/docs/api/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">API</a>を確認すると良いでしょう。</p>

<h3>UIを記述する言語</h3>

<p>Hello Worldやミニブラウザを作って分かる通り、HTML/JS/CSSで書きます。ネイティブ部分の呼び出しもNode.jsなので、 JavaScriptになります。</p>

<p>JavaScriptのレイヤはNode.js部分とChromium部分でv8(JavaScript Engine)が動くので、ほぼ同一の動きをします、ただし、Node.jsとChromiumでv8のバージョンは若干違う可能性があります。基本的な部分は変わりませんが、ES2016の対応状況を見てもらえれば分かる通り、新しいJavaScriptの機能面で違いはあります。</p>

<div id="attachment_20844" style="width: 650px" class="wp-caption aligncenter"><img src="/wp-content/uploads/2016/09/compat-640x155.png" alt="ES2016の対応状況" width="640" height="155" class="size-large wp-image-20844" srcset="/wp-content/uploads/2016/09/compat.png 640w, /wp-content/uploads/2016/09/compat-300x73.png 300w, /wp-content/uploads/2016/09/compat-207x50.png 207w" sizes="(max-width: 640px) 100vw, 640px" /><p class="wp-caption-text">ES2016の対応状況</p></div>

<h3>UIはネイティブかウェブか</h3>

<p>基本はウェブです。デスクトップアプリケーションとして、タスクトレイに常駐させたい場合やOSネイティブのダイアログボックスを使いたい場合は専用のAPIがあるので、それを利用して機能を作ることも可能です。</p>

<h3>パフォーマンス</h3>

<p>Electronは基本的にウェブなので、ネイティブのデスクトップアプリと比較するとそこまで高速ではありません。</p>

<p>Electronのアプリを起動させると、Chromium用のプロセスが3つ起動し、Node.js用のメインプロセスが1つ起動します。合計4プロセスが常駐することになります。言ってしまえばブラウザそのものを起動しつつ、バックグラウンドにNode.jsを起動しているのと同様です。富豪的な方法で実現していると言えるでしょう。</p>

<p>そのため、描画パフォーマンス等はブラウザの機能がそのまま利用できますが、実行効率が良いモデルとはいえません。パフォーマンスにシビアなアプリケーションを作る場合は Electron ではなく、ネイティブの機能を使って作る方が良いでしょう。</p>

<h3>ネイティブな機能を呼び出せるか？</h3>

<p>ElectronからNode.jsを呼び出せばファイル操作や外部プロセスコールといった基本的な機能は呼び出せます。また、C言語等で書かれたネイティブライブラリもNode.jsアドオンがあれば呼び出すことが可能です。ただし、ネイティブライブラリに関しては、通常のnpmでインストールするだけでは利用できないことがあります。Nodeのバージョンがelectronのbuilt-inで保持しているバージョンと異なる場合にうまく利用できないことが多いです。ネイティブライブラリを利用したい場合は下記のガイドを確認することを推奨します。</p>

<p><a href="https://github.com/electron/electron/blob/master/docs/tutorial/using-native-node-modules.md" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ネイティブライブラリ利用ガイド</a></p>

<h2>まとめ</h2>

<p>Electronの概要とアプリ構築の方法、ウェブ用プラットフォームを構築する上での共通質問項目を記述しました。Electron はこれまでのAngularJSやReact等で構築されたSingle Page Applicationを活用して、デスクトップアプリケーションを構築するのに非常に向いています。</p>

<p>まだv1.0がリリースされて日が浅いこともあり、そこまでノウハウが溜まっているわけではありません。特にセキュリティやパフォーマンスなどの改善は今進んでいますが、ユーザーからのノウハウも溜める必要があると感じています。</p>

<p>もしまだ触っていない方がいらっしゃるのであれば、<a href="http://yosuke-furukawa.hatenablog.com/entry/2015/12/31/223045" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">electronica</a>などのチュートリアルもあるので是非やってみてください。</p>

<p>また今回の<a href="http://nodefest.jp/2016/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node学園祭</a>では、Electron作者のzcbenzもゲストスピーカーとして登壇する予定です。Electronの今後の話が聞けると思います。</p>

<p>一緒にElectronを盛り上げていきましょう。</p>
]]></content:encoded>
		
		<series:name><![CDATA[Web技術でアプリ開発2016]]></series:name>
	</item>
		<item>
		<title>今からでも間に合う！Node.js v4＆v5は何が変わったか？</title>
		<link>/yosuke_furukawa/17791/</link>
		<pubDate>Tue, 01 Dec 2015 00:00:18 +0000</pubDate>
		<dc:creator><![CDATA[古川陽介]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[ES2015]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>

		<guid isPermaLink="false">/?p=17791</guid>
		<description><![CDATA[Node.js v4リリースに向けて とうとうメジャーバージョンアップにされたNode.jsである、Node.js v4がリリースされました。今回はこのNode.js v4がこれまでのNode.js v0.12やv0.1...]]></description>
				<content:encoded><![CDATA[<h1>Node.js v4リリースに向けて</h1>

<p>とうとうメジャーバージョンアップにされたNode.jsである、Node.js v4がリリースされました。今回はこのNode.js v4がこれまでのNode.js v0.12やv0.10と比較してどう違うのかを解説します。また、最新ではv5もリリースされていますので、合わせてお伝えしていきます。</p>

<h1>なんでいきなりv4なのか</h1>

<p>おそらく一番最初に抱く感想は、v0.10とかv0.12みたいな数字からv1.0を飛ばして、なんでいきなりv4.0がリリースされたのかという疑問だと思います。これにはio.jsというプロダクトが関係しています。</p>

<p>2014年の年末、io.jsというプロダクトが発表され、2015年の初めにv1.0がリリースされました。io.jsというのは Node.js のforkで別リポジトリによって実装されたプロダクトです。io.jsの詳細は<a href="http://yosuke-furukawa.hatenablog.com/entry/2014/12/25/104300" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">筆者のブログ</a>を確認してください。io.jsがリリースされた後、週次リリースという早いサイクルでリリースするモデルを確立し、8カ月間もの間リリースを続けてきました。結果として、io.jsはv3までリリースされています。</p>

<p>紆余曲折を経た後に、Node.jsはこのio.jsと再び統合されることになります。統合までの経緯について、興味がある読者の方は<a href="https://speakerdeck.com/yosuke_furukawa/dousitekounatuta-node-dot-jstoio-dot-jsfalsefen-lie-totong-he-falsexing-fang-korekaradoujin-hua-siteikufalseka" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">筆者のYAPCでの発表資料</a>を参考にしてください。</p>

<p>統合される際にio.jsの既存バージョンと競合しないようにした結果、Node.jsはv1.0からv3.0を飛ばしていきなりv4.0がリリースされることになりました。</p>

<h1>Node.js v4とNode.js v0.12の違い</h1>

<p>Node.js v4はio.js v3が元になっています。つまり、io.js v1からv3までにはいった機能が、そのままNode.js v4では利用可能です。筆者がまとめた内容を元に、Node.js v4で入った新機能や違いを解説します。<em>また、v5で入った機能は本資料の最後にまとめて紹介します。</em></p>

<h2>JavaScript Syntax/Built-in Object の違い</h2>

<p>Node.js v4は内部的にJavaScript実行エンジンである、V8のv4.5を採用しています。これにより下記の部分でJavaScript全体の機能が進化しました。</p>

<ul>
<li>ES2015のサポート範囲拡大</li>
<li>StrongScript等のJavaScriptの新機能対応</li>
<li>Intlオブジェクトのサポートによる国際化対応</li>
</ul>

<p>一つ一つ説明していきます。</p>

<h3>ES2015のサポート範囲拡大</h3>

<p>ES2015のサポート範囲がNode.js v0.12と比較して拡大しました。具体的には下記の機能がデフォルトで有効になっています。</p>

<ul>
<li>let/const等のblock scope (&#8220;use strict&#8221;が必須)</li>
<li>class構文(&#8220;use strict&#8221;が必須)</li>
<li>Map/Set/WeakMap/WeakSetといった新しいコレクションオブジェクト</li>
<li>Generator構文</li>
<li>Binary/Octalリテラル</li>
<li>Symbolオブジェクト</li>
<li>Template Stringリテラル</li>
<li>String.prototype.repeat</li>
<li>Symbol.toStringTag</li>
<li>拡張Objectリテラル</li>
<li>Unicodeリテラル</li>
<li>アロー関数</li>
</ul>

<p>ES2015の構文のサポート範囲が変わったことで今までNode.jsで記述していたJavaScriptは変わっていくだろうと予測されます。具体的にどのように変わるのか、<code>EventEmitter</code>を拡張して<code>MyEventEmitter</code>を作るというケースを参考に考えてみましょう。</p>

<p>v0.12までは<code>EventEmitter</code>を拡張する場合、以下のように記述していました。</p>

<p></p><pre class="crayon-plain-tag">'use strict';
var events = require('events');
var util = require('util');
var EventEmitter = events.EventEmitter;
var MyEventEmitter = function(data) {
  this.data = data;
};

// EventEmitterを継承する
util.inherits(MyEventEmitter, EventEmitter);

MyEventEmitter.prototype.intervalCheck = function() {
  var originalData = this.data;
  var _this = this;
  setInterval(function (){
    if (originalData !== _this.data) {
      _this.emit('change', new Date() + ': ' + _this.data);
      originalData = _this.data;
    }
  }, 1000);
};

module.exports = MyEventEmitter;</pre><p></p>

<p>この<code>MyEventEmitter</code>は、<code>intervalCheck</code>という内部的に<code>data</code>が変わっていないかどうかを1秒ずつ調べて変更があったら、<code>change</code>イベントを発火させるという動きを行っています。v4からは下記のように<code>ES2015</code>の構文を使って下記のように記述します。</p>

<p></p><pre class="crayon-plain-tag">'use strict';
// 変更されないようにするためにconstで変数宣言する
const events = require('events');
const EventEmitter = events.EventEmitter;

// util.inheritsではなく、class構文とextendsを使う
class MyEventEmitter extends EventEmitter {
  constructor(data) {
    this.data = data;
  }
  intervalCheck() {
    // 基本的にletで変数宣言する
    let originalData = this.data;
    // アロー関数を利用する。
    setInterval(() =&gt; {
      if (originalData !== this.data) {
        // Template String Literalで変数の情報を文字列に埋め込む
        this.emit('change', `${new Date()}: ${this.data}`);
        originalData = this.data;
      }
    }, 1000);
  }
}

module.exports = MyEventEmitter;</pre><p></p>

<p>ひとつ前のコードと比較すると大分コードが短く、シンプルに記述できるようになっていることがわかります。
<a href="http://yosuke-furukawa.hatenablog.com/entry/2015/11/05/192521" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node.js v5.0では、<code>Spread call</code>や<code>new.target</code>などの新しい機能</a>も使えるようになっています。ただし、現時点ではまだ下記のような機能は有効になっていません。</p>

<ul>
<li>default params</li>
<li>rest params</li>
<li>tail call optimization</li>
<li>Proxy/Reflect</li>
<li>modules</li>
</ul>

<h3>StrongScript等のJavaScriptの新機能対応</h3>

<p>StrongScriptというのはES2015のコードを強制させ、JavaScriptの実行エンジンにとって親和性の高いコードを生成するための新しい試みです。 もともとJavaScriptには<code>use strict</code>をつけることで厳格モードというモードになりますが、この厳格モードをより強くしたモードです。</p>

<p>StrongScriptは<code>readability</code>(読みやすさ)の向上と<code>predictability</code>(予測しやすさ) の向上という2つの事を狙った試みであり、特に<code>predictability</code>が上がることでJavaScriptの実行エンジンフレンドリーになり、全体的に性能が上がる事が期待されます。</p>

<p></p><pre class="crayon-plain-tag">"use strong";

function foo() {
  //下記のコードは、StrongModeでは全てエラーになります。
  // varを利用する代わりにlet、もしくはconstを使いましょう。
  var obj1 = 'aaa'; // Restrict var (use let or const instead)

  // argumentsの代わりに、restパラメータを使いましょう
  let args = arguments; // Restrict arguments (use Rest Params …args)

  // ==の代わりに、strict equal ===を使いましょう
  if (obj1 == 'aaa') {}  // Restrict == (use strict equal ===)

  // if とか for のあとにうっかり ; を置くけど、空の式を置くのは間違いの元なのでやめましょう。
  if (obj1 === 'aaa'); // Restrict empty if and for

  // for-inの代わりに、for-ofを使いましょう。
  for (let n in [1,2,3]) // Restrict for-in (use for-of instead)

  // delete構文を使うなら、Map/Setを代わりに使いましょう。
  delete obj1.key // Restrict delete operator (use Map/Set instead)

  // undefined っていう名前の変数に何かを入れるのはやめましょう。
  let undefined = 'aaa'; // Restrict undefined binding

  // 未定義のプロパティに、勝手にアサインするのはやめましょう。
  let obj3 = { foo : 'aaa'};
  obj3.bar = '123'; // Restrict undefined property access 
}</pre><p></p>

<p>本コードを実行するためには、<code>"use strong"</code>ディレクティブを書くだけじゃなく、実行時のオプションとして<code>--strong_mode</code>オプションが必要です。</p>

<p></p><pre class="crayon-plain-tag">$ node --strong_mode weak.js</pre><p></p>

<p>StrongScriptの詳細については、以前まとめた資料があるのでご一読ください。<a href="https://speakerdeck.com/yosuke_furukawa/strongmode-and-soundscript" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">StrongScriptについて</a></p>

<h3>Intlオブジェクトのサポートによる国際化対応</h3>

<p>ECMAScript国際対応用のAPIが利用できるようになっています。ECMA-402と呼ばれる仕様で通称ESIntlと呼ばれています。これを利用することで例えば時刻表期の国際化対応や通貨表記の国際化対応ができるようになります。</p>

<p>ただしデフォルトでは利用できないので、ビルドするときにオプションを指定する必要があります。下記のようにオプションを指定して実行すると有効になります。</p>

<p></p><pre class="crayon-plain-tag">$ ./configure --with-intl=full-icu --download=all
$ make
$ make install</pre><p></p>

<p>これを使うと新たに<code>Intl</code>というbuilt-inオブジェクトがglobal空間に生成されて使えるようになります。文字列比較処理から見ていきましょう。文字列比較をする際に日本語にはカタカナやひらがなといった表記の違いとまた句読点といった区切りが存在します。</p>

<p>読みやすくするためには表記の違いを使い分けたり、句読点をしかるべき所に入れる必要がありますが、プログラミングで文字列をソートしたり、検索するときなどはこれらの区別が不要なときも多いです。こういう場合に利用するのが<code>Intl.Collator</code>というクラスです。利用方法は以下のとおり。</p>

<p></p><pre class="crayon-plain-tag">// 文字列比較系

console.log(new Intl.Collator('ja', {
  sensitivity: 'base'  // 文字の派生系を同値と見なします、カタカナとひらがなの区別もつけません。Ex.か==が , ぴ == ヒ, あ != い
}).compare('ハハ', 'パパ'));  // 0 つまり同値

console.log(new Intl.Collator('ja', {
  sensitivity: 'accent'  // 文字で発音が同じものを同値と見なします。baseの中で派生系は不等という指定です。Ex. は == ハ , ぴ != ひ, あ != い
}).compare('ハハ', 'はは'));  // 0 つまり同値

console.log(new Intl.Collator('ja', { 
  sensitivity: 'accent', 
  ignorePunctuation: true // 句読点を無視するかどうか、デフォルトはfalse 
}).compare('わたしは、ふるかわです', 'わたしはふるかわです')); // 0 つまり同値</pre><p></p>

<p>また、この他にも時刻表記(<code>Intl.DateTimeFormat</code>)や通貨表記(<code>Intl.Numberformat</code>)といった数字の表記を、国際化対応することが可能です。</p>

<p></p><pre class="crayon-plain-tag">// 日付フォーマット系
var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
console.log(new Intl.DateTimeFormat('en').format(date)); // 12/20/2012
console.log(new Intl.DateTimeFormat('ja').format(date)); // 2012/12/20
console.log(new Intl.DateTimeFormat('ja-JP-u-ca-japanese').format(date)); // 平成24/12/20

// 数値フォーマット系
console.log(new Intl.NumberFormat('en').format(10000000)); // 10,000,000
console.log(new Intl.NumberFormat('ja', {style: 'currency', currency: 'JPY'}).format(10000000)); //￥10,000,000
console.log(new Intl.NumberFormat('en', {style: 'percent', }).format(0.230232)); // 23.023%</pre><p></p>

<h2>API 部分の変更</h2>

<p>実は今回のNode.js v4では、JavaScriptの文法部分での進化と内部でのリファクタリングは進んでいるものの、APIとして新しい機能はそこまで増えていません。追加された機能はいくつかありますが、主だったものとしては下記の通りです。</p>

<ul>
<li>Buffer#indexOfの追加</li>
<li>Simple Stream Constructionの追加</li>
<li>os.homeDir()が追加</li>
</ul>

<p>一つ一つ説明していきます。</p>

<h3>Buffer API の変更</h3>

<p>Node.jsには、<code>Buffer</code>と呼ばれるbinaryデータを扱うためのAPIが存在します。普通にNode.jsのライブラリを利用するときにはあまり意識していないかもしれませんが、<code>fs</code>から<code>readFile</code>した時の戻り値や<code>http</code>のPOSTメッセージを受け取った時は基本的に<code>Buffer</code>型の値を受け取っています。JavaScriptにはArrayBufferという似たオブジェクトが存在しますが、v4.0.0からはこの<code>ArrayBuffer</code>を<code>Buffer</code>が継承する形になりました。これによって<code>ArrayBuffer</code>で利用可能なAPIは<code>Buffer</code>でも利用可能になります。</p>

<p>またそれとは別に<code>Buffer</code>に<code>indexOf</code>メソッドが増えました。これによりわざわざ文字列に変換しなくても<code>Buffer</code>としてそのまま文字列を含んでいるかどうかを検索することができるようになります。</p>

<p></p><pre class="crayon-plain-tag">var fs = require('fs');

// ./foo.txt =&gt; yosuke furukawa
fs.readFile(__dirname + '/foo.txt', function(err, buf){
  console.log(buf.indexOf('furukawa')); // 7
});</pre><p></p>

<p>これまでは<code>indexOf</code>などの文字列検索をする場合、一旦<code>toString</code>を使って文字列を変換する必要がありました。これはNode.js内部では、<code>Buffer</code>型から<code>string</code>型への変換をしている上にさらにHeapメモリを消費してしまうため、メモリ効率的にも速度効率的にも悪いというデメリットが有りました。今回のv4.0からは<code>indexOf</code>メソッドが<code>Buffer</code>で利用可能になっているため、このデメリットを解消しています。また内部的にもこのメソッドを利用することで速度の向上が見られます。</p>

<p>また、<code>ArrayBuffer</code>で再実装された影響で以下のような<code>ArrayBuffer</code>をそのままコンストラクタに入れられるようになりました。</p>

<p></p><pre class="crayon-plain-tag">const Buffer = require('buffer').Buffer;
const ab = new ArrayBuffer(16);
var buf = new Buffer(ab); // Buffer constructor accepts ArrayBuffer.

console.log(buf instanceof Uint8Array); // true
console.log(buf instanceof Buffer); // true

buf.writeUInt32BE(0x61626364, 0);

console.log(buf.toString()); //abcd</pre><p></p>

<h3>Simple Stream Construction の追加</h3>

<p>Streamの作成が簡単になりました。今までStreamを作るためには、目的のStreamを継承して、<code>TransformStream</code>であれば <code>_transform</code>のようなメソッドを拡張して実現する必要がありました。 これをより簡単にしたものが<a href="https://github.com/rvagg/through2" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">through2</a>に代表されるヘルパライブラリでしたが、簡単にいえばこのthrough2がなくても Node.js v4.0ではStreamを作るのが簡単にできるようになりました。</p>

<p></p><pre class="crayon-plain-tag">// これまで
var Transform = require('stream').Transform;
var util = require('util');

util.inherits(MyTransform, Transform);

function MyTransform(opts){
    Transform.call(this, opts);
}

MyTransform.prototype._transform = function(chunk, encoding, callback){
  // ここで変換して
  ...
  // pushする
  this.push(chunk);
};

MyTransform.prototype._flush = function(done){
  // 最後に何かしたければここで flush する
};</pre><p></p>

<p></p><pre class="crayon-plain-tag">// これから
var stream = require('stream');
var transform = new stream.Transform({
  transform: function(chunk, encoding, next) {
    // ここで変換して
    ...
    // pushする
    this.push(chunk);
  },
  flush: function(done) {
    // 最後に何かしたければここでflushする
  }
});</pre><p></p>

<p>詳しくはこちらの<a href="https://nodejs.org/api/stream.html#stream_simplified_constructor_api" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">API資料</a>を参考にしてください。</p>

<h3>os.homedir機能の追加</h3>

<p>HOMEディレクトリを取得するための関数<code>os.homedir</code>が追加されました。</p>

<p></p><pre class="crayon-plain-tag">const os = require('os');
console.log(os.homedir()); // /User/yosuke</pre><p></p>

<p>今までは<code>HOME</code>の環境変数から取得したり色々な方法で解決していましたが、これからは<code>os.homedir</code>関数で取得することができます。</p>

<h1>性能向上</h1>

<p>v8のバージョンアップ、http_parserモジュールのバージョンアップにより、これまでよりも性能が8%ほど向上しています。まだ、前述した<code>Buffer</code>の改善により、メモリ効率的にも向上が見られます。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2015/11/perf1.png" data-wpel-link="internal"><img src="/wp-content/uploads/2015/11/perf1-640x344.png" alt="perf1" width="640" height="344" class="aligncenter size-large wp-image-17805" srcset="/wp-content/uploads/2015/11/perf1.png 640w, /wp-content/uploads/2015/11/perf1-300x161.png 300w, /wp-content/uploads/2015/11/perf1-207x111.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>

<p><a href="https://raygun.io/blog/2015/09/nodejs-and-io-js-are-now-one/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">この結果</a>を見ていただくと分かる通り、Requests/secondベースでhttpの速度が8%程改善されています。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2015/11/perf2.png" data-wpel-link="internal"><img src="/wp-content/uploads/2015/11/perf2-640x300.png" alt="perf2" width="640" height="300" class="aligncenter size-large wp-image-17806" srcset="/wp-content/uploads/2015/11/perf2.png 640w, /wp-content/uploads/2015/11/perf2-300x141.png 300w, /wp-content/uploads/2015/11/perf2-207x97.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>

<p>また<a href="http://apmblog.dynatrace.com/2015/09/05/all-you-need-to-know-about-node-js-4-0/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">こちらの結果</a>では、性能改善はもとより、メモリ効率的にも効率化されていることがわかります。</p>

<p><code>http</code>だけではなく、<code>fs</code>に<code>writev</code>書き込みをするモードが追加されたり、<code>require</code>関数の無駄な処理を省いて高速化するといったことが行われています。</p>

<h1>Deprecated になったAPI</h1>

<p>残念ながら、<code>deprecated</code>になったAPIも存在します。下記のAPIは今後は使わないようにしてください。</p>

<ul>
<li>domains(エラーはキャッチできるものの、キャッチしたエラーでできることが少ない)</li>
<li>fs.exist/existSync(存在確認後に別プロセスから消されることもあるのでRace Conditionに弱い)</li>
<li>util.isObject/isNumberなどのisXXX系(一部既存のライブラリの関数と異なる振る舞いがあり、使いにくいため)</li>
</ul>

<h1>Node.js v4のNode.js v0.12は互換性はあるのか</h1>

<p>JavaScriptのレイヤは互換性があります。ほとんどのモジュールはそのまま動くでしょう。ただし、CやC++のnative module をバインディングして作っているライブラリはV8がバージョンアップされたことにより、内部のABIの互換が崩れているため動かなくなります。それらのモジュールが、まだ<code>v4.0</code>に対応するまではアップグレードをしても動きません。</p>

<p>C/C++のnativeモジュールの問題で動いていないモジュールを確認するためにはこの<code>issue</code>を確認していただくとよいと思います。</p>

<p><a href="https://github.com/nodejs/node/issues/2798" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">https://github.com/nodejs/node/issues/2798</a></p>

<h1>Node.js v4とNode.js v5の違い</h1>

<p>Node.js v4はLTSというリリースしてから2年半サポートするサポートポリシーがついていますが、Node.js v5にはついていません。つまり、 Node.js v5は次のバージョンが出たらサポートされなくなります。
今のところ、 LTSの対象になるのは偶数のバージョンとされています。ただし、偶数のバージョンが必ず<code>LTS</code>というわけではなく、今のところ偶然そういうバージョンになっているというのが正しい状態なので、きちんとLTSかどうかを把握するためには、バージョン番号の他にLTS識別名(<code>Argon</code>や<code>Boron</code>等の元素名)が付いていることを確認したほうがよいです。</p>

<p>確認するには、公式サイトのトップページに書いてある情報から識別するのが簡単です。</p>

<div id="attachment_17808" style="width: 650px" class="wp-caption aligncenter"><a href="https://html5experts.jp/wp-content/uploads/2015/11/official.png" data-wpel-link="internal"><img src="/wp-content/uploads/2015/11/official-640x434.png" alt="公式サイト" width="640" height="434" class="size-large wp-image-17808" srcset="/wp-content/uploads/2015/11/official.png 640w, /wp-content/uploads/2015/11/official-300x203.png 300w, /wp-content/uploads/2015/11/official-207x140.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></a><p class="wp-caption-text">公式サイト</p></div>

<p>v0.10からv0.12までのLTSプランについては下記の図を参考にしてください。</p>

<div id="attachment_17809" style="width: 650px" class="wp-caption aligncenter"><a href="https://html5experts.jp/wp-content/uploads/2015/11/schedule.png" data-wpel-link="internal"><img src="/wp-content/uploads/2015/11/schedule-640x358.png" alt="スケジュール" width="640" height="358" class="size-large wp-image-17809" srcset="/wp-content/uploads/2015/11/schedule.png 640w, /wp-content/uploads/2015/11/schedule-300x168.png 300w, /wp-content/uploads/2015/11/schedule-207x116.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></a><p class="wp-caption-text">スケジュール</p></div>

<p>Node.js v5には、<code>npmのバージョン3</code>や<code>ALPN</code>サポートがついています。ただし、v5.0は前述したとおりLTSバージョンではないので次のバージョンからはサポートされません。最新の機能を使ってみたい方向けのバージョンになります。実際に最新の機能を使ってみたい場合は、Node.js v5を利用するのがいいかと思います。</p>

<h1>まとめ</h1>

<p>Node.js v4.0がリリースされました。実際にすでにプロダクションで使っているところもある安定したバージョンになります。また、Node.js v4.0には新しいJavaScriptであるES2015の機能が使えるようになっています。APIにもいくつか変更があるのと、性能が改善されています。</p>

<p>また、LTSということで、今から2年半、2018年4月まではメンテナンスされる予定です。v5もすでにリリースされていますが、これは今のところ最新の機能を使ってみたいedgeなエンジニア向けのバージョンです。</p>

<p>バージョンの違いを意識した上で利用してください。</p>

<p>また、Node.jsのv0.10やv0.12を使っている人たちはまだ多いと思いますが、来年の末には両方のバージョンともにサポートが切れるので、今のうちから新しいバージョンにバージョンアップを検討しておくことをおすすめします。</p>
]]></content:encoded>
			</item>
		<item>
		<title>JavaScriptエンジニアへのIoTのすすめ：Node.jsとArduinoでスマートデバイスのプロトタイプをしてみよう</title>
		<link>/girlie_mac/17684/</link>
		<pubDate>Thu, 26 Nov 2015 00:00:36 +0000</pubDate>
		<dc:creator><![CDATA[Tomomi Imura]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Arduino]]></category>
		<category><![CDATA[Internet of Things]]></category>
		<category><![CDATA[IoT]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[PubNub]]></category>

		<guid isPermaLink="false">/?p=17684</guid>
		<description><![CDATA[ここ、HTML5Experts.jpでも今年になって IoTやWoT関連の話題がことかかず、みさなんも関心を持ち始めていることかと思われます。 私もフロントエンド・エンジニアではありますが、もともと関心があったことと、去...]]></description>
				<content:encoded><![CDATA[<p>ここ、HTML5Experts.jpでも今年になって IoTやWoT関連の話題がことかかず、みさなんも関心を持ち始めていることかと思われます。</p>

<p>私もフロントエンド・エンジニアではありますが、もともと関心があったことと、去年からデータ・ストリームのPaaS (platform as a service) 会社である<a href="http://pubnub.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PubNub</a>で働いていることもあって、IoTを避けずには通れなくなり、電子工作を始めるようになりました。</p>

<p>そこで、最近東京・渋谷で行われた、東京Node学園で登壇した際に話したテーマ、<a href="https://speakerdeck.com/girlie_mac/tokyo-nodefest-2015-hardware-hacking-for-javascript-developers-japanese-ri-ben-yu" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Hardware Hacking for JavaScript Developers</a> から、ArduinoとNode.jsを使ってプロトタイプする、という内容についてチュートリアル形式で書いてみたいと思います。</p>

<h3>Arduinoが変えたMakerムーブメント</h3>

<p>みなさん、<a href="https://www.arduino.cc/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Arduino</a>の名前は聞いたことがあると思います。Arduino、もしくはGenuinoとして知られているこのオープンソースのAVRマイクロコントローラ電子工作キットは、既存のマイコンボードと違い、安価で、I/Oポートもあり、USB接続で今すぐ始められます。開発環境（IDE）とサンプルコードなども簡単にダウンロード、インストールできることから初心者でも簡単に電子工作ができるようになったという面では、今までハードウェアに縁がなかったソフトウェアエンジニアが、Makerムーブメントに積極的に参加するようになった火付け役ともいえると思います。</p>

<p>しかし、同じソフトウェアといえども、ウェブエンジニアはどうでしょう？Arduinoで使用される言語はC-ベースのSketchということで敷居が高く感じたり、興味がなかったりするのではないでしょうか？でも、もし馴染みのJavaScriptで好きなことができたら、遊んでみたくなりませんか？</p>

<h3>Johnny-Five: JSロボティクス・フレームワーク</h3>

<p>そこで紹介したいのが、JavaScript ロボティクス・フレームワークである、<a href="http://johnny-five.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Johnny-Five</a>。日本ではまださほど知られていない感じがしますが、オープンソースでディベロッパ・コミュニティの結束も固く、<a href="http://nodebots.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">インターナショナル NodeBots Day</a>などでユーザ・コミュニティも大きくなってきたこのフレームワークを使えば、JavaScriptをで簡単にプログラマブル電子工作ができるようになります。ここは説明よりも実践してみるほうが理解も深まると思いますので、さっそくチュートリアルを始めましょう！</p>

<p><img src="/wp-content/uploads/2015/11/arduino-loves-j5.png" alt="arduino nodejs johnny-five" width="640" height="236" class="aligncenter size-full wp-image-17712" srcset="/wp-content/uploads/2015/11/arduino-loves-j5.png 640w, /wp-content/uploads/2015/11/arduino-loves-j5-300x111.png 300w, /wp-content/uploads/2015/11/arduino-loves-j5-207x76.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>1. Arduino のセットアップ</h2>

<p>用意するハードウェアとインストールの必要なソフトウェア</p>

<ul>
<li><a href="https://www.arduino.cc/en/Main/ArduinoBoardUno" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Arduino (Genuino) Uno</a></li>
<li><a href="http://arduino.cc/en/main/software" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Arduino IDE</a></li>
<li><a href="https://nodejs.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node.js</a> </li>
</ul>

<p>始めにセットアップから始めます。ArduinoボードをUSBで、コンピュータにつなぎます。Arduino IDEをダウンロード、インストールして、起動させたら、ボードとシリアルポートの設定をします。</p>

<p>メニューの<strong>ツール</strong> &gt; <strong>マイコンボード</strong>の中の<strong>Arduino Uno</strong>を選択し、 <strong>ツール</strong> &gt; <strong>シリアルポート</strong>の中の<strong>/dev/tty.usbmodem</strong>、もしくは<strong>cu.usbmodem</strong>という名前が含まれる項目を選びます。</p>

<p><img src="/wp-content/uploads/2015/11/arduino-johnnyfive-setup-jp.png" alt="Arduino setup" width="640" height="180" class="aligncenter size-full wp-image-17690" srcset="/wp-content/uploads/2015/11/arduino-johnnyfive-setup-jp.png 640w, /wp-content/uploads/2015/11/arduino-johnnyfive-setup-jp-300x84.png 300w, /wp-content/uploads/2015/11/arduino-johnnyfive-setup-jp-207x58.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>Johnny-FiveはFirmataプロトコルを使ってArduinoと通信をするので、今度は<strong>StandardFirmata</strong>をインストールします。</p>

<p>メニューから<strong>ファイル</strong> &gt; <strong>スケッチの例</strong> &gt; <strong>Firmata</strong> &gt; <strong>StandardFirmata</strong>を選択すると新しいウィンドウが開きます。そこから左から２番目にあるアップロードボタンをクリックします。下のGIFアニメーションの通りです。</p>

<p><img src="/wp-content/uploads/2015/11/arduino-johnnyfive-firmata-jp.gif" alt="Arduino setup GIF animation" width="640" height="512" class="aligncenter size-full wp-image-17727" /></p>

<p>終わったら閉じ、このIDEはもうこれからは必要ありません。</p>

<h2>2. Hello World</h2>

<p>まずコンピュータに、Node.jsがすでにインストールされていることを、確認してください。そして適当なディレクトリ (e.g. /j5-hello/) を作ったら、そのディレクトリ内にパーケッジマネージャであるnpmを使って、Johnny-Fiveをインストールします。</p>

<p><code>$ npm install johnny-five</code></p>

<p>さっそくJohhny-Fiveを使ってはじめの一歩である、Hello Worldを書いてみましょう。新しい言語やフレームワークを学ぶ際に初めて書くプログラムは&#8221;Hello world&#8221;という2つの単語を出力する、というのが一般ですが、ここでは、ハードウェア界のHello Worldとも言える「Lチカ」（LEDを点滅させる）をしてみます。</p>

<h3>必要なハードウェア</h3>

<ul>
<li>セットアップ済みのArduino Uno</li>
<li>LED</li>
<li>ブレッドボード </li>
<li>ジャンパー・ワイヤー（両側がピン状になっているオス〜オス）</li>
<li>抵抗　（200-330Ω 程度）</li>
</ul>

<p><img src="/wp-content/uploads/2015/11/arduino-hello-world-jp.jpg" alt="Arduino LED blink" width="640" height="318" class="aligncenter size-full wp-image-17692" srcset="/wp-content/uploads/2015/11/arduino-hello-world-jp.jpg 640w, /wp-content/uploads/2015/11/arduino-hello-world-jp-300x149.jpg 300w, /wp-content/uploads/2015/11/arduino-hello-world-jp-207x103.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>まずはハードウェア側のセッティングです。
部品などどこで買ったらわからない、という方はAmazonなどでキットを買うのをお勧めします。</p>

<h3>LEDの基本</h3>

<p>LED（発光ダイオード）には、極性があり、その＋極を<strong>アノード</strong>、ー極を<strong>カソード</strong>と呼びます。普通のLEDには通常、足が二本ついていますので長い方がアノードです。これは回路を作るときに重要になってきますので必ず覚えておいてください！</p>

<p><img src="/wp-content/uploads/2015/11/led-anode-cathode-jp.png" alt="LED anode and cathode" class="aligncenter" /></p>

<h3>電気回路の作成</h3>

<p>ArduinoからLEDを光らせるための回路はこんな感じになります。ここでは便宜上、電源からつながるプラス側を赤、Groundにつながるマイナス側を黒のワイヤーで示してあります。何色を使っても構いませんが、混乱を避けるために2つの異なる色にすることをお勧めします。</p>

<p><img src="/wp-content/uploads/2015/11/arduino-uno-led_bb.png" alt="Fritzing Arduino Uno LED" width="640" height="483" class="aligncenter size-full wp-image-17716" srcset="/wp-content/uploads/2015/11/arduino-uno-led_bb.png 640w, /wp-content/uploads/2015/11/arduino-uno-led_bb-300x226.png 300w, /wp-content/uploads/2015/11/arduino-uno-led_bb-207x156.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>ブレッドボードや抵抗についての基本、そしてこの「Lチカ」の回路については<a href="https://html5experts.jp/youtoy/12029/" data-wpel-link="internal">初心者でもわかる・できる！Arduinoを使った初めての電子工作実践</a>の<strong>「部品をつなぐ」</strong>セクションで豊田陽介さんが詳しく説明されているので、それを参考にしましょう。</p>

<h3>Johnny-FiveでLチカ</h3>

<p>では、適当なファイルネーム、例えば<code>blink.js</code>を作成し下のコードをコピーペーストしてください。</p>

<p></p><pre class="crayon-plain-tag">var five = require('johnny-five');
var board = new five.Board();

board.on('ready', function() {
  var led = new five.Led(13);
  led.blink(500);
});</pre><p></p>

<p>ターミナルからこのファイルを走らせましょう。</p>

<p><code>$ sudo node blink.js</code></p>

<p>正しく作動しましたか？回路が正しく、コードにエラーがない場合は、500ms間隔でLEDが点滅します。</p>

<p><img src="/wp-content/uploads/2015/11/arduino-johnnyfive-blink.gif" alt="LED blink animated GIF" class="aligncenter" /></p>

<p>これで、JavaScriptとハードウェアを使う基本を学びましたので、今度は実際にスマートデバイスのプロトタイプを作ってみましょう。</p>

<h2>3. スマート照明システムをプロトタイプする</h2>

<p><a href="http://www2.meethue.com/ja-JP/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Philips HUE</a>や、<a href="http://www.lifx.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">LIFX</a>は、ムードや状況によってスマートフォンやタブレットから、明かりの色や照度をカスタマイズできるLED電球です。さっそく Arduinoを使ってこれらのように色を自在に変えられるLEDを作ってみましょう。色を変えるためのコントローラはHTML５を使ってブラウザから操作できるようにしましょう。</p>

<h3>JavaScript で Internet of Things</h3>

<p>さて、ここで重要なのは、デバイス（ハードウェア）とモバイルUI (HTML5)をインターネットでつなげて、IoT化するということです。Bluetoothなどと違い、インターネットにさえ繋がっていれば、あなたが世界のどこにいようともモバイルから自宅の照明の操作をすることができるのです。この2ウェイ・コミュニケーションを可能にするにはソケット・コネクションが必要になります。ここで、この双方向通信を簡単に行うことにできるPubNubのJavaScript APIを使って、IoTのプロトタイプをしてみましょう。</p>

<p><a href="http://pubnub.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PubNub</a> はグローバルなデータストリームネットワーク (DSN) で、 このAPIを使ってリアルタイムアプリケーション、IoTデバイスなどを接続、拡張、管理することができます。今から作成するスマートLEDは下の図のようにモバイルデバイス（このプロトタイプの場合はブラウザ）とハードウェアをインターネットでつなげることが可能です。</p>

<p><img src="/wp-content/uploads/2015/11/pubnub-arduino.png" alt="How PubNub makes an Arduino as an IoT device" width="640" height="233" class="aligncenter size-full wp-image-17720" srcset="/wp-content/uploads/2015/11/pubnub-arduino.png 640w, /wp-content/uploads/2015/11/pubnub-arduino-300x109.png 300w, /wp-content/uploads/2015/11/pubnub-arduino-207x75.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>例えば、サンフランシスコにいるモバイルユーザが東京にあるArduinoを操作する場合：</p>

<ol>
<li>ユーザがモバイルから「緑い照明にしたい」と送信（パブリッシュ）</li>
<li>メッセージがPubNubのサンスランシスコデータセンターに送信される</li>
<li>データは世界各地のデータセンターと同期</li>
<li>Arduinoが東京データセンターからデータを受信（サブスクライブ）して、LEDを緑に</li>
</ol>

<p>この全てのオペレーションがわずか¼秒かそれ以下で行われるのが、PubNubの利点です。</p>

<p>このチュートリアルではJavaScriptを使ってプロトタイプを作りますが、PubNubの組み込みとモバイルのSDKを使えば、実際にHUEのようなスマートデバイスを製品化することも可能なのです。</p>

<h3>必要なハードウェア</h3>

<ul>
<li>Arduino Uno</li>
<li><a href="http://www.amazon.co.jp/100%E5%80%8B-5%E3%83%9F%E3%83%AA%E3%83%A1%E3%83%BC%E3%83%88%E3%83%AB-4%E3%83%94%E3%83%B3-RGB-%E3%82%AB%E3%82%BD%E3%83%BC%E3%83%89/dp/B00HIUDSJK/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">RGBフルカラーLED（カソードコモン）</a></li>
<li>ブレッドボード </li>
<li>ジャンパー・ワイヤー（オス〜オス）</li>
<li>抵抗　（220Ω x 2, 330Ω x 1）</li>
</ul>

<p><img src="/wp-content/uploads/2015/11/arduino-rgbled-jp.jpg" alt="Arduino RGB LED JP" width="640" height="273" class="aligncenter size-full wp-image-17702" srcset="/wp-content/uploads/2015/11/arduino-rgbled-jp.jpg 640w, /wp-content/uploads/2015/11/arduino-rgbled-jp-300x128.jpg 300w, /wp-content/uploads/2015/11/arduino-rgbled-jp-207x88.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h3>RGBフルカラーLEDの基本</h3>

<p>RGB (Red-Green-Blue) フルカラーLEDは、赤、緑、青の3色のLEDが一本になったもので、この三原色の組み合わせで無数の色を作り出すことができます。ほとんどのこのタイプのLEDは、各色のピンが一本づつと共通のカソード（もしくはアノード）の合計4本のピンがあります。このチュートリアルでは、カソードコモンのものを使用しています。</p>

<p><img src="/wp-content/uploads/2015/11/led-rgb-cathode-jp.png" alt="RGB LED JP" width="640" height="151" class="aligncenter size-full wp-image-17703" srcset="/wp-content/uploads/2015/11/led-rgb-cathode-jp.png 640w, /wp-content/uploads/2015/11/led-rgb-cathode-jp-300x71.png 300w, /wp-content/uploads/2015/11/led-rgb-cathode-jp-207x49.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h3>電気回路の作成</h3>

<p>Hello World を成功させたからには、この下の図の回路も問題なく作れるのではないでしょうか。がんばってみてください。</p>

<p>ワイヤーを差し込むピンは、単純な「Lチカ」とは違い、PWM (Pulse Width Modulation：パルス幅変調) ピンを使っています。Arduinoボードにラベルされているピン番号の前に「〜」が付いているものがPWMピンになります。</p>

<p>これらと他のピンの違いは、簡単にいえばアナログかデジタルかの違いになります。例えばLEDの場合、一般のデジタルピンにつなぐとオフかオンの2つのステートしか表現できません。Lチカ（点滅）はこのデジタル信号をオンとオフと交互に発しています。しかしRGBフルカラーLEDで無数の色を表現するには、各色の値の組み合わせで色を作り出すので中間の値が必要になりますので、アナログであるPWMピンを使います。</p>

<p><img src="/wp-content/uploads/2015/11/arduino-uno-rgb-led_bb.png" alt="Fritzing Arduino RGB LED" width="640" height="476" class="aligncenter size-full wp-image-17719" srcset="/wp-content/uploads/2015/11/arduino-uno-rgb-led_bb.png 640w, /wp-content/uploads/2015/11/arduino-uno-rgb-led_bb-300x223.png 300w, /wp-content/uploads/2015/11/arduino-uno-rgb-led_bb-207x154.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>ここで抵抗を220Ωと330Ωにわけてあるのは、同じ抵抗だと赤が強くなるので、抵抗を増やして赤を控えめな明かるさになるように調整してあります。抵抗の計算方法やカラーコードの読み方については、ここで割愛しますが、自分でいろいろ調整してみてください。</p>

<h3>IoT リモートコントローラーUIの作成</h3>

<p>ブラウザでLEDの色をコントロールするUIを作ってみましょう。</p>

<p><img src="/wp-content/uploads/2015/11/hue-prototype-ui.png" alt="hue-prototype-ui" width="640" height="434" class="aligncenter size-full wp-image-17707" srcset="/wp-content/uploads/2015/11/hue-prototype-ui.png 640w, /wp-content/uploads/2015/11/hue-prototype-ui-300x203.png 300w, /wp-content/uploads/2015/11/hue-prototype-ui-207x140.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>HTML5の&#8220;、&#8221;R&#8221;、&#8221;G&#8221;、&#8221;B&#8221;の各色調整をするスライダーバーを描写します。各色は0から255まで(<code>min="0" max="255"</code>)の整数の値(<code>step="1"</code>)を、つまみで調節できるようなUIにしてみましょう。</p>

<p></p><pre class="crayon-plain-tag">&lt;input id="red" type="range" min="0" max="255" step="1" value="0"&gt;</pre><p></p>

<p>これで赤色を調節するスライダーができました。このチュートリアルではCSS部分は省略しますが、スクリーンショットのようなスタイルにするには、<a href="https://github.com/girliemac/arduino-led-rgb" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">GitHubのソースコード</a>の<a href="https://github.com/girliemac/arduino-led-rgb/tree/gh-pages" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">gh-pages ブランチ</a>を参照してください。</p>

<p>次はJavaScriptでこの値がユーザによって変更された時に、その値をArduino側に反映させるようにします。これを実現させるためにPubNub JavaScript APIで両デバイスを接続させてみましょう。</p>

<p>PubNub JavaScript API を使うには、まず自分の<a href="https://admin.pubnub.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">APIキーを取得</a>を取得したら、CDNライブラリ（もしくはローカルファイル）を読み込みます。</p>

<p></p><pre class="crayon-plain-tag">&lt;script src="//cdn.pubnub.com/pubnub-3.7.15.min.js"&gt;&lt;/script&gt;</pre><p></p>

<p>そして自分のキーを使ってインスタンスを初期化します。</p>

<p></p><pre class="crayon-plain-tag">var pubnub = PUBNUB({
　subscribe_key : '自分の subscribe key',                          
　publish_key   : '自分の publish key'
});</pre><p></p>

<p>UIイベント発生時に自動的にLEDの色を変える仕様にしてみましょう。
スライダーDOMに<code>change</code>イベントが発生した時に、変更値をPubNubに <code>publish</code> メソッドを使って送信（パブリッシュ）します。</p>

<p></p><pre class="crayon-plain-tag">var red = document.getElementById('red');

red.addEventListener('change', function(){
  pubnub.publish({
    channel: 'smart-led', 
    message: {color: 'red', brightness: +this.value}
  });
}, false);</pre><p></p>

<p>この時の <code>channel</code> 名は何でも構いませんが、のちにこのデータを受信する際にはこれと同じチャンネル名にします。</p>

<p>では次はNode.jsを使ってハードウェア側でこの値を反映させてみましょう。</p>

<h3>Johnny-Five と PubNub で Arduino を IoTデバイスに</h3>

<p>Johnny-FiveでRGBフルカラーLEDを動かすコードは、いたって簡単。npmでjohnny-fiveをインストールしたら、次のコードを動かしてみてください。</p>

<p></p><pre class="crayon-plain-tag">var five = require('johnny-five');
var led;

five.Board().on('ready', function() {
  console.log('ready');

  // RGB LED を初期化
  led = new five.Led.RGB({
    pins: { // pin 番号
      red: 6,
      green: 5,
      blue: 3
    }
  });

  // テスト
  led.color({red: 255, blue: 0, green: 0});
  
  led.on();
});</pre><p></p>

<p>LEDが赤色に光ましたか？
何も起こらない場合はコードと配線などを確かめてください。それでも作動しない場合は、使っているRGB LED がカソードコモンではなくアノードコモンの可能性があります。その場合は、GND（図上の黒のワイヤー）を、Arduinoの5Vのピンに変えてみてください。</p>

<p>「テスト」とコメントされた行をみてください。<code>color</code> メソッドでLEDの色を指定しているのがわかると思います。この場合、赤の値が255で他が0となっているので、LEDを赤で光らせることができます。Johnny-Fiveでは、CSSのcolorプロパティと同じようにRGBの値を0～255で指定することができるのです。RGBの各値を変えながらどんな色になるか実験してみてください。</p>

<p>さて今度は、このRGBの値をブラウザUIからリモートコントロールして、動的にLEDの色を変えてみましょう。</p>

<p>さきほどのUI側からパブリッシュされたデータを受け取って、ハードウェアに反映させるには、PubNub <code>subscribe</code> メソッドで受信（サブスクライブ）しましょう。</p>

<p>まず、npm で pubnub をインストールします。</p>

<p><code>$ npm install pubnub </code></p>

<p>フロントエンドと同様に、pubnubインスタンスの初期化します。</p>

<p></p><pre class="crayon-plain-tag">var pubnub = require('pubnub').init({
  subscribe_key: '自分の subscribe key',
  publish_key:   '自分の publish key'
});</pre><p></p>

<p>UI側から送信されたデータは、同じ <code>channel</code> にアクセスすることによって自動的に送信することができます。データが送信された際には、<code>subscribe</code> メソッドの <code>callback</code> が返されます。このコールバック内で送信された色データをLEDに反映させます。</p>

<p></p><pre class="crayon-plain-tag">pubnub.subscribe({
  channel: 'smart-led',
  callback: function(m) {
    if(led) {
      r = (m.color === 'red') ? m.brightness : r;
      g = (m.color === 'green') ? m.brightness : g;
      b = (m.color === 'blue') ? m.brightness : b;

      led.color({red: r, blue: b, green: g});

      console.log( 'color change to...' );
      console.log( led.color() );
    }
  }
  error: function(err) {console.log(err);}
});</pre><p></p>

<p>送信されたメッセージ、<code>m</code> は、UIから送られたオブジェクト、例えば青の値が150の場合、<code>{color: 'blue', brightness: 150}</code>、となります。次に送信された値が、<code>{color: 'red', brightness: 150}</code> ならば、前の値の青＝150はそのままで、ここに赤＝150が加わることによって、LEDは紫に近い色になります。さらに赤緑青の3つの値が変われば、LEDはさまざまな色に変わります。</p>

<p><img src="/wp-content/uploads/2015/11/poormans-philips-hue.jpg" alt="IoT 101 Arduino with RGB LED" width="640" height="480" class="aligncenter size-full wp-image-17705" srcset="/wp-content/uploads/2015/11/poormans-philips-hue.jpg 640w, /wp-content/uploads/2015/11/poormans-philips-hue-300x225.jpg 300w, /wp-content/uploads/2015/11/poormans-philips-hue-207x155.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>全コードは、<a href="https://github.com/girliemac/arduino-led-rgb" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">GitHubのソースコード</a>の<a href="https://github.com/girliemac/arduino-led-rgb" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">master ブランチ</a>内の <code>/node/index.js</code>を参照してください。</p>

<p>さて、これでIoTスマート照明のプロトタイプができあがりました！</p>

<p><img src="/wp-content/uploads/2015/11/arduino-johnnyfive-hue.gif" alt="arduino-johnnyfive-hue" width="371" height="317" class="aligncenter size-full wp-image-17706" /></p>

<p>Johnny-FiveもPubNubもArduinoだけではなく、他のいろいろなマイクロコンピュータに使うことができます。いろいろなセンサーやカメラなどを組み合わせて、IoTプロトタイプに挑戦してみてください。</p>

<p>では、Happy hacking!</p>

<h2>References</h2>

<ul>
<li><a href="http://johnny-five.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Johnyy-Five</a>: The original JavaScript Robotics programming framework</li>
<li><a href="https://www.pubnub.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PubNub</a>: The global realtime Data Stream Network for IoT, mobile, and web applications</li>
</ul>
]]></content:encoded>
			</item>
		<item>
		<title>WebGLとWebSocketによる3Dオンラインレースゲーム「JS-Racing」の全て！（後編）</title>
		<link>/knockknockjp/10481/</link>
		<pubDate>Tue, 02 Sep 2014 00:00:00 +0000</pubDate>
		<dc:creator><![CDATA[西田慎吾]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[Webアプリ]]></category>

		<guid isPermaLink="false">/?p=10481</guid>
		<description><![CDATA[連載： HTML5 Japan Cup 特集 (6)WebGLとWebSocketによる3Dオンラインレースゲーム「JS-Racing」の全て！（後編） 前回に引き続きHTML5 Japan Cup 2014にてWebG...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/5jcup-2/" class="series-207" title="HTML5 Japan Cup 特集" data-wpel-link="internal">HTML5 Japan Cup 特集</a> (6)</div><p>WebGLとWebSocketによる3Dオンラインレースゲーム「JS-Racing」の全て！（後編）</p>

<p><a href="https://html5experts.jp/knockknockjp/10226/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">前回</a>に引き続き<a href="https://5jcup.org/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">HTML5 Japan Cup 2014</a>にて<strong>WebGL賞</strong>と<strong>優秀賞</strong>をいただいたオンラインレースゲーム、<a href="http://js-racing.knockknock.jp/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">JS-Racing</a>の技術解説をさせていただきます。</p>

<p><img src="/wp-content/uploads/2014/08/img14.jpg" alt="img14" width="630" height="484" class="alignnone size-full wp-image-10498" srcset="/wp-content/uploads/2014/08/img14.jpg 630w, /wp-content/uploads/2014/08/img14-300x230.jpg 300w, /wp-content/uploads/2014/08/img14-207x159.jpg 207w" sizes="(max-width: 630px) 100vw, 630px" /></p>

<h2>サーバサイドの使用技術</h2>

<p>サーバサイドの技術として<a href="http://nodejs.jp/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Node.js</a>を使用しています。Node.jsはサーバーサイドで動作するJavaScriptで、<strong>ノンブロッキングI/Oというモデルを採用しています。非同期処理でデータベースへのアクセスとWebページの表示を別々に行ってくれる</strong>ので、ストレスなく大量のページの表示が出来ます。また、Socket.ioというライブラリを扱うことで、WebSocketを使用したリアルタイム通信を実現するができます。</p>

<p>Node.jsでWebアプリケーションを構築する場合、実験的にローカルのみの開発の場合は問題ありませんが、公開するとなると、Node.jsをインストールして実行できるサーバが必要になります。そのためには共有サーバのレンタルではなく、<strong>管理者権限 (root) が付与される専用サーバや、仮想専用サーバ（VPS）のレンタルが必要</strong>になると思います。管理者権限のため自由に環境をセットアップする事ができるために、そのWebアプリケーションに合わせた環境構築が可能になります。</p>

<h3>ExpressによるWebアプリケーションフレームワークの利用</h3>

<p>Node.jsで作成したWebアプリケーションの場合、リクエストURIの解析からファイルの配信など、HTTPサーバの機能を実装しなくてはいけません。これらの<strong>基本機能が備わったWebアプリケーションフレームワークを利用することで、非常に手軽にWebアプリケーションを作成</strong>できます。今回は、多数開発されているWebフレームワークのなかでも、有名で多く利用されている<a href="http://expressjs.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Express</a>を利用しました。</p>

<p></p><pre class="crayon-plain-tag">var express = require("express");
var app = express();
// HTMLエンジンとしてjadeを使用
app.set("view engine", "jade");
app.set("views", __dirname + "/views");
app.use(express.static(__dirname + "/public"));
// js-racing.knockknock.jp/にアクセスがあった場合、
// /views/index.jadeに「title=JS Racing」と「version=ver1.2」の値を渡して、HTMLを表示
app.get("/", function(req, res) {
    res.render("index", {
        title: "JS Racing",
        version: "ver1.2"
    });
});
// js-racing.knockknock.jp/controller.htmlにアクセスがあった場合、
// /views/controller.jadeに「title=JS Racing」と「version=ver1.2」と「id=URLパラメータのidの値」の値を渡して、HTMLを表示
app.get("/controller.html", function(req, res) {
    res.render("controller", {
        title: "JS Racing",
        version: "ver1.2",
        id: req.query.id
    });
});
app.set("port", process.env.PORT || 3000);
var server = require("http").createServer(app);
server.listen(app.get("port"), function(){
    console.log("Express server listening on port " + app.get("port"));
});</pre><p></p>

<p>22行目の<code>req.query.id</code>は<code>http://js-racing.knockknock.jp:3000/controller.html?id=000000</code>として渡された、URLパラメータのidの値が参照できます。この値は、ソケット通信時に発行されたソケットIDの値で、PCから発行されたスマフォ用のURL（QRコードか短縮URL）にURLパラメータとして付加されたもので、この値を元にPCとスマートフォンとのペアリングを行います。</p>

<p><strong>ExpressはHTMLのテンプレートエンジンとして<a href="http://jade-lang.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Jade</a>か、<a href="http://embeddedjs.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ejs</a>を選択</strong>することができます。Jadeはインデントが必須です。閉じタグを省略できる代わりに、DOMの入れ子構造に沿って適切にインデントを使用する必要があります。基本的な記法がHTMLとは違うために、HTMLに親しんだマークアップエンジニアからすれば、慣れるまで違和感があるものだと思います。ejsはJadeとは違い、基本的な記法はHTMLをベースにしていますので、ローカルでくみ上げたHTMLをejsに組み込むことが容易です。今回はより楽にコード量を少なくHTMLを構築したいという点で、閉じたタグなしで記述できるJadeを選択しています。</p>

<p></p><pre class="crayon-plain-tag">// オープニングタイトル表示部分
section#sceneOpening.scene-opening(style="display: none;")
    div.scene-opening__inner
        div.scene-opening__box5
            h1.scene-opening__ttl
                // expressから受け取ったパラメータ、titleの値を挿入
                span.scene-opening__ttl__txt #{title}
                    br
                // expressから受け取ったパラメータ、versionの値を挿入
                span.scene-opening__ttl__txt2 &amp;nbsp;#{version}</pre><p></p>

<p>Jadeはこのようにインデントによって、要素の入れ子構造を定義しています。そのため閉じタグが不要で、<strong>HTMLを非常に簡略化</strong>して記述することが可能になります。HTMLを簡略化できるだけがJadeの特徴ではありません。<strong>HTMLをコンポーネント化して再利用</strong>したり、条件分岐をさせたり、繰り返し処理をさせたり、これまでサーバサイドで行っていたようなことがNode.jsで可能になります。ちなみにCSSはSass（Compass）を使い、記法にはBEMという命名規則を採用しています。</p>

<h3>Socket.ioによるリアルタイム通信</h3>

<p>車の同時走行と、スマートフォンからの車の操作を実現するためには、ブラウザとサーバ双方から、任意のタイミングでデータを送受信する必要があります。このリアルタイム通信を実現するために、<a href="http://socket.io/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Socket.io</a>を利用しています。Socket.ioは<strong>WebSocketなどのリアルタイム通信技術をラップして、シンプルなAPIを提供しているため、通信部分の煩雑さを意識することなく、リアルタイム通信を簡単に構築</strong>することができます。</p>

<p>スマートフォンからの車の操作を実現するために、接続時に発行されたソケットIDを共有することで、スマートフォンとPCをペアリングして相互通信しています。サーバを介しての通信なので、若干のタイムラグが発生する懸念もあったのですが、現状ではタイムラグを感じることはありませんでした。環境に依存することですので、あくまで今回の場合はということですが。</p>

<p></p><pre class="crayon-plain-tag">var socketArr = [];
io.sockets.on("connection", function(socket){
    // Socket.IDをPCに送信
    socket.emit("emit_id_form_server", socket.id);
    // PCから車の状態を受信
    socket.on("emit_carcondition_form_client", function(data){
        // 受信した車の状態
        var info = {
            id: socket.id,
            name: data.name,
            x: data.x,
            y: data.y,
            bodyAngle: data.bodyAngle,
            wheelAngle: data.wheelAngle,
            speed: data.speed,
            colorBody: data.colorBody,
            colorWing: data.colorWing,
            colorDriver: data.colorDriver
        };
        // 接続しているクライアントごとの情報を更新
        var flg = true;
        var i = 0, max;
        for (i = 0, max = socketArr.length; i &lt; max; i = i + 1) {
            if (socketArr[i].id == info.id) {
                socketArr[i] = info;
                flg = false;
            }
        }
        if (flg) {
            socketArr.push(info);
        }
    });
    // スマートフォンからの操作情報を受信
    socket.on("emit_controller_data_form_client", function(data){
        var id = data.id;
        var event = data.event;
        var value = data.value;
        var flg = false;
        var sockets = io.sockets.sockets;
        var i = 0, max;
        for (i = 0, max = sockets.length; i &lt; max; i = i + 1) {
            // ソケットIDが一致したクライアントに操作情報を送信
            if (sockets[i].id == id) {
                sockets[i].emit("emit_controller_data_from_server", {
                    id: id,
                    event: event,
                    value: value
                });
                flg = true;
            }
        }
        // ソケットIDが一致したクライアントが存在しなかった場合はスマートフォンに接続解除イベントを送信
        if (!flg) {
            socket.emit("emit_disconnect_client_from_server");
        }
    });
    // 接続しているクライアントの情報を1秒間に3回、接続しているクライアント全てに送信
    setInterval(function(){
        socket.emit("emit_other_carcondition_from_server", socketArr);
    }, 1000 / 3);
});</pre><p></p>

<p>接続している全てのクライアントに、接続している全てのクライアントの情報を送信するイベントは、ネットワークの負荷を考えて、1秒間に3回と制限をかけています。つまり3FPSのタイミングで同期をとることになりますが、クライアント側では30FPSでゲームが進行しているために、タイムラグが発生してしまいます。タイムラグによって発生する違和感をなくすために、クライアント側ではサーバ側から配信されるクライアント情報を元に、前回配信された情報との差分を10で割った値を算出して、クライアント側の1FPS毎の値として反映させ、違和感を解消しています。</p>

<h3>MongoDBでラップタイムの保存と走行データの保存</h3>

<p>ラップタイムを保存したり、走行データを保存するのにデータベースとして<a href="http://www.mongodb.org/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">MongoDB</a>を利用しています。MongoDBは、<strong>オープンソースのドキュメント指向データベース</strong>です。RDBMSのようにレコードをテーブルに格納するのではなく、ドキュメントと呼ばれる構造的データをオブジェクト形式でデータを管理します。
ちなみにNode.jsから<a href="http://mongoosejs.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Mongoose</a>を利用し、MongoDBに接続しています。Mongooseでは、Schemaインスタンスを通してModelを定義する事ができます。以下のようなオブジェクト形式でデータを管理出来るのが、MongoDBの特徴です。</p>

<p></p><pre class="crayon-plain-tag">var UserSchema = new mongoose.Schema({
    id:String, // ソケットID
    name:String, // 名前
    date:String, // 登録日
    time:Number, // ラップタイム
    comment: String, // コメント
    color: {
        body: String, // ボディカラー
        wing: String, // ウィングカラー
        driver: String // ドライバーカラー
    },
    runningPath: Array // 走行データ
});
var Users = db.model("user", UserSchema);</pre><p></p>

<p>走行データは1秒間に30回、車の位置と角度を記録した配列ですので、全てのユーザーの分だけ保存すると、データ量が肥大してしまう可能性があります。データ量を押さえるために、ラップタイムでソートして10位圏外の走行データは保持していません。</p>

<p>サーバサイドではNode.jsのおかげで、Webアプリケーションを構築する環境がとても整ってきていると思います。
基本的な言語はJavaScriptですので、サーバサイドもフロントエンドエンジニアが押さえるべき領域になっているのではと思います。</p>

<h2>その他の技術的トピック</h2>

<p>サーバサイド、クライアントサイドの技術それぞれの紹介を軸として、説明させていただきましたが、これ以外にもいくつか工夫した、技術的なポイントがありますので、紹介します。</p>

<h3>コースデータの共有</h3>

<p>3D表現を利用するために使用したthree.jsと、ゲームエンジンとして使った2D物理エンジンBox2DJSで、コースデータを共有するために2次元配列を使用しました。各値には数値を格納して、数値によってthree.jsでは壁、道路、芝、タイヤ、木、等の3Dモデルが配置され、Box2DJSでは3Dモデルの形状に沿った障害物を配置します。ちなみに開発当初、コースエディット機能や、SNSでコース共有機能等を考えていましたので、テキストデータとして扱いやすい2次元配列をコースデータとして採用したという経緯があります。</p>

<p></p><pre class="crayon-plain-tag">module imjcart.logic.map.value {
    export class MapConst {
        // コースデータ用の2次元配列
        static MAP:any = [           
　　　　　　[1,1,1,1,1,1,1,1,1,　〜省略〜　1,1,1,1,1,1,1,1,1,1,1], 
　　　　　　[1,4,4,4,4,4,4,4,4,　〜省略〜　4,4,4,4,4,4,4,4,4,4,1],
           〜省略〜　　　　　　
　　　　　　[1,1,1,1,1,1,1,1,1,　〜省略〜　1,1,1,1,1,1,1,1,1,1,1]
        ]
        // コースデータ配列内の各値が、何を表しているかの定数
        static MAP_KEY_NONE:number = 0; // アスファルト
        static MAP_KEY_WALL:number = 1; // 外壁
        static MAP_KEY_BLOCK:number = 2; // ブロック
        static MAP_KEY_TIRE:number = 3; // タイヤ
        static MAP_KEY_GRASS:number = 4; // 芝
        static MAP_KEY_TREE:number = 5; // 木
        static MAP_KEY_CAR_START_POSITION:number = 6; // 車のスタート位置
        static MAP_KEY_LAP_MEDIAN_CENTER_02:number = 7; // ゴールライン（逆走制御）
        static MAP_KEY_LAP_MEDIAN_CENTER_01:number = 8; // ゴールライン（逆走制御）
        static MAP_KEY_LAP_START_POINT:number = 9; // ゴールライン
        static MAP_KEY_SAND:number = 10; // 砂地
    }
}</pre><p></p>

<p>ただ、このような膨大な量の2次元配列を、テキストエディタで作成、編集するのは非常に非効率だと考えて、途中からExcelによって管理する方法に切り替えました。Excelでは条件付き書式を設定して、セルの値によって、わかりやすいように色づけするように設定しています。データ書き出しの際には、ExcelからCSVデータとしてテキストデータを書き出して、CSVデータをテキストエディタで置換して2次元配列にしています。</p>

<p><img src="/wp-content/uploads/2014/08/img12.gif" alt="img12" width="630" height="300" class="alignnone size-full wp-image-10487" /></p>

<h3>コースの装飾</h3>

<p>コースは直線と直角だけではなく、カーブ等の曲線も再現できなくてはいけません。2次元配列をコースデータとして利用する場合は、この点を補完する必要があります。また、コースというのは大抵、両脇のシケインとの境目にラインが引いてあります。こういったアスファルトや芝生といった要素以外の装飾を、周りの要素の配置条件に応じて追加することで、よりコースらしい外観を作る事ができます。下のキャプチャが装飾やカーブの補完をかけている物と、コースデータをそのまま表示したものとの違いです。</p>

<p><img src="/wp-content/uploads/2014/08/img13.jpg" alt="img13" width="630" height="254" class="alignnone size-full wp-image-10492" srcset="/wp-content/uploads/2014/08/img13.jpg 630w, /wp-content/uploads/2014/08/img13-300x120.jpg 300w, /wp-content/uploads/2014/08/img13-207x83.jpg 207w" sizes="(max-width: 630px) 100vw, 630px" /></p>

<h2>まとめ</h2>

<p>以上が今回作成したJS-Racingの主な技術解説になります。Webアプリケーションを構築するには、今回解説したように多くの技術が必要となります。ご紹介した技術は、たくさんある中から必要なものを取捨選択した結果ですので、当然コンテンツが変われば、必要となる技術も変わります。このコンテンツで使用した主な技術の全体図をまとめました。</p>

<p><img src="/wp-content/uploads/2014/08/img02.jpg" alt="使用技術概要" width="630" height="400" class="alignnone size-full wp-image-10250" srcset="/wp-content/uploads/2014/08/img02.jpg 630w, /wp-content/uploads/2014/08/img02-300x190.jpg 300w, /wp-content/uploads/2014/08/img02-207x131.jpg 207w" sizes="(max-width: 630px) 100vw, 630px" /></p>

<p>オンラインレースゲームというシンプルな内容のコンテンツですが、プラットフォームとしてWebブラウザを使用している点に注目していただけると嬉しいです。今までご紹介した技術は、オンラインゲームを作るだけの技術ではなく、Webコンテンツを作る上で、大きな可能性を持っている技術になります。私はゲームを作る上でこれらの技術を利用していますが、Web上の様々なサービスと連携をとることで、もっと広がりを持つコンテンツを作ることができると思います。ぜひ皆さんも、HTML5とJavaScript（クライアントサイド、サーバサイド含む）を使って、Webアプリケーションを作ってみてください。</p>
]]></content:encoded>
		
		<series:name><![CDATA[HTML5 Japan Cup 特集]]></series:name>
	</item>
		<item>
		<title>Googleのベストプラクティスに沿ったモダンな製作の出発点「Web Starter Kit」</title>
		<link>/nakajmg/8931/</link>
		<pubDate>Thu, 07 Aug 2014 00:00:20 +0000</pubDate>
		<dc:creator><![CDATA[中島 直博]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Web Starter Kit]]></category>

		<guid isPermaLink="false">/?p=8931</guid>
		<description><![CDATA[Web Starter Kitが提供するもの Web Starter Kitは、Googleが提供するマルチデバイスでのWeb開発をサポートするボイラープレート(HTMLのひな形)と、開発を補助するツール群です。 特に注...]]></description>
				<content:encoded><![CDATA[<h2>Web Starter Kitが提供するもの</h2>

<p><a href="https://developers.google.com/web/starter-kit/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Starter Kit</a>は、Googleが提供するマルチデバイスでのWeb開発をサポートするボイラープレート(HTMLのひな形)と、開発を補助するツール群です。<br>
特に注目したいのが開発を補助するツール群で、このツールを使えば今どきのWeb開発のワークフローをすぐに取り入れることができます。</p>

<p><img src="/wp-content/uploads/2014/08/wsk_0.png" alt="wsk_0" width="1000" height="349" class="aligncenter size-full wp-image-9467" srcset="/wp-content/uploads/2014/08/wsk_0.png 640w, /wp-content/uploads/2014/08/wsk_0-300x104.png 300w, /wp-content/uploads/2014/08/wsk_0-207x72.png 207w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>

<p>Web Starter Kitを開発の出発点とすることで、<a href="https://developers.google.com/web/fundamentals/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Fundamentals</a>にあるWeb開発のベストプラクティスを取り入れられるながらスムーズ制作に入れるとともに、より効率的な開発を可能にします。</p>

<p>本記事では導入方法と機能の説明を中心に、Web Starter Kitの紹介をしていきます。</p>

<h2>Web Starter Kitの導入</h2>

<h3>入手方法</h3>

<p>Web Starter Kitの入手には2つの方法があります。一つは<a href="https://github.com/google/web-starter-kit" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Starter Kitのリポジトリ</a>からクローンする方法、もう一つは、<a href="https://developers.google.com/web/starter-kit/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Starter Kitのサイト</a>からダウンロードする方法があります。</p>

<p>Web Starter Kitは現在Beta版で、アップデートが頻繁に行われています。リリースサイクルが早いため、Webページからのダウンロードの場合最新のバージョンではない可能性もあるのでリポジトリからのクローンをオススメします。</p>

<p>※ 執筆時点でのバージョンは <code>0.4.0</code> です。アップデートの変更点を把握するためにも<a href="https://github.com/google/web-starter-kit/releases" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Starter KitのReleases</a>を定期的にチェックするといいでしょう。</p>

<h3>フォルダとファイルの構成</h3>

<p>フォルダ構成は以下のようになっています。</p>

<p></p><pre class="crayon-plain-tag">/
└── app
    ├── fonts
    ├── images
    ├── scripts
    ├── styleguide
    └── styles</pre><p></p>

<p>Web Starter Kitのドキュメントルートとなるフォルダには、各種の設定ファイルが含まれています。開発する際の中心となるのは <code>app</code> フォルダ以下で、デフォルトでHTMLのひな形やWebフォントなど、いくつかのリソースが含まれています。</p>

<p>各フォルダの役目は以下のようになっています。</p>

<table>
<thead>
<tr>
  <th></th>
  <th></th>
</tr>
</thead>
<tbody>
<tr>
  <td>/</td>
  <td>ドキュメントルート。 <code>gulpfile.js</code> や <code>package.json</code> 、エディタの挙動を設定する <code>.editorconfig</code> や Gitで使う <code>.gitignore</code> などの設定ファイルが入っている。</td>
</tr>
<tr>
  <td>app</td>
  <td>リソースを入れるフォルダ。開発はこのフォルダ内で行う。</td>
</tr>
<tr>
  <td>fonts</td>
  <td>Webフォントを入れるフォルダ。<a href="http://developer.android.com/design/style/typography.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Robotoフォント</a>が含まれている。</td>
</tr>
<tr>
  <td>images</td>
  <td>画像を入れるフォルダ。アイコンフォントも含まれている。</td>
</tr>
<tr>
  <td>scripts</td>
  <td>JavaScriptファイルを入れるフォルダ。ドロワーメニュー用のJavaScrpitファイルが含まれている。</td>
</tr>
<tr>
  <td>styleguide</td>
  <td>スタイルガイド用のHTMLファイルが含まれている。</td>
</tr>
<tr>
  <td>styles</td>
  <td>CSSファイルを入れるフォルダ。UI Component用のCSSファイルなどが含まれている</td>
</tr>
</tbody>
</table>

<p>Web Starter Kitで用意されている各種の設定ファイルなどは、そのまま使うのではなく、各自が自分のスタイルに合わせてカスタマイズするとよいでしょう。ファビコンやスマートフォンでサイトをホーム画面に追加した際に表示されるアイコンなど、各自で差し替える必要があります。
<code>humans.txt</code>や<code>robots.txt</code>なども使用する場合には適宜編集する必要があります。</p>

<h2>動作環境の構築</h2>

<p>Web Starter Kitに用意されたツールを使用するためには、以下の言語の環境が必要となります。</p>

<ul>
<li><a href="http://nodejs.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node.js</a> (v0.10.x以上)</li>
<li><a href="https://www.ruby-lang.org/ja/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Ruby</a> (1.8.7以上)</li>
</ul>

<p>これに加えて、以下のツールのインストールが必要になります。</p>

<ul>
<li>Nore.js上で動くタスクランナーの<a href="http://gulpjs.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">gulp</a> (3.5.x以上)</li>
<li><a href="http://sass-lang.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Sass</a>のコンパイルを行う<a href="https://rubygems.org/gems/sass" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Rubyのgem</a> (3.3.x以上)</li>
</ul>

<p>それぞれのインストール方法について、簡単に紹介していきます。</p>

<p>※ これらを既にインストール済みの場合は、このセクションは飛ばしてください。</p>

<h3>Node.jsのインストール</h3>

<p>Node.jsのバージョンは <code>v0.10.x</code> 以上が必要になります。すでにインストールしている場合は、<code>$ node -v</code>で現在のバージョンを確認してください。Node.jsのインストールは<a href="http://nodejs.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">http://nodejs.org/</a>にあるインストーラーを使うのが簡単です。</p>

<p>※ 「Node.jsのバージョンを管理したい」という場合には、<a href="https://github.com/hokaccha/nodebrew" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">nodebrew</a>がオススメです。</p>

<h3>Rubyのインストール</h3>

<p>Macを利用している場合は、デフォルトでRubyがインストールされているので、そのまま利用することができます。（ただしその場合でも動作には <code>1.8.7</code> 以上のバージョンが必要となりますので <code>$ ruby -v</code> で現在のバージョンを確認してください）</p>

<p>Windowsの場合には<a href="https://www.ruby-lang.org/ja/downloads/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Rubyの公式サイト</a>か<a href="http://www.rubyinstaller.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Ruby Installer</a>などからインストーラーを入手してインストールしてください。</p>

<h3>Sassのインストール</h3>

<p>Rubyのインストール後にSassのコンパイラをインストールします。インストールはRubyのパッケージマネージャのRubyGemsから行います。
コマンドラインツールから以下のコマンドを実行します。</p>

<p><code>$ gem install sass</code></p>

<p>※ もし権限に関するエラーが出た場合は、<code>sudo</code>をつけて実行してください。</p>

<p>インストール後、下記コマンドを実行してバージョン番号が表示されることを確認します。</p>

<p><code>$ sass -v</code></p>

<h3>gulpのインストール</h3>

<p>gulpは、Node.jsのパッケージマネージャであるnpmでインストールします。
インストールには、下記のコマンドを実行します。</p>

<p><code>$ npm install -g gulp</code></p>

<p>※ もし権限に関するエラーが出た場合は、<code>sudo</code>をつけて実行してください。</p>

<h3>モジュールのインストール</h3>

<p>最後に、タスクの実行に必要なモジュールをインストールします。Web Starter Kitをクローン(展開)したフォルダに移動して、下記のコマンドを実行します。</p>

<p><code>$ npm install</code>
※ もし権限に関するエラーが出た場合は、<code>sudo</code>をつけて実行してください。</p>

<p>このコマンドを実行することで、動作に必要なモジュールがインストールされます。インストールされるモジュールは、同じフォルダにある <code>package.json</code>に書かれています。</p>

<p>以上で環境構築はおしまいです。</p>

<h2>制作・開発</h2>

<p>環境が揃えば制作に入れます。制作はひな形とするHTMLを選択することから始めます。</p>

<h3>ひな形とするHTMLの選択</h3>

<p>Web Starter Kitで用意されているひな形のHTMLは、<code>index.html</code>と<code>basic.html</code>の2つがあります。</p>

<p>2つのHTMLの主な違いは以下の点です。</p>

<ul>
<li>マルチデバイス向けメニューの有無</li>
<li>Web Starter Kitが用意しているCSSとJSファイルの読み込み</li>
</ul>

<p><code>index.html</code>には、マルチデバイス向けのドロワーメニュー用のリソース読み込みが指定されていて、初めからメニューのHTMLが書かれています。メニューをそのまま使う場合に、<code>index.html</code>を。プレーンな状態のHTMLから始めたい場合には、<code>basic.html</code>を使って制作を進めましょう。</p>

<h3>UI Component</h3>

<p>Web Starter Kitにはマルチデバイス向けのメニューのほかに、いくつかのUI Componentが用意されています。ボタンやアイコン、テーブルといったComponentのほかにGridシステムもあります。いずれのComponentもマルチデバイス向けに書かれており、レスポンシブ対応されています。Componentの一覧は、<code>app/styleguide/index.html</code>で確認することができます。</p>

<p><img src="/wp-content/uploads/2014/07/wsk_04.png" alt="wsk_04" width="1000" height="528" class="aligncenter size-full wp-image-9324" srcset="/wp-content/uploads/2014/07/wsk_04.png 640w, /wp-content/uploads/2014/07/wsk_04-300x158.png 300w, /wp-content/uploads/2014/07/wsk_04-207x108.png 207w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>

<p>このComponent群は、<a href="https://developers.google.com/web/fundamentals/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Fundamentals</a>で使用されているComponentをまとめたもので、Googleのベストプラクティスが詰め込まれています。</p>

<p>UI Componentが用意されているWeb Starter Kitですが、GoogleはこれらのComponentを使うことを必ずしも推奨しているわけではありません。Web Starter Kitをベースに独自のスタイルを開発し、自分のStarter Kitを作っていきましょう。</p>

<h2>gulpによる作業の自動化</h2>

<p>Web Starter Kitには開発を補助するツールとして、<a href="http://gulpjs.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">gulp</a>(Node.js製のタスクランナー)を採用しています。gulpを使えば様々な作業をタスク化し、自動化することができます。</p>

<p>用意されているタスクは以下の様なものです。</p>

<ul>
<li>ローカルサーバ</li>
<li>マルチデバイスでのオートリロード</li>
<li>Sassのコンパイル</li>
<li>ベンダープレフィックスの付与</li>
<li>HTML、CSS、JavaScriptの最適化とminify</li>
<li>画像の最適化</li>
<li>PageSpeed Insightsによるパフォーマンス測定</li>
</ul>

<p>これらのタスクを活用することでより効率的に開発を進めることができるでしょう。</p>

<h3>タスクの実行</h3>

<p>gulpのタスクを実行してみましょう。</p>

<p>gulpのタスクは、<code>gulpfile.js</code>に書かれています。デフォルトで10数個のタスクが記述されていますが、主に使うのは4つです。それぞれを実行して試してみましょう。</p>

<h3>ローカルサーバとファイルの監視</h3>

<p>ローカルサーバを立ち上げ、各種ファイルの変更を監視してそれぞれのタスクを実行してくれます。 
開発中はこのコマンドを実行しておくことになります。</p>

<p><code>$ gulp serve</code></p>

<p>コマンドを実行するとブラウザが立ち上がり、<code>app</code> フォルダをドキュメンルートとして、<code>index.html</code>が開かれます。</p>

<p><img src="/wp-content/uploads/2014/07/wsk_01.png" alt="wsk_01" width="1000" height="593" class="aligncenter size-full wp-image-8941" srcset="/wp-content/uploads/2014/07/wsk_01.png 640w, /wp-content/uploads/2014/07/wsk_01-300x177.png 300w, /wp-content/uploads/2014/07/wsk_01-207x122.png 207w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>

<p>ローカルファイルに変更があった場合、<a href="http://www.browsersync.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">BrowserSync</a>によって、変更がブラウザに反映されます。試しにトップページ(app/index.html)を編集して保存してみると、自動的にブラウザがリロードされ、最新の内容が反映されるのが確認できます。</p>

<p><img src="/wp-content/uploads/2014/07/wsk_02.png" alt="wsk_02" width="1000" height="593" class="aligncenter size-full wp-image-8942" srcset="/wp-content/uploads/2014/07/wsk_02.png 640w, /wp-content/uploads/2014/07/wsk_02-300x177.png 300w, /wp-content/uploads/2014/07/wsk_02-207x122.png 207w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>

<h3>ビルドして成果物を生成する</h3>

<p>Sassのコンパイルや各種ファイルのminify、画像の最適化などを行うコマンドです。生成したファイルは<code>dist</code>フォルダに格納されます。</p>

<p><code>$ gulp</code></p>

<p>このコマンドは以下のようなタスクを含んでいます。</p>

<ul>
<li>CSSファイルの生成

<ul>
<li>Sassのコンパイル</li>
<li>ベンダープレフィックスの付与</li>
<li>未使用セレクタの削除</li>
<li>結合と縮小</li>
</ul></li>
<li>JSファイルの生成

<ul>
<li>結合と難読化と縮小</li>
</ul></li>
<li>HTMLの縮小</li>
<li>画像の最適化</li>
</ul>

<h3>ビルドしたファイルをローカルサーバで確認する</h3>

<p><code>serve</code>タスクで、開発中のファイル(appフォルダ以下)をローカルサーバで確認しましたが、ビルドした結果を確認する場合には、以下のコマンドを使います。</p>

<p><code>$ gulp serve:dist</code></p>

<p>ビルドが行われた後に<code>dist</code>フォルダをドキュメントルートとしたローカルサーバが立ち上がります。</p>

<h3>ページのパフォーマンスを確認する</h3>

<p>Googleが提供するパフォーマンス診断ツールの<a href="https://developers.google.com/speed/pagespeed/insights/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PageSpeed Insights</a>で、Webページのパフォーマンスを測ることができます。</p>

<p><code>$ gulp pagespeed</code></p>

<p><img src="/wp-content/uploads/2014/07/wsk_03.png" alt="wsk_03" width="1000" height="723" class="aligncenter size-full wp-image-8943" srcset="/wp-content/uploads/2014/07/wsk_03.png 640w, /wp-content/uploads/2014/07/wsk_03-300x216.png 300w, /wp-content/uploads/2014/07/wsk_03-207x149.png 207w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>

<p>このタスクではコマンドラインからPageSpeed Insightsの結果を取得できる<a href="https://www.npmjs.org/package/psi" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">psi(PageSpped Insights for Node)</a>が使われています。</p>

<p>注意したい点として、PageSpeed Insightsはパブリックなサイトしか測定できません。ページのパフォーマンス測定を行いたい場合には、ビルドした成果物をサーバにアップロードして公開状態にしておく必要があります。</p>

<p>測定するURLは<code>gulpfie.js</code>にて設定することができます。</p>

<h3>gulpタスク一覧</h3>

<p>このほかにも、いくつかのタスクが用意されています。タスク一覧は以下の通りです。カスタマイズして使う場合には、これら既存のタスクを参考にすると良いでしょう。</p>

<table>
<thead>
<tr>
  <th>タスク名</th>
  <th>処理内容</th>
</tr>
</thead>
<tbody>
<tr>
  <td>jshint</td>
  <td><a href="http://www.jshint.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">JSHint</a>を使ってapp/scripts以下のJavaScriptファイルに対して構文チェックを行います。.jshintrcでJSHintの設定を行うことができます</td>
</tr>
<tr>
  <td>images</td>
  <td>imageminによる画像の最適化を行います。Gif/JPEG/PNG/SVGに対応しています。</td>
</tr>
<tr>
  <td>copy</td>
  <td>appフォルダ配下のファイルをdistフォルダにコピーします。appフォルダ直下のHTMLファイルは除外されます</td>
</tr>
<tr>
  <td>fonts</td>
  <td>app/fontsフォルダのファイルをdist/fontsにコピーします</td>
</tr>
<tr>
  <td>styles:css</td>
  <td>app/stylesフォルダ以下のCSSファイルに対して、<a href="https://github.com/ai/autoprefixer" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Autoprefixer</a>によるベンダープレフィックスの付与を行います。対象とするブラウザはgulpfile.jsのAUTOPREFIXER_BROWSERSで設定します。</td>
</tr>
<tr>
  <td>styles:components</td>
  <td>UI ComponentのSassファイルをコンパイルし、app/styles/componentsに出力します。</td>
</tr>
<tr>
  <td>styles:scss</td>
  <td>app/styles配下のSassファイルをコンパイルし、Autoprefixerによるベンダープレフィックスの付与を行います。</td>
</tr>
<tr>
  <td>styles</td>
  <td>上記3つのstylesタスクをまとめて実行します。</td>
</tr>
<tr>
  <td>html</td>
  <td>appフォルダ配下のHTMLファイルをMinifyし、HTMLファイル内で読み込まれているJavaScriptファイルをuglifyによって結合してMinifyします。また、HTMLファイルで読み込まれているCSSファイルから、HTML内で使われていないスタイルの記述をuncssによって削除します。一連の処理が終了した後distフォルダに出力します。</td>
</tr>
<tr>
  <td>clean</td>
  <td>distフォルダと、一時ファイルを格納している.tmpフォルダを削除します。</td>
</tr>
<tr>
  <td>serve</td>
  <td>appフォルダをベースにローカルサーバを立て、各種ファイルの変更を監視します。監視しているファイルに変更があった場合、それぞれに設定されたタスクを実行し、borwserSyncによって自動リロードされます。</td>
</tr>
<tr>
  <td>serve:dist</td>
  <td>下記defaultタスクを実行した後、distフォルダをベースにローカルサーバを立てます。最終的な成果物の確認に使用します。</td>
</tr>
<tr>
  <td>default</td>
  <td>cleanとstylesのタスクを順に実行した後、jshint, html, images, fonts, copyのタスクを並列で実行します。</td>
</tr>
<tr>
  <td>pagespeed</td>
  <td><a href="https://developers.google.com/speed/pagespeed/insights/?hl=ja" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PageSpeed Insights</a>でページのパフォーマンスを測定します。</td>
</tr>
</tbody>
</table>

<p>それぞれのタスクは、<code>$ gulp タスク名</code> という形式のコマンドで個別に実行することができます。</p>

<h2>おわりに</h2>

<p>今回は、Web Starter Kitの概要、機能などについて紹介しました。Web Starter Kitをうまく自身のワークフローに取り入れ、開発の出発点にすれば、開発を始める際の準備が軽減されるかもしれません。</p>

<p>ただし、プリプロセッサやAltJSなど、開発に使用するものは人によって様々です。そういった場合にはWeb Starter Kitをそのまま使うのではなく、カスタマイズしたり、一部だけを参考にしたりするだけでもよいでしょう。</p>

<p>Web Starter Kitはまだ始まったばかりのプロジェクトなので、多くの人にフィットするようになるにはまだ時間がかかるかもしれません。ですが頻繁に更新はされていて、今後は<a href="https://github.com/google/web-starter-kit/tree/material-layout" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Material Designなどが取り込まれていく動き</a>もありますので、要注目のプロジェクトです。</p>
]]></content:encoded>
			</item>
		<item>
		<title>自分の書いたコードが即座に解析できる「StyleStats」でCSSを測ろう！</title>
		<link>/t32k/5743/</link>
		<pubDate>Thu, 20 Mar 2014 00:00:06 +0000</pubDate>
		<dc:creator><![CDATA[石本 光司]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Node.js]]></category>

		<guid isPermaLink="false">/?p=5743</guid>
		<description><![CDATA[こんにちわ、@t32kだよ！ 私は仕事では主にHTML/CSSコーディングを担当しているのですが、自分の書いたCSSがイケてるのか、そうでないのか、気になります。私、気になります！そうでなくても、他プロジェクトのCSSの...]]></description>
				<content:encoded><![CDATA[<p>こんにちわ、<a href="https://twitter.com/t32k/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">@t32k</a>だよ！</p>

<p>私は仕事では主にHTML/CSSコーディングを担当しているのですが、自分の書いたCSSがイケてるのか、そうでないのか、気になります。私、気になります！そうでなくても、他プロジェクトのCSSのレビューをお願いされたりすることもあるので、そのCSSがどのような状態であるのか、すばやく簡単に理解する必要性がありました。</p>

<p>そこで私は、<strong>StyleStats</strong>というNode.js製のツールを作りました。</p>

<ul>
<li><strong><a href="https://github.com/t32k/stylestats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">t32k/stylestats &#8211; GitHub</a></strong></li>
</ul>

<p>StyleStatsの使い方はとても簡単です。ターミナルから下記コマンドを打てば、すぐにCSSを解析した結果が得られます。</p>

<p><code>
$ npm install -g stylestats
$ stylestats http://t32k.me/static/blog/skelton.css
</code></p>

<p><strong>stylestats</strong>の引数に調べたいCSSファイルのパス（複数ファイル、リモートファイル対応）を入力すれば解析してくれます。</p>

<p>解析結果：</p>

<p><code>
StyleStats!
┌─────────────────────────────────┬──────────────────────┐
│ Stylesheets                     │ 1                    │
├─────────────────────────────────┼──────────────────────┤
│ Size                            │ 15.1KB               │
├─────────────────────────────────┼──────────────────────┤
│ Rules                           │ 207                  │
├─────────────────────────────────┼──────────────────────┤
│ Selectors                       │ 424                  │
├─────────────────────────────────┼──────────────────────┤
│ Simplicity                      │ 48.82%               │
├─────────────────────────────────┼──────────────────────┤
│ Most Identifers                 │ 4                    │
├─────────────────────────────────┼──────────────────────┤
│ Most Identifers Selector        │ nav ul li a          │
├─────────────────────────────────┼──────────────────────┤
│ Lowest Cohesion                 │ 24                   │
├─────────────────────────────────┼──────────────────────┤
│ Lowest Cohesion Selector        │ a.button             │
├─────────────────────────────────┼──────────────────────┤
│ Total Unique Font Sizes         │ 15                   │
├─────────────────────────────────┼──────────────────────┤
│ Unique Font Size                │ 10px                 │
│                                 │ 11px                 │
├─────────────────────────────────┼──────────────────────┤
│ Total Unique Colors             │ 11                   │
├─────────────────────────────────┼──────────────────────┤
│ Unique Color                    │ #000                 │
│                                 │ #777                 │
├─────────────────────────────────┼──────────────────────┤
│ Id Selectors                    │ 14                   │
├─────────────────────────────────┼──────────────────────┤
│ Universal Selectors             │ 0                    │
├─────────────────────────────────┼──────────────────────┤
│ Unqualified Attribute Selectors │ 7                    │
├─────────────────────────────────┼──────────────────────┤
│ Javascript Specific Selectors   │ 0                    │
├─────────────────────────────────┼──────────────────────┤
│ Important Keywords              │ 6                    │
├─────────────────────────────────┼──────────────────────┤
│ Float Properties                │ 4                    │
├─────────────────────────────────┼──────────────────────┤
│ Media Queries                   │ 8                    │
├─────────────────────────────────┼──────────────────────┤
│ Properties Count                │ width: 53            │
│                                 │ padding-left: 32     │
└─────────────────────────────────┴──────────────────────┘
</code></p>

<h2>なぜCSSを解析する必要があるのか？</h2>

<p>単純にCSSを記述することは非常に簡単です。スタイルを当てたいターゲットをセレクタで表現し、そのブロックの中にプロパティと値を宣言していくだけです。</p>

<p>この方法は閲覧するだけのドキュメントをスタイル付けするのには問題はなかったでしょう。しかし、現在、私たちが作っているWebサイトというのは単純に読むだけのドキュメントというよりかは、アプリケーションに近い性質があります。アプリケーションというのはユーザーがしてほしいタスクを実行するもので状態変化などを伴いますし、デスクトップアプリケーションを見れば分かる通り、ボタンのような多種多様なUIコンポーネントを必要とします。また、Webアプリケーションはひっきりなしにリニューアルされ、UIコンポーネントを修正したり、削除したり、追加する必要性があります。</p>

<p>Webアプリケーションを実現するために、従来のドキュメント型のサイトを構築してきたようなCSSの記述の仕方をしていれば、すぐさまCSSはスパゲッティ化することでしょう。そこで私たちは、CSSを書く上でのベストプラクティスを理解しなければなりません。</p>

<ul>
<li><a href="https://github.com/stubbornella/csslint/wiki/Rules" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Rules · stubbornella/csslint Wiki</a></li>
</ul>

<p>基本的には<a href="http://www.jshint.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">JSHint</a>のように、CSSにも<a href="http://csslint.net/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">CSS Lint</a>があるので、こういったツールを使えばよいと思います。StyleStatsで出力される指標は、CSS Lintで指摘される項目を参考にしたものなので、StyleStatsを実行することで、そのベストプラクティスから自分のコードがどれだけ適応しているのか確認することができます。</p>

<p>CSSは基本的にUIに責任を持っているため、JavaScriptのように致命的なエラーになることが少ないかと思います。しかし、これがやっかいな問題だと思います。なんとなく使っていないコードが残っていたり、なんとなく非効率なセレクターを書いてたりして、問題が顕在化したときに対処しようと思っても、スパゲッティすぎてどうすることもできない（Command+A Delete!）ということがあります。</p>

<p>そうならないためにも、日々のコード品質チェックは欠かせないものと思います。</p>

<ul>
<li><a href="https://github.com/es-analysis/plato" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">es-analysis/plato</a></li>
<li><a href="http://es-analysis.github.io/plato/examples/jquery/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Plato &#8211; JavaScript Introspection</a></li>
</ul>

<p>JavaScriptにPlatoという解析ツールがあるのですが、ちょうどこれのCSS版となれるよう今後ともStyleStatsを開発していくつもりです。</p>

<h2>各種指標の意味</h2>

<p>単純にファイルサイズだけを見ていても、そのCSSが良いCSSなのか悪いCSSなのかよく分かりませんので、その他にもCSS Lintをベースとした指標があります。</p>

<p><code>
$ stylestats -c path/to/.stylestatsrc
</code></p>

<p>のように、<code>.stylestatsrc</code>といった<a href="https://github.com/t32k/stylestats/blob/master/test/fixture/.stylestatsrc" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">設定JSONファイル</a>を一緒に読み込むことで、指標の表示・非表示などカスタマイズできます。</p>

<p>今回解説する指標はv2.2.1時での指標となります。今後、変更や追加などが想定されますので、留意しておいてください。</p>

<p><img src="http://i.imgur.com/kdCndXt.png" alt="" /></p>

<h3>Stylesheets</h3>

<p><code>stylesheets</code> : boolean</p>

<p>解析するスタイルシートの数。下記のように3つのファイルを解析すれば、この値は3になります。</p>

<p><code>
$ stylestats path/to/foo.css path/to/bar.css path/to/baz.css
</code></p>

<h3>Size</h3>

<p><code>"stylesheets": true</code></p>

<p>解析するスタイルシートのファイルサイズです。複数読み込んだ場合は、合計値となります（以後の指標に関しても同じ）。</p>

<h3>Data Uri Size と　Raito Of Data Uri Size</h3>

<p><code>"dataUriSize": true, "raitoOfDataUriSize": true,</code></p>

<p>スタイルシート内のインライン画像のサイズとスタイルシートに対する割合です。画像をインライン画像にすることでHTTPリクエストを抑えることができますが、外部CSSファイルがダウンロードされないとレンダリングが始まらないので、この数値が大きすぎるのもよくありません。</p>

<h3>Gzipped Size</h3>

<p><code>"gzippedSize": false</code></p>

<p>解析するスタイルシートのgzip後のファイルサイズです。処理が重いのでデフォルトでは、OFF(<code>false</code>)にしてあります。</p>

<h3>Rules</h3>

<p><code>"rules": true</code></p>

<p>ルール（宣言ブロック）の数です（上図参照）。</p>

<h3>Selectors</h3>

<p><code>"selectors": true</code></p>

<p>セレクタの数です（上図参照）。</p>

<h3>Simplicity</h3>

<p><code>"simplicity": true</code></p>

<p>Rules / Selectors をパーセンテージで表したものです。これは個人的に追加した指標なのですが、Sassの<code>@extend</code>などで継承していると下記のような状態になったりします。もちろんこれはバッドプラクティス的な継承の使い方かと思います。</p>

<p><a href="http://www.slideshare.net/t32k/sasscompass-20689960/63" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="http://i.imgur.com/rRcAxBK.png" alt="" /></a></p>

<p>このようにならないためにも、1つのルールには1つのセレクタが対応しているほうが、シンプルで都合がよいかとおもいます。</p>

<h3>Most Identifers と Most Identifers Selector</h3>

<p><code>"mostIdentifers": true,　"mostIdentifersSelector": true</code></p>

<p>セレクタの中で最も識別子が多いセレクタとその数を表示します。</p>

<p><code>
.foo .bar .baz { color: red; }
</code></p>

<p>上記のセレクタの識別子は、3つとなります。これもSassのネストのような機能を使っていると、深くなる傾向がありますので、気をつけてください。</p>

<h3>Lowest Cohesion と Lowest Cohesion Selector</h3>

<p><code>"lowestCohesion": true, "lowestCohesionSelector": true</code></p>

<p>cohesionは凝集度のことです。凝集度に関してはWikipediaを参照。</p>

<blockquote>凝集度（ぎょうしゅうど、コヒージョン、cohesion）とは、情報工学においてモジュール内のソースコードが特定の機能を提供すべく如何に協調しているかを表す度合いである。IPAが実施する情報処理技術者試験では、強度(きょうど、ストレングス、strength)という言葉が使われる。凝集度は順序尺度の一種であり、「凝集度が高い」とか「凝集度が低い」といった言い方で使われる。凝集度の高いモジュールは、堅牢性、信頼性、再利用性、読みやすさなどの点で好ましく、凝集度の低いモジュールは保守/評価/再利用/読解が難しいため、好ましくないとされる。</blockquote>

<ul>
<li><a href="http://ja.wikipedia.org/wiki/%E5%87%9D%E9%9B%86%E5%BA%A6" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">凝集度 &#8211; Wikipedia</a></li>
</ul>

<p>Lowest Cohesionは、<a href="http://article.enja.io/articles/solidcss.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">SOLID CSS</a>のSingle Responsibility Principle / 単一責任の原則に由来していて、一つのルールにあまりいろいろなスタイルを詰め過ぎないほうがよいです。そこで、Lowest Cohesionは各ルール内で一番宣言数が多いルールのセレクタと宣言数を表します。</p>

<h3>Total Unique Font Sizes と Unique Font Size</h3>

<p><code>"totalUniqueFontSizes": true, "uniqueFontSize": true</code></p>

<p>ユニークなフォントサイズとその数です。現状、<code>font-size</code>プロパティで宣言されている値をとっています。<code>font</code>プロパティによるショートカットで宣言されている値を取得できていません。</p>

<h3>Total Unique Colors と Unique Color</h3>

<p><code>"totalUniqueColors": true, "uniqueColor": true</code></p>

<p>ユニークなカラーとその数です、現状、<code>color</code>プロパティで宣言されている値を取得しています。<code>background-color</code>、<code>border-color</code>などの値を含むかは検討中です。</p>

<h3>ID Selectors</h3>

<p><code>"idSelectors": true</code></p>

<p>IDセレクタを含むセレクタの数です。</p>

<h3>Universal Selectors</h3>

<p><code>"universalSelectors": true</code></p>

<p>ユニバーサルセレクタを含むセレクタの数です。</p>

<h3>Unqualified Attribute Selectors</h3>

<p><code>"unqualifiedAttributeSelectors": true</code></p>

<p>不適切な属性セレクタの数です。</p>

<p><code>
.selected [type=text] {
    color: red;
}
</code></p>

<p>上記はこれに該当しますが、下記は該当しません。なぜならセレクタは右から左へと解析されるので、上記のセレクタの場合すべての要素をチェックするからです。</p>

<p><code>
.selected [type=text] a {
    color: red;
}
</code></p>

<ul>
<li><a href="https://github.com/stubbornella/csslint/wiki/Disallow-unqualified-attribute-selectors" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Disallow unqualified attribute selectors · stubbornella/csslint Wiki</a></li>
</ul>

<h3>Javascript Specific Selectors</h3>

<p><code>"javascriptSpecificSelectors": /[#\.]js\-/g</code></p>

<p><code>js-*</code>のようなJavaScript専用のセレクタを含む数です。DOMをJavaScriptから操作する場合、id属性やclass属性をフックとして利用します。これらの属性値には<code>js-*</code>のような接頭辞をつけて、JavaScript専用のものとし、これらのセレクタにはスタイルを付与しないほうが、独立性を保てます。</p>

<p>デフォルトの接頭辞には<code>js-*</code>を指定していますが、任意の正規表現オブジェクトを渡すことで変更可能です。</p>

<p>参考：</p>

<ul>
<li><a href="http://article.enja.io/articles/about-html-semantics-and-front-end-architecture.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">About HTML semantics and front-end architecture | en.ja Article</a></li>
</ul>

<h3>Important Keywords</h3>

<p><code>"importantKeywords": true</code></p>

<p><code>!important</code>の宣言数です。</p>

<h3>Float Properties</h3>

<p><code>"floatProperties": true</code></p>

<p><code>float</code>プロパティの宣言数です。あれこれfloatしてレイアウトをこねくり回すより、最初からグリッドフレームワークの導入を考えたほうが得策な場合もあります。</p>

<ul>
<li><a href="https://github.com/stubbornella/csslint/wiki/Disallow-too-many-floats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Disallow too many floats · stubbornella/csslint Wiki</a></li>
</ul>

<h3>Media Queries</h3>

<p><code>"mediaQueries": true</code></p>

<p>Media Queriesの宣言数です。</p>

<h3>Properties Count</h3>

<p><code>"propertiesCount": 10</code></p>

<p>どのプロパティがどれだけ宣言されているのか、デフォルトではTop10までランキング出力してくれます。大体のCSSの傾向というか、見れて楽しいです。全部見たかったら、<code>{"propertiesCount": 1000}</code>といった風に指定すればよいです。個人的にこれが欲しかったので作ったという背景もあります。</p>

<p>v2.2.1では以上の指標ですが、こういった指標があったら便利だなとか、この指標はこうしたほうがよいなどありましたら、どんどんIssuesをあげてください。よろしくお願いします。</p>

<ul>
<li><a href="https://github.com/t32k/stylestats/issues?state=open" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Issues · t32k/stylestats</a></li>
</ul>

<h2>CIツールとの統合</h2>

<p><code>
stylestats path/to/stylesheet.css -t csv
</code></p>

<p>という風にすればCSV形式で出力できるので、これを使って<a href="http://jenkins-ci.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Jenkins CI</a>上でファイルサイズの変遷など記録したいと思います。</p>

<ul>
<li><a href="https://github.com/t32k/stylestats/wiki/Plot-with-Jenkins" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Plot with Jenkins · t32k/stylestats Wiki</a></li>
</ul>

<p><a href="http://www.cloudbees.com/jenkins/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">CloudBees</a>社のサービスを使えば、月100分までのビルドが無料でJenkinsをクラウド上で利用できるので、それで試すのもよいかと思います。</p>

<p>Jenkins上で、グラフを作るために<a href="https://wiki.jenkins-ci.org/display/JENKINS/Plot+Plugin" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Plot Plugin</a>をインストールします。このプラグインは、Java properties、CSV、XMLにファイルを読み込みんでグラフを生成してくれます。</p>

<p>あとは、<a href="https://wiki.jenkins-ci.org/display/JENKINS/NodeJS+Plugin" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">NodeJS Plugin</a>などで、Node.jsとStyleStatsをインストールすれば準備完了です。</p>

<p><img src="http://i.imgur.com/PcH645t.png" alt="" /></p>

<p>ビルドの処理にStyleStatsを実行し、CSVで出力し、ビルド後の処理でそのCSVを読み込んであげれば、完了です。</p>

<p>あとは、GitHubと連携してレポジトリにコミットしたら、ビルドを実行させるなどトリガを設定してもらえればと思います。</p>

<p>File Size:
<img src="http://i.imgur.com/1c0cwgo.png" alt="1c0cwgo.png (750×450)" /></p>

<p>Style Info:
<img src="http://i.imgur.com/kF0CLWt.png" alt="kF0CLWt.png (750×450)" /></p>

<p>あとはデータが貯まると、こんなかんじでグラフが生成されます。CSSのリファクタリングは地道な作業ですので、ファイルサイズだけでなく、セレクタ数やルール数など細かい指標の変遷をチェックしていれば、モチベーションも持続できるかと思います。ぜひとも、導入してみてください。</p>

<p>また、ローカルで確認したい場合はGrunt/Gulpプラグインもありますので、そちらを使ってもらえるとよいです。</p>

<ul>
<li><a href="https://github.com/tvooo/grunt-stylestats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">tvooo/grunt-stylestats</a></li>
<li><a href="https://github.com/1000ch/gulp-stylestats" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">1000ch/gulp-stylestats</a></li>
</ul>

<p>今後はtestspecファイルを認識できるようにし、例えばImpotant Keywordsが10個以上だったらテストを失敗させるといったことを、Jenkins・Travis-CIなどでできるようにしたいと思っています。</p>

<p>みなさんもぜひ使ってみてくださいね！</p>

<p>[追記] 2014年4月4日</p>

<ul>
<li><a href="http://www.stylestats.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Online tool for StyleStats</a></li>
</ul>

<p>オンラインでStyleStatsを使えるツールを公開しました。</p>
]]></content:encoded>
			</item>
		<item>
		<title>シグナリングサーバーを応用！ 「WebRTCを使って複数人で話してみよう」</title>
		<link>/mganeko/5438/</link>
		<pubDate>Tue, 04 Mar 2014 01:00:10 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=5438</guid>
		<description><![CDATA[連載： WebRTCを使ってみよう！ (4)こんにちは！ 前回はシグナリングサーバーを動かして、WebRTCでPeer-to-Peer通信をつなぐ処理を作りました。最後に書いた通り、前回の実装ではサーバーあたり2人だけし...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc-beginner/" class="series-158" title="WebRTCを使ってみよう！" data-wpel-link="internal">WebRTCを使ってみよう！</a> (4)</div><p>こんにちは！ <a href="https://html5experts.jp/mganeko/5349/" title="WebRTC初心者でも簡単にできる！Node.jsで仲介（シグナリング）を作ってみよう" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">前回はシグナリングサーバーを動かして</a>、WebRTCでPeer-to-Peer通信をつなぐ処理を作りました。最後に書いた通り、前回の実装ではサーバーあたり2人だけしか同時に通知できません。今回はこれをもっと実用的にしていきましょう。
※今回もNode学園祭2013で発表した内容と共通の部分が多いです。<a href="http://www.slideshare.net/mganeko/2013-web-rtcnode" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">その時の資料</a>も併せてご参照ください。</p>

<p>※こちらの記事は2014年に書かれました。<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">2016年8月のアップデート記事</a>がありますので、そちらもご参照ください。</p>

<h2>複数会議室を作ろう</h2>

<p>前回作ったのは、いわばカップル1組限定サイトのシングルテナントアプリでした（左）。これを複数組が共存できる、マルチテナント（複数会議室）のアプリに改造します（右）。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/rtc11_multiroom.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/rtc11_multiroom-300x199.png" alt="rtc11_multiroom" width="300" height="199" class="alignnone size-medium wp-image-5439" srcset="/wp-content/uploads/2014/02/rtc11_multiroom-300x199.png 300w, /wp-content/uploads/2014/02/rtc11_multiroom-207x137.png 207w, /wp-content/uploads/2014/02/rtc11_multiroom.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
複数組が共存できない理由は、シグナリングの通信が同じシグナリングサーバーに接続している全員に飛んでしまうからです。これを混線しないように分離してあげる必要があります。シグナリングサーバーで利用しているsocket.ioでは、これを簡単に実現できるroom機能があります。</p>

<ul>
    <li>roomに入室する&#8230; socket.join()</li>
    <li>roomから退室する&#8230; socket.leave()</li>
    <li>room内だけにメッセージを送る&#8230; socket.broadcast.to.emit()</li>
</ul>

<p>まずクライアント側を一部手直しします。
</p><pre class="crayon-plain-tag">function onOpened(evt) {
    console.log('socket opened.');
    socketReady = true;

    var roomname = getRoomName(); // 会議室名を取得する
    socket.emit('enter', roomname);
}</pre><p> 
ソケット接続が確立したら、シグナリングサーバーに対して入室要求(enter)を送っています。ここでgetRoomName()はアプリケーション側で実装する部分で、何らかの方法で会議室名を取得して返します。
手抜きなサンプルとしてはこんな感じでしょうか。URLの?以降をそのまま切り出して返しています。</p>

<p></p><pre class="crayon-plain-tag">function getRoomName() { // たとえば、 URLに  ?roomname  とする
  var url = document.location.href;
  var args = url.split('?');
  if (args.length &gt; 1) {
    var room = args[1];
    if (room != "") {
      return room;
    }
  }
  return "_defaultroom";
}</pre><p></p>

<p>ついでにもう少し直しましょう。実は前回までのサンプルではカメラしかアクセスしていません。マイクはアクセスしていないので、声が聞こえません。今回はマイクも取得するように一カ所だけ修正します。※webkitGetUserMedia()の引数を変更</p>

<p></p><pre class="crayon-plain-tag">// start local video
  function startVideo() {
    navigator.webkitGetUserMedia({video: true, audio: true},  // &lt;--- audio: true に変更
      function (stream) { // success
        localStream = stream;
        localVideo.src = window.webkitURL.createObjectURL(stream);
        localVideo.play();
        localVideo.volume = 0;
      },
      function (error) { // error
        console.error('An error occurred: [CODE ' + error.code + ']');
        return;
      }
    );
  }</pre><p> 
※同一PC上で複数の2つのウィンドウ/タブを開いて通信する場合、ハウリングしやすいので音量を絞るか、ヘッドフォンを利用してください。</p>

<p>今度はシグナリングサーバー側も修正しましょう。クライアントからの入室要求(enter)に対応するのと、会議室内だけに通信する部分です。</p>

<p></p><pre class="crayon-plain-tag">// 入室
socket.on('enter', function(roomname) {
    socket.set('roomname', roomname);
    socket.join(roomname);
});

socket.on('message', function(message) {
  emitMessage('message', message);
});

socket.on('disconnect', function() {
  emitMessage('user disconnected');
});

// 会議室名が指定されていたら、室内だけに通知
function emitMessage(type, message) {
  var roomname;
  socket.get('roomname', function(err, _room) {  roomname = _room;  });

  if (roomname) {  socket.broadcast.to(roomname).emit(type, message);   }
  else {   socket.broadcast.emit(type, message);   }
}</pre><p> 
シグナリングサーバーを起動しなおして、HTMLをリロードすれば、複数会議室に対応したマルチテナントアプリの完成です。
URLの後ろに ?room1 や ?room2 などのように会議室名を指定すれば、 その部屋の人と通信できます。
<br /></p>

<h2>複数人で通信してみたい</h2>

<p>次は2人だけでなく、複数人で同時に話せるようにしてみたいと思います。こんな感じです。
<a href="https://html5experts.jp/wp-content/uploads/2014/02/rtc_nn.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/rtc_nn-300x241.png" alt="rtc_nn" width="300" height="241" class="alignnone size-medium wp-image-5446" srcset="/wp-content/uploads/2014/02/rtc_nn-300x241.png 300w, /wp-content/uploads/2014/02/rtc_nn-207x166.png 207w, /wp-content/uploads/2014/02/rtc_nn.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<h3>複数のPeer-to-Peer通信を扱うには</h3>

<p>複数人と通信するには、クライアント側（ブラウザ側）に相手の数だけPeerConnectionが必要です。それを管理するための便宜上のクラスを作ります。
通信状況や、相手のID(socket.ioが割り振る）を保持します。 
</p><pre class="crayon-plain-tag">var MAX_CONNECTION_COUNT = 3;
var connections = {}; // Connection hash
function Connection() { // Connection Class
  var self = this;
  var id = "";  // socket.id of partner
  var peerconnection = null; // RTCPeerConnection instance
  var established = false; // is Already Established
  var iceReady = false;
}

function getConnection(id) {
  var con = null;
  con = connections[id];
  return con;
}

function addConnection(id, connection) {
  connections[id] = connection;
}</pre><p> 
ついでに、複数のConnectionを格納する配列と、それを管理する関数群も用意します。ここに挙げた2つ以外に、getConnectionCount(), isConnectPossible(), deleteConnection(id), などなど。（詳細は最後に全ソースを掲載します）
<br /></p>

<h3>シグナリングを手直し</h3>

<p>シグナリングの流れも手直しが必要です。
今までのシグナリングでは、最初にOffer SDPを送る際に同じ部屋の全員に送っていました(broadcast)。すると全員からAnswer SDPが返ってきてしまうので、情報が衝突してしまいます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/sdp_nn_corrupt.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/sdp_nn_corrupt-300x200.png" alt="sdp_nn_corrupt" width="300" height="200" class="alignnone size-medium wp-image-5453" srcset="/wp-content/uploads/2014/02/sdp_nn_corrupt-300x200.png 300w, /wp-content/uploads/2014/02/sdp_nn_corrupt-207x138.png 207w, /wp-content/uploads/2014/02/sdp_nn_corrupt.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
そこで、まず部屋に誰が居るかを確認し(call-response)、一人ずつ個別にOffer-Answerのやり取りをする必要があります。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/sdp_call_response.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/sdp_call_response-300x225.png" alt="sdp_call_response" width="300" height="225" class="alignnone size-medium wp-image-5456" srcset="/wp-content/uploads/2014/02/sdp_call_response-300x225.png 300w, /wp-content/uploads/2014/02/sdp_call_response-207x155.png 207w, /wp-content/uploads/2014/02/sdp_call_response.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
では、クライアント側のソースを直していきましょう。</p>

<p></p><pre class="crayon-plain-tag">function call() {
  if (! isLocalStreamStarted()) return;
  socket.json.send({type: "call"});
}

function onMessage(evt) {
  var id = evt.from;
  var target = evt.sendto;
  var conn = getConnection(id);

  if (evt.type === 'call') {
    if (! isLocalStreamStarted()) return;
    if (conn) return;  // already connected

    if (isConnectPossible()) {
      socket.json.send({type: "response", sendto: id });
    }
    else {   console.warn('max connections. so ignore call');     }
  }
  else if (evt.type === 'response') {
    sendOffer(id);
    return;
  }
}</pre><p> 
call()で全員にbroadcastし、受け取った側はonMessage()の中でcallを受け取ると、responseを相手を特定して送り返します。発信側はreponseを受け取ると、その相手に対してoffer SDPを送っています。 sendOffer()の中身もちょっと変わります。
</p><pre class="crayon-plain-tag">function sendOffer(id) {
  var conn = getConnection(id); // &lt;--- すでに作成済のコネクションを探す
  if (!conn) {
    conn = prepareNewConnection(id);
  }

  conn.peerconnection.createOffer(function (sessionDescription) { // in case of success
    conn.iceReady = true;
    conn.peerconnection.setLocalDescription(sessionDescription);
    sessionDescription.sendto = conn.id; // &lt;--- 送る相手を指定する
    sendSDP(sessionDescription);
  }, function () { // in case of error
    console.log(&quot;Create Offer failed&quot;);
  }, mediaConstraints);
  conn.iceReady = true;
}</pre><p> 
同様に、sendAnswer()もちょっと変えます。複数のコネクションに対応するのと、送る相手を指定するのが変更点です。</p>

<p></p><pre class="crayon-plain-tag">function sendAnswer(evt) {
  console.log('sending Answer. Creating remote session description...' );
  var id = evt.from;
  var conn = getConnection(id); // &lt;--- すでに作成済のコネクションを探す
  if (! conn) {
    console.error('peerConnection not exist!');
    return
  }

  conn.peerconnection.createAnswer(function (sessionDescription) { 
    // in case of success
    conn.iceReady = true;
    conn.peerconnection.setLocalDescription(sessionDescription);
    sessionDescription.sendto = id; // &lt;--- 送る相手を指定する
    sendSDP(sessionDescription);
  }, function () { // in case of error
    console.log(&quot;Create Answer failed&quot;);
  }, mediaConstraints);
  conn.iceReady = true;
}</pre><p> 
<br />
さらに、SDPを覚える処理も複数セッションに対応させます。</p>

<p></p><pre class="crayon-plain-tag">function setOffer(evt) {
  var id = evt.from;
  var conn = getConnection(id);
  if (! conn) {
    conn = prepareNewConnection(id);
    conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  else {
    console.error('peerConnection alreay exist!');
  }
}

function setAnswer(evt) {
  var id = evt.from;
  var conn = getConnection(id);
  if (! conn) {
    console.error('peerConnection not exist!');
    return
  }
  conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
}</pre><p> 
<br />
引き続きConnectionを生成する処理も修正します。今まではPeerConnectionを直接返していましたが、今回はConnectionのインスタンスを生成し、そこにPeerConnectionを保持させます。また、Candidateの送信時にも相手先を指定します。
</p><pre class="crayon-plain-tag">function prepareNewConnection(id) {
  var pc_config = {"iceServers":[]};
  var peer = null;
  try {
    peer = new webkitRTCPeerConnection(pc_config);
  } catch (e) {
    console.log("Failed to create PeerConnection, exception: " + e.message);
  }
  var conn = new Connection();  // &lt;--- Connectionを作成し、PeerConnectionを保持させる
  conn.id = id;
  conn.peerconnection = peer;
  peer.id = id;
  addConnection(id, conn);

  // send any ice candidates to the other peer
  peer.onicecandidate = function (evt) {
  if (evt.candidate) {
    console.log(evt.candidate);
    sendCandidate({type: &quot;candidate&quot;, 
                          sendto: conn.id, // &lt;-- 送信先を指定
                          sdpMLineIndex: evt.candidate.sdpMLineIndex,
                          sdpMid: evt.candidate.sdpMid,
                          candidate: evt.candidate.candidate});
    } else {
      console.log(&quot;End of candidates. ------------------- phase=&quot; + evt.eventPhase);
      conn.established = true;
    }
  };

  // ...
}</pre><p> 
<br />
Candidateの送信部分を変更したので、Candidateを受信した処理も変更しましょう。 onCandidate()も複数コネクションに対応させます。</p>

<p></p><pre class="crayon-plain-tag">function onCandidate(evt) {
  var id = evt.from;
  var conn = getConnection(id);
  if (! conn) {
    console.error('peerConnection not exist!');
    return;
  }
    
  // --- check if ice ready ---
  if (! conn.iceReady) {
    console.warn("PeerConn is not ICE ready, so ignore");
    return;
  }
	  
  var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
  console.log("Received Candidate...")
  console.log(candidate);
  conn.peerconnection.addIceCandidate(candidate);
}</pre><p>
<br />
さてさて、クライアント側の修正はいったん終わりにして、次はシグナリングサーバー側を修正します。前半では部屋の中だけに送信する機能を加えましたが、次は特定の相手にだけ送信できるようにします。 
</p><pre class="crayon-plain-tag">socket.on('message', function(message) {
    // 送信元のidをメッセージに追加（相手が分かるように）
    message.from = socket.id;

    // 送信先が指定されているか？
    var target = message.sendto;
    if (target) {
      // 送信先が指定されていた場合は、その相手のみに送信
      io.sockets.socket(target).emit('message', message);
      return;
    }

    // 特に指定がなければ、ブロードキャスト
    emitMessage('message', message);
  });</pre><p>
ここまででいったん動かしてみましょう。まだ映像が2人までしか出ませんが、通信はできるはずです。</p>

<h3>複数の映像を扱えるようにしよう</h3>

<p>ここまでで複数人相手に通信をできるようにしました。でも通信できても映像は見えていません。ちゃんと見えるようにしましょう。
まずHTMLに複数のvideoタグを配置します。
</p><pre class="crayon-plain-tag">&lt;div style="position: relative;"&gt;
   &lt;video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt;
   &lt;!-- &lt;video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt; --&gt;
   &lt;video id="webrtc-remote-video-0" autoplay style="position: absolute; top: 250px; left: 0px; width: 320px; height: 240px; border: 1px solid black; "&gt;&lt;/video&gt;
   &lt;video id="webrtc-remote-video-1" autoplay style="position: absolute; top: 250px; left: 330px; width: 320px; height: 240px; border: 1px solid black; "&gt;&lt;/video&gt;
   &lt;video id="webrtc-remote-video-2" autoplay style="position: absolute; top: 0px; left: 330px; width: 320px; height: 240px; border: 1px solid black; " &gt;&lt;/video&gt;
  &lt;/div&gt;</pre><p></p>

<p>その複数のvideoタグを扱えるような関数群を追加します。※本当は動的にタグを作成、削除するのがかっこいいのですが…。
</p><pre class="crayon-plain-tag">var localVideo = document.getElementById('local-video');
  //var remoteVideo = document.getElementById('remote-video');
  var localStream = null;
  var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};

  // ---- multi people video &amp; audio ----
  var videoElementsInUse = {};
  var videoElementsStandBy = {};
  pushVideoStandBy(getVideoForRemote(0));
  pushVideoStandBy(getVideoForRemote(1));
  pushVideoStandBy(getVideoForRemote(2));


  function getVideoForRemote(index) {
    var elementID = 'webrtc-remote-video-' + index;
    var element = document.getElementById(elementID);
    return element;
  }

  function getAudioForRemote(index) {
    var elementID = 'webrtc-remote-audio-' + index;
    var element = document.getElementById(elementID);
    return element;
  }

  // ---- video element management ---
  function pushVideoStandBy(element) {
    videoElementsStandBy[element.id] = element;
  }

  function popVideoStandBy() {
    var element = null;
    for (var id in videoElementsStandBy) {
      element = videoElementsStandBy[id];
      delete videoElementsStandBy[id];
      return element;
    }
    return null;
  }

  function pushVideoInUse(id, element) {
    videoElementsInUse[id] = element;
  }

  function popVideoInUse(id) {
    element = videoElementsInUse[id];
    delete videoElementsInUse[id];
    return element;
  }

  function attachVideo(id, stream) {
    console.log('try to attach video. id=' + id);
    var videoElement = popVideoStandBy();
    if (videoElement) {
      videoElement.src = window.URL.createObjectURL(stream);
      console.log("videoElement.src=" + videoElement.src);
      pushVideoInUse(id, videoElement);
      videoElement.style.display = 'block';
    }
    else {
      console.error('--- no video element stand by.');
    }
  }

  function detachVideo(id) {
    console.log('try to detach video. id=' + id);
    var videoElement = popVideoInUse(id);
    if (videoElement) {
      videoElement.pause();
      videoElement.src = "";
      console.log("videoElement.src=" + videoElement.src);
      pushVideoStandBy(videoElement);
    }
    else {
      console.warn('warning --- no video element using with id=' + id);
    }
  }

  // ...</pre><p> 
※ソース全体は最後に記載します。
<br /><br />
これで準備が整いました。早速接続してみましょう。
[Start video]ボタンを押して、[Connect]を押す、という操作を一人ずつ行ってください。一人、また一人と接続され、最大4人まで通信できます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/rtc4.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/rtc4-300x240.png" alt="rtc4" width="300" height="240" class="alignnone size-medium wp-image-5468" srcset="/wp-content/uploads/2014/02/rtc4-300x240.png 300w, /wp-content/uploads/2014/02/rtc4-207x165.png 207w, /wp-content/uploads/2014/02/rtc4.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<h2>補足 （2014/03/09追記）</h2>

<p>Twitter経由でご指摘をいただきました。<strong>User B/Cからresponseではなく、Offerを送れば良いのでは？</strong><br />
アドバイスに従うと、次のように改善されます。</p>

<ul>
    <li>現状：User Aからcall, User B/Cからresponse, User AからOffer, User B/CからAnswer</li>
    <li>改善：User Aからcallme, User B/CからOffer, User AからAnswer</li>
</ul>

<p>確かにその通りです。メッセージのやり取りが片道分少なくなり、すっきりしますね。
ご指摘ありがとうございました。</p>

<h2>次回は</h2>

<p>次回は最終回の予定です。NATやFirewallを越えて通信するための、STUN/TURNについて説明したいと思います。</p>

<h2>今回のソースコード</h2>

<h3>シグナリングサーバー (node.js)</h3>

<p></p><pre class="crayon-plain-tag">var port = 9001;
var io = require('socket.io').listen(port);
console.log((new Date()) + " Server is listening on port " + port);

io.sockets.on('connection', function(socket) {
  // 入室
  socket.on('enter', function(roomname) {
    socket.set('roomname', roomname);
    socket.join(roomname);
  });
 
  socket.on('message', function(message) {
    // 送信元のidをメッセージに追加（相手が分かるように）
    message.from = socket.id;

    // 送信先が指定されているか？
    var target = message.sendto;
    if (target) {
	　　// 送信先が指定されていた場合は、その相手のみに送信
      io.sockets.socket(target).emit('message', message);
      return;
    }

	// 特に指定がなければ、ブロードキャスト
    emitMessage('message', message);
  });
 
  socket.on('disconnect', function() {
    emitMessage('user disconnected');
  });
 
  // 会議室名が指定されていたら、室内だけに通知
  function emitMessage(type, message) {
    var roomname;
    socket.get('roomname', function(err, _room) {  roomname = _room;  });
 
    if (roomname) {  socket.broadcast.to(roomname).emit(type, message);   }
    else {   socket.broadcast.emit(type, message);   }
  }
});</pre><p></p>

<h3>クライアント側 (HTML, JavaScript)</h3>

<p></p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;WebRTC 4&lt;/title&gt;  
&lt;/head&gt;
&lt;body&gt;
  &lt;button type="button" onclick="startVideo();"&gt;Start video&lt;/button&gt;
  &lt;button type="button" onclick="stopVideo();"&gt;Stop video&lt;/button&gt;
  &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
  &lt;!-- &lt;button type="button" onclick="connect();"&gt;Connect&lt;/button&gt; --&gt;
  &lt;button type="button" onclick="call();"&gt;Connect&lt;/button&gt;
  &lt;button type="button" onclick="hangUp();"&gt;Hang Up&lt;/button&gt;
  &lt;br /&gt;
  &lt;div style="position: relative;"&gt;
   &lt;video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt;
   &lt;!-- &lt;video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt; --&gt;
   &lt;video id="webrtc-remote-video-0" autoplay style="position: absolute; top: 250px; left: 0px; width: 320px; height: 240px; border: 1px solid black; "&gt;&lt;/video&gt;
   &lt;video id="webrtc-remote-video-1" autoplay style="position: absolute; top: 250px; left: 330px; width: 320px; height: 240px; border: 1px solid black; "&gt;&lt;/video&gt;
   &lt;video id="webrtc-remote-video-2" autoplay style="position: absolute; top: 0px; left: 330px; width: 320px; height: 240px; border: 1px solid black; " &gt;&lt;/video&gt;
  &lt;/div&gt;
  
  &lt;!---
  &lt;p&gt;
   SDP to send:&lt;br /&gt;
   &lt;textarea id="text-for-send-sdp" rows="5" cols="100" disabled="1"&gt;SDP to send&lt;/textarea&gt;
  &lt;/p&gt;
  &lt;p&gt;
   SDP to receive:&lt;br /&gt;
   &lt;textarea id="text-for-receive-sdp" rows="5" cols="100"&gt;&lt;/textarea&gt;&lt;br /&gt;
   &lt;button type="button" onclick="onSDP();"&gt;Receive SDP&lt;/button&gt;
  &lt;/p&gt;
  
  &lt;p&gt;
   ICE Candidate to send:&lt;br /&gt;
   &lt;textarea id="text-for-send-ice" rows="5" cols="100" disabled="1"&gt;ICE Candidate to send&lt;/textarea&gt;
  &lt;/p&gt;
  &lt;p&gt;  
   ICE Candidates to receive:&lt;br /&gt;
   &lt;textarea id="text-for-receive-ice" rows="5" cols="100"&gt;&lt;/textarea&gt;&lt;br /&gt;
   &lt;button type="button" onclick="onICE();"&gt;Receive ICE Candidates&lt;/button&gt;
  &lt;/p&gt;
  ---&gt;
  
  &lt;!---- socket ------&gt;
  &lt;script src="http://localhost:9001/socket.io/socket.io.js"&gt;&lt;/script&gt;
  
  &lt;script&gt;
  var localVideo = document.getElementById('local-video');
  //var remoteVideo = document.getElementById('remote-video');
  var localStream = null;
  var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};

  // ---- multi people video &amp; audio ----
  var videoElementsInUse = {};
  var videoElementsStandBy = {};
  pushVideoStandBy(getVideoForRemote(0));
  pushVideoStandBy(getVideoForRemote(1));
  pushVideoStandBy(getVideoForRemote(2));


  function getVideoForRemote(index) {
    var elementID = 'webrtc-remote-video-' + index;
    var element = document.getElementById(elementID);
    return element;
  }

  // ---- video element management ---
  function pushVideoStandBy(element) {
    videoElementsStandBy[element.id] = element;
  }

  function popVideoStandBy() {
    var element = null;
    for (var id in videoElementsStandBy) {
      element = videoElementsStandBy[id];
      delete videoElementsStandBy[id];
      return element;
    }
    return null;
  }

  function pushVideoInUse(id, element) {
    videoElementsInUse[id] = element;
  }

  function popVideoInUse(id) {
    element = videoElementsInUse[id];
    delete videoElementsInUse[id];
    return element;
  }

  function attachVideo(id, stream) {
    console.log('try to attach video. id=' + id);
    var videoElement = popVideoStandBy();
    if (videoElement) {
      videoElement.src = window.URL.createObjectURL(stream);
      console.log("videoElement.src=" + videoElement.src);
      pushVideoInUse(id, videoElement);
      videoElement.style.display = 'block';
    }
    else {
      console.error('--- no video element stand by.');
    }
  }

  function detachVideo(id) {
    console.log('try to detach video. id=' + id);
    var videoElement = popVideoInUse(id);
    if (videoElement) {
      videoElement.pause();
      videoElement.src = "";
      console.log("videoElement.src=" + videoElement.src);
      pushVideoStandBy(videoElement);
    }
    else {
      console.warn('warning --- no video element using with id=' + id);
    }
  }

  function detachAllVideo() {
    var element = null;
    for (var id in videoElementsInUse) {
      detachVideo(id);
    }
  }

  function getFirstVideoInUse() {
    var element = null;
    for (var id in videoElementsInUse) {
      element = videoElementsInUse[id];
      return element;
    }
    return null;
  }

  function getVideoCountInUse() {
    var count = 0;
    for (var id in videoElementsInUse) {
      count++;
    }
    return count;
  }
  
  
  function isLocalStreamStarted() {
    if (localStream) {
      return true;
    }
    else {
      return false;
    }
  }

  // -------------- multi connections --------------------
  var MAX_CONNECTION_COUNT = 3;
  var connections = {}; // Connection hash
  function Connection() { // Connection Class
    var self = this;
    var id = "";  // socket.id of partner
    var peerconnection = null; // RTCPeerConnection instance
    var established = false; // is Already Established
    var iceReady = false;
  }

  function getConnection(id) {
    var con = null;
    con = connections[id];
    return con;
  }

  function addConnection(id, connection) {
    connections[id] = connection;
  }

  function getConnectionCount() {
    var count = 0;
    for (var id in connections) {
      count++;
    }

    console.log('getConnectionCount=' + count);
    return count;
  }

  function isConnectPossible() {
    if (getConnectionCount() &lt; MAX_CONNECTION_COUNT)
      return true;
    else
      return false;
  }

  function getConnectionIndex(id_to_lookup) {
    var index = 0;
    for (var id in connections) {
      if (id == id_to_lookup) {
        return index;
      }

      index++;
    }

    // not found
    return -1;
  }

  function deleteConnection(id) {
    delete connections[id];
  }

  function stopAllConnections() {
    for (var id in connections) {
      var conn = connections[id];
      conn.peerconnection.close();
      conn.peerconnection = null;
      delete connections[id];
    }
  }

  function stopConnection(id) {
    var conn = connections[id];
    if(conn) {
      console.log('stop and delete connection with id=' + id);
      conn.peerconnection.close();
      conn.peerconnection = null;
      delete connections[id];
    }
    else {
      console.log('try to stop connection, but not found id=' + id);
    }
  }

  function isPeerStarted() {
    if (getConnectionCount() &gt; 0) {
      return true;
    }
    else {
      return false;
    }
  }

  
  // ---- socket ------
  // create socket
  var socketReady = false;
  var port = 9001;
  var socket = io.connect('http://localhost:/' + port + '/');
  
  // socket: channel connected
  socket.on('connect', onOpened)
        .on('message', onMessage);

  function onOpened(evt) {
    console.log('socket opened.');
    socketReady = true;
	
    var roomname = getRoomName(); // 会議室名を取得する
    socket.emit('enter', roomname);
	console.log('enter to ' + roomname);
  }

  // socket: accept connection request
  function onMessage(evt) {
    var id = evt.from;
    var target = evt.sendto;
    var conn = getConnection(id);

    if (evt.type === 'call') {
      if (! isLocalStreamStarted()) {
	    return;
	  }
      if (conn) {
	    return;  // already connected
	  }

      if (isConnectPossible()) {
        socket.json.send({type: "response", sendto: id });
      }
      else {
	    console.warn('max connections. so ignore call'); 
	  }
	  return;
    }
    else if (evt.type === 'response') {
      sendOffer(id);
      return;
    } else if (evt.type === 'offer') {
      console.log("Received offer, set offer, sending answer....")
      onOffer(evt);	  
    } else if (evt.type === 'answer' &amp;&amp; isPeerStarted()) {  // **
      console.log('Received answer, settinng answer SDP');
	  onAnswer(evt);
    } else if (evt.type === 'candidate' &amp;&amp; isPeerStarted()) { // **
      console.log('Received ICE candidate...');
	  onCandidate(evt);
    } else if (evt.type === 'user dissconnected' &amp;&amp; isPeerStarted()) { // **
      console.log("disconnected");
      //stop();
	  detachVideo(id); // force detach video
      stopConnection(id);
    }
  }

  function getRoomName() { // たとえば、 URLに  ?roomname  とする
    var url = document.location.href;
    var args = url.split('?');
    if (args.length &gt; 1) {
      var room = args[1];
      if (room != "") {
        return room;
      }
    }
    return "_defaultroom";
  }
  
  // ----------------- handshake --------------
  //var textForSendSDP = document.getElementById('text-for-send-sdp');
  //var textForSendICE = document.getElementById('text-for-send-ice');
  //var textToReceiveSDP = document.getElementById('text-for-receive-sdp');
  //var textToReceiveICE = document.getElementById('text-for-receive-ice');
  //var iceSeparator = '------ ICE Candidate -------';
  //var CR = String.fromCharCode(13);
  
  /*--
  function onSDP() {
    var text = textToReceiveSDP.value;
	var evt = JSON.parse(text);
	if (peerConnection) {
	  onAnswer(evt);
	}
	else {
	  onOffer(evt);
	}
	
	//textToReceiveSDP.value ="";
  }
  --*/
  
  //--- multi ICE candidate ---
  /*--
  function onICE() {
    var text = textToReceiveICE.value;
	var arr = text.split(iceSeparator);
	for (var i = 1, len = arr.length; i &lt; len; i++) {
      var evt = JSON.parse(arr[i]);
	  onCandidate(evt);
    }

	textToReceiveICE.value ="";
  }
  ---*/
  
  
  function onOffer(evt) {
    console.log("Received offer...")
	console.log(evt);
    setOffer(evt);
	sendAnswer(evt);
	//peerStarted = true; --
  }
  
  function onAnswer(evt) {
    console.log("Received Answer...")
	console.log(evt);
	setAnswer(evt);
  }
  
  function onCandidate(evt) {
	var id = evt.from;
    var conn = getConnection(id);
    if (! conn) {
	  console.error('peerConnection not exist!');
	  return;
	}
    
    // --- check if ice ready ---
    if (! conn.iceReady) {
      console.warn("PeerConn is not ICE ready, so ignore");
      return;
    }
	  
    var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
    console.log("Received Candidate...")
	console.log(candidate);
    conn.peerconnection.addIceCandidate(candidate);
  }

  function sendSDP(sdp) {
    var text = JSON.stringify(sdp);
	console.log("---sending sdp text ---");
	console.log(text);
	//textForSendSDP.value = text;
	
	// send via socket
	socket.json.send(sdp);
  }
  
  function sendCandidate(candidate) {
    var text = JSON.stringify(candidate);
	console.log("---sending candidate text ---");
	console.log(text);
	//textForSendICE.value = (textForSendICE.value + CR + iceSeparator + CR + text + CR);
	//textForSendICE.scrollTop = textForSendICE.scrollHeight;
	
	// send via socket
	socket.json.send(candidate);
  }
  
  // ---------------------- video handling -----------------------
  // start local video
  function startVideo() {
	navigator.webkitGetUserMedia({video: true, audio: true},
     function (stream) { // success
      localStream = stream;
      localVideo.src = window.webkitURL.createObjectURL(stream);
      localVideo.play();
	  localVideo.volume = 0;
     },
     function (error) { // error
      console.error('An error occurred:');
      console.error(error);
      return;
     }
	);
  }

  // stop local video
  function stopVideo() {
    localVideo.src = "";
    localStream.stop();
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection(id) {
    var pc_config = {"iceServers":[]};
    var peer = null;
    try {
      peer = new webkitRTCPeerConnection(pc_config);
    } catch (e) {
      console.log("Failed to create PeerConnection, exception: " + e.message);
    }
    var conn = new Connection();
    conn.id = id;
    conn.peerconnection = peer;
    peer.id = id;
    addConnection(id, conn);

    // send any ice candidates to the other peer
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);
        sendCandidate({type: "candidate", 
                          sendto: conn.id,
                          sdpMLineIndex: evt.candidate.sdpMLineIndex,
                          sdpMid: evt.candidate.sdpMid,
                          candidate: evt.candidate.candidate});
      } else {
        console.log("End of candidates. ------------------- phase=" + evt.eventPhase);
        conn.established = true;
      }
    };

    console.log('Adding local stream...');
    peer.addStream(localStream);

    peer.addEventListener("addstream", onRemoteStreamAdded, false);
    peer.addEventListener("removestream", onRemoteStreamRemoved, false)

    // when remote adds a stream, hand it on to the local video element
    function onRemoteStreamAdded(event) {
      console.log("Added remote stream");
      attachVideo(this.id, event.stream);
	  //remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
    }

    // when remote removes a stream, remove it from the local video element
    function onRemoteStreamRemoved(event) {
      console.log("Remove remote stream");
      detachVideo(this.id);
	  //remoteVideo.pause();
      //remoteVideo.src = "";
    }

    return conn;
  }

  function sendOffer(id) {
	var conn = getConnection(id);
    if (!conn) {
      conn = prepareNewConnection(id);
    }

	conn.peerconnection.createOffer(function (sessionDescription) { // in case of success
      conn.iceReady = true;
      conn.peerconnection.setLocalDescription(sessionDescription);
      sessionDescription.sendto = id;
	  sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Offer failed");
    }, mediaConstraints);
    conn.iceReady = true;
  }

  function setOffer(evt) {
	var id = evt.from;
    var conn = getConnection(id);
    if (! conn) {
      conn = prepareNewConnection(id);
      conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
    }
	else {
	  console.error('peerConnection alreay exist!');
	}
  }
  
  function sendAnswer(evt) {
    console.log('sending Answer. Creating remote session description...' );
	var id = evt.from;
    var conn = getConnection(id);
    if (! conn) {
	  console.error('peerConnection not exist!');
	  return
    }

    conn.peerconnection.createAnswer(function (sessionDescription) { 
      // in case of success
      conn.iceReady = true;
      conn.peerconnection.setLocalDescription(sessionDescription);
      sessionDescription.sendto = id;
	  sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Answer failed");
    }, mediaConstraints);
    conn.iceReady = true;
  }

  function setAnswer(evt) {
	var id = evt.from;
    var conn = getConnection(id);
    if (! conn) {
	  console.error('peerConnection not exist!');
	  return
    }
    conn.peerconnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  
  // -------- handling user UI event -----
  /*-----
  // start the connection upon user request
  function connect() {
    if (!peerStarted &amp;&amp; localStream &amp;&amp; socketReady) { // **
	//if (!peerStarted &amp;&amp; localStream) { // --
      sendOffer();
      peerStarted = true;
    } else {
      alert("Local stream not running yet - try again.");
    }
  }
  ----------*/
  
  // call others before connecting peer
  function call() {
    if (! isLocalStreamStarted()) {
      alert("Local stream not running yet. Please [Start Video] or [Start Screen].");
      return;
    }
    if (! socketReady) {
      alert("Socket is not connected to server. Please reload and try again.");
      return;
    }

    // call others, in same room
    console.log("call others in same room, befeore offer");
    socket.json.send({type: "call"});
  }
  
  // stop the connection upon user request
  function hangUp() {
    console.log("Hang up.");
    socket.json.send({type: "bye"});
    detachAllVideo();
    stopAllConnections();
  }

  /*--
  function stop() {
    peerConnection.close();
    peerConnection = null;
    //peerStarted = false; --
  }
  --*/

  
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><p></p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTCを使ってみよう！]]></series:name>
	</item>
		<item>
		<title>WebRTC初心者でも簡単にできる！Node.jsで仲介（シグナリング）を作ってみよう</title>
		<link>/mganeko/5349/</link>
		<pubDate>Fri, 21 Feb 2014 01:00:05 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=5349</guid>
		<description><![CDATA[連載： WebRTCを使ってみよう！ (3)こんにちは！ がねこまさしです。前回はWebRTCの通信を手動でつなぎましたが、今回は仲介役のサーバーを作ってみましょう。 ※今回の内容は、Node学園祭2013で発表した内容...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc-beginner/" class="series-158" title="WebRTCを使ってみよう！" data-wpel-link="internal">WebRTCを使ってみよう！</a> (3)</div><p>こんにちは！ がねこまさしです。<a href="https://html5experts.jp/mganeko/5181/" title="WebRTCに触ってみたいエンジニア必見！手動でWebRTC通信をつなげてみよう" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">前回はWebRTCの通信を手動でつなぎました</a>が、今回は仲介役のサーバーを作ってみましょう。</p>

<p>※今回の内容は、Node学園祭2013で発表した内容(の一部)とほぼ同じです。<a href="http://www.slideshare.net/mganeko/2013-web-rtcnode" title="WebRTCをはじめよう" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">その時の資料</a>もご参照ください。</p>

<p><strong>※こちらの記事は2014年に書かれました。<a href="https://html5experts.jp/mganeko/20013/" target="_blank" data-wpel-link="internal">2016年7月のアップデート記事</a>がありますので、そちらもご参照ください。</strong></p>

<h2>シグナリングサーバーを立てよう</h2>

<p><a href="https://html5experts.jp/mganeko/5181/" title="WebRTCに触ってみたいエンジニア必見！手動でWebRTC通信をつなげてみよう" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">前回</a>は手動でコピー＆ペーストしてシグナリングを実現しました。今回はそれを楽にしましょう。</p>

<h3>シグナリングサーバーはどうして必要なの？</h3>

<p>シグナリングの過程では、お互いのIPアドレスやポート番号を渡す必要があります。この段階ではお互いIPアドレスを知らないので直接やりとりできません。そこで、仲介役となるシグナリングサーバーが必要となります。このサーバーは、どちらブラウザもIPアドレスを知っていることが前提となります。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/signaling_server.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/signaling_server-300x196.png" alt="signaling_server" width="300" height="196" class="alignnone size-medium wp-image-5355" srcset="/wp-content/uploads/2014/02/signaling_server-300x196.png 300w, /wp-content/uploads/2014/02/signaling_server-207x135.png 207w, /wp-content/uploads/2014/02/signaling_server.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
つまり、Peer-to-Peer通信の開始前には、普通のサーバー/クライアント型の通信が行わることになります。</p>

<h3>Node.jsを準備しよう</h3>

<p>今回はシグナリング処理をWebSocketを使って実現してみます。ソケットの処理が実現できればどのような言語でも構わないのですが、メッセージング処理が得意なNode.jsを使うことにします。
Node.jsのインストーラーを<a href="http://nodejs.jp/nodejs.org_ja/docs/v0.10/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">こちらのサイト</a>から入手し、手順に従ってインストールしてください。Windows,Mac OS X,Linux用のバイナリが用意されています。今回のサンプルはv0.10.15で動作確認していますが、v0.10.x系ならばそのまま動くはずです。</p>

<p>Node.jsのインストールが完了したら、こんどはWebSocket用のモジュールをインストールします。コマンドプロンプト/ターミナルから、 次のコマンドを実行してください。 ※必要に応じて、sudoなどをご利用ください。
</p><pre class="crayon-plain-tag">npm install socket.io</pre><p>
<a href="http://socket.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">socket.io</a>は、異なる種類のブラウザ間の通信を簡単に行えるようにしてくれるモジュールです。異なる複数の通信方式をサポートしています。</p>

<ul>
    <li>&#8216;websocket&#8217; , &#8216;flashsocket&#8217; , &#8216;htmlfile&#8217; , &#8216;xhr-polling&#8217; , &#8216;jsonp-polling&#8217;</li>
</ul>

<h3>シグナリングサーバーを動かそう</h3>

<p>次のコードを好きなファイル名で保存してください。（例えば signaling.js)</p>

<p></p><pre class="crayon-plain-tag">var port = 9001;
var io = require('socket.io').listen(port);
console.log((new Date()) + " Server is listening on port " + port);

io.sockets.on('connection', function(socket) {
  socket.on('message', function(message) {
    socket.broadcast.emit('message', message);
  });

  socket.on('disconnect', function() {
    socket.broadcast.emit('user disconnected');
  });
});</pre><p> 
起動するにはコマンドプロンプト/ターミナルから、 次のコマンドを実行してください。
</p><pre class="crayon-plain-tag">node signaling.js</pre><p>
シグナリングサーバーの動作は単純で、右からきたメッセージをそのまま左に流すだけです。</p>

<h2>シグナリング処理を変更しよう</h2>

<p>それでは前回のHTMLを、少しずつ変更して行きましょう。まず、socket.ioのクライアント用JavaScriptを読み込みます。localhostの部分は、実際のシグナリングサーバーに変更してください。
</p><pre class="crayon-plain-tag">&lt;script src="http://localhost:9001/socket.io/socket.io.js"&gt;&lt;/script&gt;</pre><p> 
<br />
次に、socket.ioの接続、通信処理をJavaScriptに追加します。
</p><pre class="crayon-plain-tag">// ---- socket ------
  // create socket
  var socketReady = false;
  var port = 9001;
  var socket = io.connect('http://localhost:/' + port + '/');
  // socket: channel connected
  socket.on('connect', onOpened)
        .on('message', onMessage);

  function onOpened(evt) {
    console.log('socket opened.');
    socketReady = true;
  }

  // socket: accept connection request
  function onMessage(evt) {
    if (evt.type === 'offer') {
      console.log("Received offer, set offer, sending answer....")
      onOffer(evt);	  
    } else if (evt.type === 'answer' &amp;&amp; peerStarted) {
      console.log('Received answer, settinng answer SDP');
      onAnswer(evt);
    } else if (evt.type === 'candidate' &amp;&amp; peerStarted) {
      console.log('Received ICE candidate...');
      onCandidate(evt);
    } else if (evt.type === 'user dissconnected' &amp;&amp; peerStarted) {
      console.log("disconnected");
      stop();
    }
  }</pre><p> 
重要なのはonMessage()の処理で、Offer SDP,Answer SDP,ICE Candidateのそれぞれに対応して、前回用意したOnOffer(), onAnswer(), onCandidate()を呼び出しています。<br /></p>

<p>今度は実際にSDP/ICE Candidateを送る部分を変更します。前回はテキストエリアに表示するだけでしたが、今回はそれをsocket.io経由で送信します。</p>

<p></p><pre class="crayon-plain-tag">function sendSDP(sdp) {
    var text = JSON.stringify(sdp);
	console.log("---sending sdp text ---");
	console.log(text);
	textForSendSDP.value = text;
	
	// send via socket
	socket.json.send(sdp); // &lt;--- ここを追加
  }
  
  function sendCandidate(candidate) {
    var text = JSON.stringify(candidate);
	console.log("---sending candidate text ---");
	console.log(text);
	textForSendICE.value = (textForSendICE.value + CR + iceSeparator + CR + text + CR);
	textForSendICE.scrollTop = textForSendICE.scrollHeight;
	
	// send via socket
	socket.json.send(candidate); // &lt;--- ここを追加
  }</pre><p></p>

<p>最後は、ちょっとしたフラグの処理の追加です。</p>

<p></p><pre class="crayon-plain-tag">function onOffer(evt) {
    console.log("Received offer...")
    console.log(evt);
    setOffer(evt);
    sendAnswer(evt);
    peerStarted = true;  // &lt;--- ここを追加
  }

  // start the connection upon user request
  function connect() {
    if (!peerStarted &amp;&amp; localStream &amp;&amp; socketReady) { // &lt;--- ここを変更
      sendOffer();
      peerStarted = true;
    } else {
      alert("Local stream not running yet - try again.");
    }
  }

  function stop() {
    peerConnection.close();
    peerConnection = null;
    peerStarted = false;  // &lt;--- ここを追加
  }</pre><p></p>

<h2>実際に動かしてみよう</h2>

<p>シグナリングサーバーが動いてることを確認したら、Chromeのウィンドウを2つ開いて修正したHTMLを読み込んでください。</p>

<p>(1) 両方のウィンドウで[Start video]ボタンをクリックします。カメラのアクセスを許可すると、それぞれリアルタイムの映像が表示されます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/signaling_1.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/signaling_1-300x162.png" alt="signaling_1" width="300" height="162" class="alignnone size-medium wp-image-5366" srcset="/wp-content/uploads/2014/02/signaling_1-300x162.png 300w, /wp-content/uploads/2014/02/signaling_1-1024x555.png 1024w, /wp-content/uploads/2014/02/signaling_1-207x112.png 207w, /wp-content/uploads/2014/02/signaling_1.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
<br />
(2) どちらかのウィンドウで[Connect]ボタンを押します。SDP, ICE Candidateが自動で交換され、ビデオ通信が始まります。<br />
<a href="https://html5experts.jp/wp-content/uploads/2014/02/signaling_2.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/signaling_2-300x154.png" alt="signaling_2" width="300" height="154" class="alignnone size-medium wp-image-5367" srcset="/wp-content/uploads/2014/02/signaling_2-300x154.png 300w, /wp-content/uploads/2014/02/signaling_2-1024x528.png 1024w, /wp-content/uploads/2014/02/signaling_2-207x106.png 207w, /wp-content/uploads/2014/02/signaling_2.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
前回の14ステップに比べて、ぐっと減って2ステップになりました。これなら使えそうですね。</p>

<h2>シグナリングの流れを追ってみる</h2>

<p>シグナリングの流れを追跡してみましょう。前回はコード上を追っかけたので、今回は流れを図で見てみます。<br /></p>

<h3>SDPの交換</h3>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/02/signaling_sdp.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/signaling_sdp-300x225.png" alt="signaling_sdp" width="300" height="225" class="alignnone size-medium wp-image-5369" srcset="/wp-content/uploads/2014/02/signaling_sdp-300x225.png 300w, /wp-content/uploads/2014/02/signaling_sdp-207x155.png 207w, /wp-content/uploads/2014/02/signaling_sdp.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
SDPのOffer, Answerが、シグナリングサーバー経由で交換されます。</p>

<h3>ICE Candidateの交換</h3>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/02/signaling_ice.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/02/signaling_ice-300x219.png" alt="signaling_ice" width="300" height="219" class="alignnone size-medium wp-image-5370" srcset="/wp-content/uploads/2014/02/signaling_ice-300x219.png 300w, /wp-content/uploads/2014/02/signaling_ice-207x151.png 207w, /wp-content/uploads/2014/02/signaling_ice.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
複数のICE Candidateが飛び交い、すべての交換が終わるとPeer-to-Peer通信が始まります。</p>

<h2>次回は</h2>

<p>今回はシグナリングサーバーを動かして、Peer-to-Peer通信確立までを自動化しました（それが普通ですけど）。実は今回の仕組みでは、一つのシグナリングサーバーで同時に2人までしか通信できません。まったく実用的ではありません。次回は複数人での通信にチャレンジする予定です。</p>

<h2>今回のソース</h2>

<p>最後に、今回使ったHTMLを掲載しておきます。</p>

<p></p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;WebRTC 1 to 1 signaling&lt;/title&gt;  
&lt;/head&gt;
&lt;body&gt;
  &lt;button type="button" onclick="startVideo();"&gt;Start video&lt;/button&gt;
  &lt;button type="button" onclick="stopVideo();"&gt;Stop video&lt;/button&gt;
  &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
  &lt;button type="button" onclick="connect();"&gt;Connect&lt;/button&gt;
  &lt;button type="button" onclick="hangUp();"&gt;Hang Up&lt;/button&gt;
  &lt;br /&gt;
  &lt;div&gt;
   &lt;video id="local-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt;
   &lt;video id="remote-video" autoplay style="width: 240px; height: 180px; border: 1px solid black;"&gt;&lt;/video&gt;
  &lt;/div&gt;
  
  &lt;p&gt;
   SDP to send:&lt;br /&gt;
   &lt;textarea id="text-for-send-sdp" rows="5" cols="100" disabled="1"&gt;SDP to send&lt;/textarea&gt;
  &lt;/p&gt;
  &lt;p&gt;
   SDP to receive:&lt;br /&gt;
   &lt;textarea id="text-for-receive-sdp" rows="5" cols="100"&gt;&lt;/textarea&gt;&lt;br /&gt;
   &lt;button type="button" onclick="onSDP();"&gt;Receive SDP&lt;/button&gt;
  &lt;/p&gt;
  
  &lt;p&gt;
   ICE Candidate to send:&lt;br /&gt;
   &lt;textarea id="text-for-send-ice" rows="5" cols="100" disabled="1"&gt;ICE Candidate to send&lt;/textarea&gt;
  &lt;/p&gt;
  &lt;p&gt;  
   ICE Candidates to receive:&lt;br /&gt;
   &lt;textarea id="text-for-receive-ice" rows="5" cols="100"&gt;&lt;/textarea&gt;&lt;br /&gt;
   &lt;button type="button" onclick="onICE();"&gt;Receive ICE Candidates&lt;/button&gt;
  &lt;/p&gt;
  
  &lt;!---- socket ------&gt;
  &lt;script src="http://localhost:9001/socket.io/socket.io.js"&gt;&lt;/script&gt;
  
  &lt;script&gt;
  var localVideo = document.getElementById('local-video');
  var remoteVideo = document.getElementById('remote-video');
  var localStream = null;
  var peerConnection = null;
  var peerStarted = false;
  var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':false, 'OfferToReceiveVideo':true }};

  
  // ---- socket ------
  // create socket
  var socketReady = false;
  var port = 9001;
  var socket = io.connect('http://localhost:/' + port + '/');
  // socket: channel connected
  socket.on('connect', onOpened)
        .on('message', onMessage);

  function onOpened(evt) {
    console.log('socket opened.');
    socketReady = true;
  }

  // socket: accept connection request
  function onMessage(evt) {
    if (evt.type === 'offer') {
      console.log("Received offer, set offer, sending answer....")
      onOffer(evt);	  
    } else if (evt.type === 'answer' &amp;&amp; peerStarted) {
      console.log('Received answer, settinng answer SDP');
	  onAnswer(evt);
    } else if (evt.type === 'candidate' &amp;&amp; peerStarted) {
      console.log('Received ICE candidate...');
	  onCandidate(evt);
    } else if (evt.type === 'user dissconnected' &amp;&amp; peerStarted) {
      console.log("disconnected");
      stop();
    }
  }

  
  
  // ----------------- handshake --------------
  var textForSendSDP = document.getElementById('text-for-send-sdp');
  var textForSendICE = document.getElementById('text-for-send-ice');
  var textToReceiveSDP = document.getElementById('text-for-receive-sdp');
  var textToReceiveICE = document.getElementById('text-for-receive-ice');
  var iceSeparator = '------ ICE Candidate -------';
  var CR = String.fromCharCode(13);
  
  function onSDP() {
    var text = textToReceiveSDP.value;
	var evt = JSON.parse(text);
	if (peerConnection) {
	  onAnswer(evt);
	}
	else {
	  onOffer(evt);
	}
	
	textToReceiveSDP.value ="";
  }  
  
  //--- multi ICE candidate ---
  function onICE() {
    var text = textToReceiveICE.value;
	var arr = text.split(iceSeparator);
	for (var i = 1, len = arr.length; i &lt; len; i++) {
      var evt = JSON.parse(arr[i]);
	  onCandidate(evt);
    }

	textToReceiveICE.value ="";
  }
  
  
  function onOffer(evt) {
    console.log("Received offer...")
	console.log(evt);
    setOffer(evt);
	sendAnswer(evt);
	peerStarted = true;  // ++
  }
  
  function onAnswer(evt) {
    console.log("Received Answer...")
	console.log(evt);
	setAnswer(evt);
  }
  
  function onCandidate(evt) {
    var candidate = new RTCIceCandidate({sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate});
    console.log("Received Candidate...")
	console.log(candidate);
    peerConnection.addIceCandidate(candidate);
  }

  function sendSDP(sdp) {
    var text = JSON.stringify(sdp);
	console.log("---sending sdp text ---");
	console.log(text);
	textForSendSDP.value = text;
	
	// send via socket
	socket.json.send(sdp);
  }
  
  function sendCandidate(candidate) {
    var text = JSON.stringify(candidate);
	console.log("---sending candidate text ---");
	console.log(text);
	textForSendICE.value = (textForSendICE.value + CR + iceSeparator + CR + text + CR);
	textForSendICE.scrollTop = textForSendICE.scrollHeight;
	
	// send via socket
	socket.json.send(candidate);
  }
  
  // ---------------------- video handling -----------------------
  // start local video
  function startVideo() {
	navigator.webkitGetUserMedia({video: true, audio: false},
    function (stream) { // success
      localStream = stream;
      localVideo.src = window.webkitURL.createObjectURL(stream);
      localVideo.play();
	  localVideo.volume = 0;
    },
    function (error) { // error
      console.error('An error occurred: [CODE ' + error.code + ']');
      return;
    }
	);
  }

  // stop local video
  function stopVideo() {
    localVideo.src = "";
    localStream.stop();
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection() {
    var pc_config = {"iceServers":[]};
    var peer = null;
    try {
      peer = new webkitRTCPeerConnection(pc_config);
    } catch (e) {
      console.log("Failed to create peerConnection, exception: " + e.message);
    }

    // send any ice candidates to the other peer
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);
        sendCandidate({type: "candidate", 
                          sdpMLineIndex: evt.candidate.sdpMLineIndex,
                          sdpMid: evt.candidate.sdpMid,
                          candidate: evt.candidate.candidate}
		);
      } else {
        console.log("End of candidates. ------------------- phase=" + evt.eventPhase);
      }
    };

    console.log('Adding local stream...');
    peer.addStream(localStream);

    peer.addEventListener("addstream", onRemoteStreamAdded, false);
    peer.addEventListener("removestream", onRemoteStreamRemoved, false)

    // when remote adds a stream, hand it on to the local video element
    function onRemoteStreamAdded(event) {
      console.log("Added remote stream");
      remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
    }

    // when remote removes a stream, remove it from the local video element
    function onRemoteStreamRemoved(event) {
      console.log("Remove remote stream");
      remoteVideo.src = "";
    }

    return peer;
  }

  function sendOffer() {
    peerConnection = prepareNewConnection();
    peerConnection.createOffer(function (sessionDescription) { // in case of success
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Offer failed");
    }, mediaConstraints);
  }

  function setOffer(evt) {
    if (peerConnection) {
	  console.error('peerConnection alreay exist!');
	}
    peerConnection = prepareNewConnection();
    peerConnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  
  function sendAnswer(evt) {
    console.log('sending Answer. Creating remote session description...' );
	if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}
	
    peerConnection.createAnswer(function (sessionDescription) { // in case of success
      peerConnection.setLocalDescription(sessionDescription);
      console.log("Sending: SDP");
      console.log(sessionDescription);
      sendSDP(sessionDescription);
    }, function () { // in case of error
      console.log("Create Answer failed");
    }, mediaConstraints);
  }

  function setAnswer(evt) {
    if (! peerConnection) {
	  console.error('peerConnection NOT exist!');
	  return;
	}
	peerConnection.setRemoteDescription(new RTCSessionDescription(evt));
  }
  
  // -------- handling user UI event -----
  // start the connection upon user request
  function connect() {
    if (!peerStarted &amp;&amp; localStream &amp;&amp; socketReady) { // **
	//if (!peerStarted &amp;&amp; localStream) { // --
      sendOffer();
      peerStarted = true;
    } else {
      alert("Local stream not running yet - try again.");
    }
  }

  // stop the connection upon user request
  function hangUp() {
    console.log("Hang up.");
    stop();
  }

  function stop() {
    peerConnection.close();
    peerConnection = null;
    peerStarted = false;
  }

  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><p></p>

<h3>＜バックナンバー＞</h3>

<ul>
<li><a href="https://html5experts.jp/mganeko/5098/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">HTML5でWebRTCを使ってみよう！「カメラを使ってみよう」編</a></li>
<li><a href="https://html5experts.jp/mganeko/5181/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">WebRTCに触ってみたいエンジニア必見！手動でWebRTC通信をつなげてみよう編</a></li>
</ul>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTCを使ってみよう！]]></series:name>
	</item>
		<item>
		<title>NUCで手のひらサイズの格安WebSocketサーバーを立ててみた(アプリ起動編)</title>
		<link>/toshirot/4786/</link>
		<pubDate>Thu, 30 Jan 2014 23:50:59 +0000</pubDate>
		<dc:creator><![CDATA[高橋 登史朗]]></dc:creator>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[NUC]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[Webアプリ]]></category>

		<guid isPermaLink="false">/?p=4786</guid>
		<description><![CDATA[連載： NUCでWebSocketサーバを立ててみた (3)前回まででNUCのハードを組み立てて、OSやJavaScript実行環境Node.jsなど、基本的なソフトのインストールを準備しました。 今回はいよいよWebS...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/nuc-try/" class="series-157" title="NUCでWebSocketサーバを立ててみた" data-wpel-link="internal">NUCでWebSocketサーバを立ててみた</a> (3)</div><p><a href="https://html5experts.jp/toshirot/4595/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">前回</a>まででNUCのハードを組み立てて、OSやJavaScript実行環境Node.jsなど、基本的な<a href="https://html5experts.jp/toshirot/4718/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ソフトのインストールを準備</a>しました。</p>

<p>今回はいよいよWebSocketサーバーを立ち上げ、アプリを動かします。</p>

<p>WebSocketサーバーといっても、今回はWebSocketサーバーそのものを作るわけではありません。サーバーにはNode.jsのnpmパッケージからwsというWebSocket用モジュールを利用します。</p>

<p>このモジュールはあの有名なSocket.IOで、WebSocketのコアの部分にも使われている高速なWebSocketサーバーを持っています。アイディア次第で、たとえば、チャットサーバーにでも、ゲームデータサーバーにでも、あるいは、ニュース配信サーバーにでも何にでも使えます。</p>

<p>「WebSocket」自体は双方向通信のプロトコルとAPIというだけですので、その使い方次第で用途は無数に考えられるわけです。</p>

<h2>今回作るもの</h2>

<p>今回は、NUCを使って、とりあえずWebSocketサーバーを立ち上げてみようという短期連載ですので、その雰囲気がわかる程度の簡単なものでやってみます。</p>

<p>恐縮ですが拙著「Node.jsプログラミングガイド」のサンプルの中から、WebSocketを使ったリアルタイムチャートを作ってみます。ここでのWebSocketサーバーは、チャートへデータを連続的にプッシュし続ける機能を担います。</p>

<p>まず最初に前回もチラ見した、仕上がりをもう一度見てみましょう。</p>

<p>下の赤い「Play」ボタンをクリックすると動きます。</p>

<p>(invalid jsdo.it code)</p>

<p>上記チャートは<a href="http://ccchart.com/test/ws2.htm" title="ccchart" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ccchart</a>ですが、チャートが変化し続けるためのデータがサーバー側からリアルタイムにプッシュされ続けています。Ajaxなどのように、ブラウザ側からユーザーがリクエストする必要はありません。</p>

<p>このケースでは、すべての接続しているブラウザのチャートへデータが自動的にプッシュされ続けます。</p>

<p>この仕組みを使えば、たとえば、社内の毎時変化するデータなどを関係者全員がリアルタイムに閲覧するといったシステムを作ることも簡単です。</p>

<p>ちなみに参考までですが、ccchartのメリットはCanvasベースなので高速でSVGよりもプラットフォーム依存が少ないことと、インターネットにつながっている必要もないのでイントラネットでも使えることなどでしょうか。</p>

<p>(※今回は解説できませんが、もし、このサンプルが動かない環境なら、ポートを80や443などにする必要があるかもしれません。ただし、Linuxでは80などのwell-known ポートは、今回のようなユーザー権限ではなくroot権限で起動する必要があり、セキュアにすることも考慮すると別途少し手間がかかります)</p>

<p>では、これをNUC内に置いてみます。</p>

<h2>ソースを取得する</h2>

<p>まず、今回使うソースを手に入れます。「Node.jsプログラミングガイド」の<a href="http://jsgt.org/np/code/doc/01.htm" title="Node.jsプログラミングガイド　サンプル" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">サンプルページ</a>
1-Basic Knowledge 基礎知識 から
02-wsserver2にある2つのファイル
<code>
chart.htm
wsserver.js
</code>
をコピーします。クライアント側のHTMLとJavaScript、つまりchart.htmのコードは、下記にもありますが jsdo.itのサンプルからもご確認いただけます。</p>

<h2>アプリ用のディレクトリを用意する</h2>

<p>ファイルを作る場所はお好みですが、今回は、ユーザー名を仮にhogeとして、ホームディレクトリ(/home/hoge) 配下へ前回作っておいた公開用ディレクトリ /home/hoge/public_html を利用して、Apacheで公開してみます。</p>

<p>作成するディレクトリツリーは、次のようになります。</p>

<p><code>
/home/hoge
   ├──public_html/
   │    └─mychart<br />
   │         └─chart.htm 
   └──wsserver.js
</code></p>

<p>まず、ホームディレクトリのpublic_html下にchart.htmを置くためのディレクトリmychartを作ります。
</p><pre class="crayon-plain-tag">cd /home/hoge/public_html
mkdir mychart
cd mychart</pre><p>
mychartディレクトリができたら、そこへ chart.htm をFTPでアップロードしておきましょう。wsserver.jsは、/home/hoge直下へ置きます。</p>

<p>次に、そのひとつ上の非公開ディレクトリ /home/hoge/ 内に node_modules というディレクトリを作ります。
</p><pre class="crayon-plain-tag">cd /home/hoge
mkdir node_modules</pre><p>
これは、Nodeのモジュールが格納される場所で、ディレクトリ/home/hoge配下でインストールするローカルなモジュールは、(インストールコマンドnpmの引数-gを指定しなければ) ここに入ります。</p>

<p>また、wsserver.jsはサーバー側ですので一般的には、公開ディレクトリpublic_htmlではなく非公開な場所(今回は/home/hoge直下)へアップロードします。</p>

<h2>WebSocket用モジュールをインストール</h2>

<p>では次にNodeのWebSocket用モジュールwsを入れてみます。これには高速なWebSocketサーバーとクライアントが同梱されています。</p>

<p>では入れてみましょう。
</p><pre class="crayon-plain-tag">npm i ws</pre><p>
これだけです。(npm install wsと同じ意味です)
では、node_modules下に本当に入ったのかlsコマンドで見てみましょう。
</p><pre class="crayon-plain-tag">ls -l /home/tato/node_modules</pre><p>
すると、下記のように表示されるのでwsが入っていることがわかります。
<code>
total 4
drwxrwxr-x 11 hoge hoge 4096  1月 15 18:36 ws
</code>
作成されたディレクトリツリーは、次のようになっているはずです。</p>

<p><code>
/home/hoge
   ├──public_html/
   │    └─mychart<br />
   │         └─chart.htm 
   ├──wsserver.js
   └──node_modules/ ←追加された
        └─ ws/
</code></p>

<p>これで、設置完了です。簡単でしょう？
でも、ここでhttp://192.168.1.180/~hoge/chart.htmをブラウザで開いても動きません。</p>

<p>サーバーが起動していないからです。</p>

<p>では、動かしましょう。</p>

<h2>WebSocketサーバーを起動する</h2>

<p>以下のおまじないを打ち込みます。ホームディレクトリ/home/hoge/下のwsserver.jsをNodeで動かすという意味です。
</p><pre class="crayon-plain-tag">node ~/wsserver.js</pre><p>
node /home/hoge/wsserver.jsでも同じ意味になります。</p>

<p>もし、ここでエラーが出なければ、同一ネットワーク内の他のパソコンのブラウザで http://192.168.1.180/~hoge/chart.htm を開いてみてください。</p>

<p>動いてますね。さらに、別のパソコンで開いても見ることができます。</p>

<h2>永続化</h2>

<p>「node ~/wsserver.js」でサーバーを起動しましたが、このままでは、sshの接続を切るとサーバーが止まってしまいます。</p>

<p>そこで、永続化やNUCの再起動時も自動で立ち上がるための仕掛けが必要です。</p>

<p>永続化には、foreverというモジュールがよく使われます。再起動時の自動起動は、/etc/init.dへの登録などの方法がありますが、残念ながら紙面の都合で書ききれませんので、不明な方はこれらのキーワードで調べてみてください。</p>

<p>この連載のアクセスが多ければ続きを書けるかもしれません(笑)。
でも、それを待つよりも、もしわからなければ、自分で調べると身につきますよ。</p>

<h2>サーバー側のコード</h2>

<p>参考までにサーバー側ソースは次のようなものです。</p>

<p>手短に言うと、指定したポートへクライアントから接続があると、0.3秒ごとに生成しているデータをすべてのクライアントへプッシュしています。詳しくはソースとコメントをご覧ください。</p>

<p></p><pre class="crayon-plain-tag">//WebSocket Serverモジュールを読み込む
var WsServer = require('ws').Server;
//port: 3001でリッスンするサーバーを作成する
var ws = new WsServer({ port: 3001 });
//タイマーID
var tid;

broadCast();//データ配信開始

function broadCast(){
  //0.3秒ごとにデータを送信する
  tid = setInterval (function(){
    //データを作る
    var dataAry = mkData();
    //すべてのクライアントへ送信する
    ws.clients.forEach(function(client) { 
      client.send(JSON.stringify(dataAry));
    });
  }, 300);
}

// 送信用ランダムデータを作成する(実際には必要なデータを与える)
function mkData(){
  //送信データ形式の雛形
  var data = [
      ["時間"],
      ["s2"],
      ["s3"]
    ];

  //時間の文字列を作成する
  var now = new Date();
  var H = now.getHours();
  var M = now.getMinutes();
  var S = now.getSeconds();
  H = (H &lt; 10)?&#039;0&#039;+H:H;
  M = (M &lt; 10)?&#039;0&#039;+M:M;
  S = (S &lt; 10)?&#039;0&#039;+S:S;
  
  //送信データを作成する
  data[0]=H +&#039;:&#039; + M +&#039;:&#039; + S;
  data[1]=Math.floor(Math.random(10) * 96 );
  data[2]=32 + Math.floor(Math.random(10) * 18);
  return data;
}</pre><p></p>

<h2>クライアント側のコード</h2>

<p>ccchart自体の使い方は今回は紙面の都合で触れませんが、ポイントは、ccchartのwsメソッドの引数でサーバー側のIPアドレスとポートを指定している部分です。そこから流れてきたデータを自動的にconfigで指定した形式にチャート化します。</p>

<p></p><pre class="crayon-plain-tag">&lt;script src="http://ccchart.com/js/ccchart.js" charset="utf-8"&gt;&lt;/script&gt;
&lt;canvas id="hoge"&gt;&lt;/canvas&gt;
&lt;script&gt;

var chartdata = {
 //チャートの設定
 "config": {
    "title": "WebSocket TEST",
    "subTitle": "列データをリアルタイム受信描画 受信パターンはoneColAtATime",
    "type": "bezi2",//チャートタイプベジェ曲線 他にline, barなど
    "width" : 465,//幅
    "lineWidth": 2,//線の太さ
    "minY": 0,//最小Y値
    "xScaleSkip": 5,//垂直目盛のスキップ
    "colorSet": //カラーセット
          ["#DDA0DD","#3CB000"]
  },
  //データの雛形
  "data": [
    ["時間"],
    ["s2"],
    ["s3"]
  ]
};

ccchart
  .init('hoge', chartdata) //要素hogeへチャートデータchartdataを出力する
  .ws('ws://192.168.1.180:3001') //このURLをサーバー側のIPとポートに合わせる
  .on('message', ccchart.wscase.oneColAtATime);//このパターンで受信する
        // oneColAtATimeは、WebSocketの受信パターン関数
        // 一度に1列ずつ [["2013"],[435],[600]] といった配列で届く場合用
        // 参照: http://ccchart.org/test/someCols/test-2.htm
&lt;/script&gt;</pre><p></p>

<h2>最後に</h2>

<p>以上、駆け足で手のひらサイズのWebSocketサーバーを組み立ててみました。</p>

<p>ほんの10年ほど前までは、一般的には、手のひらサイズのサーバーなどほとんど考えることもありませんでしたが、今、それはすでに目の前にあります。</p>

<p>一方に社外クラウドサーバーを利用する大きな流れがありますが、同時に、目の前には、わずか数万円でモニタの裏にも貼れるような小さな内製サーバーを作れるという選択肢も生まれたわけです。</p>

<p>エクセルを使って仕事の効率を上げるように、ちょっとした業務には、どこにでも持っていける小さな内製サーバーを使って効率を上げるということも可能な時代になりつつあります。</p>

<p>多くの業務と製品がネットワーク経由のサービスと無縁ではなくなりつつある今、たとえば、機動性の高いサービスをより現場に近い場所で、NUCを使って次々と立ち上げるといった未来が、文字通り、今、私たちの手の上にあります。</p>

<p>NUC 自体の用途は限定されていません。さて、どんな未来を作りましょうか？</p>

<p>＜関連レポート＞<br>
<a href="https://html5experts.jp/toshirot/4595/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><strong>NUCで手のひらサイズの格安WebSocketサーバーを立ててみた(ハード組立編)を読む⇒</strong></a><br>
<strong><a href="https://html5experts.jp/toshirot/4718/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">NUCで手のひらサイズの格安WebSocketサーバーを立ててみた（OSインストール編）を読む⇒</a></strong><br></p>
]]></content:encoded>
		
		<series:name><![CDATA[NUCでWebSocketサーバを立ててみた]]></series:name>
	</item>
	</channel>
</rss>
