とあるWeb屋の備忘録

とあるWeb屋の備忘録。たまに雑記。

Cloud Speech API + node.jsで音声認識をさせてみる1

前回、前々回とNode.js+Express+Azureでチャットアプリを作成しました。
これからルーム機能やログイン機能を付けていったら面白そうだなと思っているのですが、一番面白そうだなと思ったのが音声入力だったので、そちらから始めてみることにしました。
前回までの記事はこちら
Node.js+Express+Azureでチャットアプリをデプロイしてみる1
Node.js+Express+Azureでチャットアプリをデプロイしてみる2

調べたところ、音声認識Google Cloud Platform(GCP)のCloud Speech APIを使って実現できるみたいなのでそちらを使ってみたいと思います。
GCPGoogleクラウド上で提供しているサービスの総称で、そこにはとても多くのAPIライブラリがあったりVMやアプリのプラットフォームがあったりします。
GCPについて
https://cloud.google.com/

実際に音声認識をさせるまでかなりハマって大変だったので、気を付けるポイントなども書いていきます。

Node.jsはインストールされている前提で、以下環境です。
環境
Window10
node.js(8.9.4)

さらに音声認識をするために必要な以下のソフトをインストールします。
sox(音声ファイルの形式を変換するソフト)
Audacity(音声ファイルが正しく記録されているか確認用なのでなくてもOK)
Macを使っていればsoxbrew install soxでOK

前準備

Cloud Speech APIを使うためにGCPに登録します。
登録はこちらを参考にしました。ありがとうございます!
https://book.mynavi.jp/manatee/detail/id=65673

プロジェクト名を設定してプロジェクトを作成したら、ナビゲーションメニューを開いて「APIとサービス」からCloud Speech APIを有効にします。
次に認証情報の「認証情報を作成」のプルダウンからサービスアカウントキーを作成します。
作成後にjsonファイル(秘密鍵)が自分のPCに保存されます。※失くさない&&公開しないようにセキュリティに気を付けて保管してください。

作成後、プロジェクトIDとjsonファイルを環境変数に登録することが必要です。

export GCLOUD_PROJECT=プロジェクトID
export GOOGLE_APPLICATION_CREDENTIALS="pass/to/createdJsonFile"

node-record-lpcm16のインストール

今回用に適当にディレクトリを作成したら、そこでまずpackage.jsonを作成します。

npm init

次にnode.jsで音声ファイルを作成するためにnode-record-lpcm16をインストールします。

npm install node-record-lpcm16 --save

インストールが完了したら、落ちてきたパッケージ内にあるindex.jsの26行目のswitch文を以下の記述に変更します。

switch (options.recordProgram) {
  // On some Windows machines, sox is installed using the "sox" binary
  // instead of "rec"
  case 'sox': // case 'sox'節に以下のコードを追加
  cmd = options.recordProgram;
  cmdArgs = [
    '-q',                                 // show no progress
    '-t', 'waveaudio',                    // input-type
    '-d',                                 // use default recording device
    '-r', options.sampleRate.toString(),  // sample rate
    '-c', '1',                            // channels
    '-e', 'signed-integer',               // sample encoding
    '-b', '16',                           // precision (bits)
    '-t', 'raw',                          // output-type
    '-'                                   // pipe
  ];
  break;
  case 'rec':
  default:
    cmd = options.recordProgram
    cmdArgs = [
      '-q',                     // show no progress
      '-r', options.sampleRate, // sample rate
      '-c', '1',                // channels
      '-e', 'signed-integer',   // sample encoding
      '-b', '16',               // precision (bits)
      '-t', 'wav',              // audio type
      '-',                      // pipe
          // end on silence
      'silence', '1', '0.1', options.thresholdStart || options.threshold + '%',
      '1', options.silence, options.thresholdEnd || options.threshold + '%'
    ]
    break
  // On some systems (RasPi), arecord is the prefered recording binary
  case 'arecord':
    cmd = 'arecord'
    cmdArgs = [
      '-q',                     // show no progress
      '-r', options.sampleRate, // sample rate
      '-c', '1',                // channels
      '-t', 'wav',              // audio type
      '-f', 'S16_LE',           // Sample format
      '-'                       // pipe
    ]
    if (options.device) {
      cmdArgs.unshift('-D', options.device)
    }
    break
}

index.jsの作成

ここまで終わったら実際に音声を取り込んで音声ファイルを吐き出すindex.jsを作成します。

const record = require('node-record-lpcm16');
const fs = require('fs');

const filename = 'test.raw';
const file = fs.createWriteStream(filename);

const encoding = 'LINEAR16';
const sampleRate = 16000;

record.start({
  sampleRateHertz: sampleRate,
  encoding: encoding,
  recordProgram: 'sox'
}).pipe(file);

setTimeout(function () {
  record.stop();
}, 7000);

index.jsを実行します。

node index.js

実際にtest.rawファイルが吐き出されたら音声ファイルの取り込みは成功です。
rest.rawの確認方法として、Audacityを使います。

Audacityを起動して、ファイル>取り込み>Rawデータの取り込みを選択します。
test.rawを選択すると、サンプリング周波数の入力画面が表示されるので16000を入力します。
取り込みが完了したら再生を押して音声が録音されているか確認できます。

問題なく音声が取り込まれていることを確認したら、次にそれをテキストに変えてConsole画面に表示させる処理を書きていきます。

長くなったので残りは後日書きます!
読んでいただいてありがとうございました!

Node.js+Express+Azureでチャットアプリをデプロイしてみる2

それでは前回の続きで、サーバ側とクライアント側の通信時の処理を書いていきます。

サーバ側

まずポートを指定します。
ポートはアプリを実行する環境によって異なるので以下の形にする。
process.envは環境変数。process.env.PORTはユーザー環境のポート番号のこと。
また、環境変数を自分で設定する場合は、nodeコマンドを実行する前にターミナルから設定できる。

const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

//ここに通信時の処理を書く

server.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});

通信時の処理を書いていく。 サーバ ー クライアント間のやり取りは、クライアントがsocket接続を要求することで開始される。
サーバがクライアントの接続を確立すると、サーバ側で'connection'イベントが発生する。(クライアント側では'connect'イベント)
このイベントは引数にsocketが渡される。
クライアントからイベントが発行されたときに受け取る処理をsocket.onを定義しておく。
ここでは、'chat message' というイベントが発行されてmsgを受け取った時の処理を定義している。
そしてクライアントからイベントを受け取ったあと、io.emitでサーバ側でイベントを発行する。

io.on('connection', (socket) => {
  console.log('a user connected');

  //socket処理 個別の要件を受け付けるイベントハンドラ 
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
    //emit → 自分を含む全員にメッセージを送信する
    io.emit('chat message', msg);
  });

});

クライアント側

まずはsocket.ioに接続。
フォームが送信されたらemitでイベントを発行して、受け渡す値を指定。($('#m').val())
次にフォームを空にして処理はいったん終了。この処理をサーバ側のonイベントで受け取る。
そしてサーバ側で処理が完了し、サーバ側からemitでイベントが発行された後に受け取るonイベントを設定する。
ここでは、イベントを受け取ってmsgをHTMLに挿入する処理を書いている。

<script>
      const socket = io();
      $('form').submit(() => {
        socket.emit('chat message', $('#m').val()) //chat messageイベント実行
        $('#m').val("");
        return false;
      })
      socket.on('chat message', (msg)=> {
        $('#messages').append($('<li>').text(msg)); //jqueryでメッセージを追加
      })
</script>

ざっくりだけどサーバ ー クライアント間の処理の流れを書きました。

Node.js+Express+Azureでチャットアプリをデプロイしてみる

socket.ioとAzureの使い方を勉強したくて簡単なチャットアプリを作りました。
とはいえ全く知識ないのでとりあえずsocket.ioのGet-startedを利用しました。

自分がわかりやすいように元々のソースを少し触っています。
長くなりそうなので2回に分けて説明しようと思います。今回は前準備編。

サーバ側の設定

index.js

// var app = require('express')(); 分かりづらかったので分解
const express = require('express');
const app = express();

// const http = require('http').Server(app); 分かりづらかったので分解
const http = require('http');
const server = http.Server(app);

// const io = require('socket.io')(server); 分かりづらかったので分解
const socketio = require('socket.io');
const io = socketio.listen(server);

const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
    io.emit('chat message', msg);
  });
});

server.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});

クライアント側の設定

index.html

<body>
  <ul id="messages"></ul>
  <form action="">
    <input id="m" autocomplete="off" /><button>Send</button>
  </form>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://code.jquery.com/jquery-3.1.1.slim.js"   integrity="sha256-5i/mQ300M779N2OVDrl16lbohwXNUdzL/R2aVUXyXWA=" crossorigin="anonymous"></script>
  <script>
    const socket = io();
    $('form').submit(() => {
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
    socket.on('chat message', (msg) => {
        $('#messages').append($('<li>').text(msg));
    });
  </script>
</body>

上のソースの解説

 サーバ側

サーバ側でまずやらないといけないことは、Websocket機能を持つサーバオブジェクトの用意です。  

そのためにまずExpressモジュールを呼び出してExpressオブジェクトをappに格納します。

const express = require('express');
const app = express();

次にServerオブジェクトを作成して引数にapp(つまりExpressオブジェクト)を渡します。これ、ExpressはappをHTTPサーバに渡せる関数ハンドラに初期化するってことらしいんだけどピンと来ないです。。

const http = require('http');
const server = http.Server(app);

そしてsocket.ioモジュールを呼び出して、ソケットとサーバを紐づけます。

const socketio = require('socket.io');
const io = socketio.listen(server);

これでソケット機能を持ったサーバが用意できました。

クライアント側

クライアント側では、Websocketに接続することが必要になります。
そのための記述をまずは書いていきます。

まずsocket.io.jsとjqueryを読ませます。

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.js"   integrity="sha256-5i/mQ300M779N2OVDrl16lbohwXNUdzL/R2aVUXyXWA=" crossorigin="anonymous"></script>

次にscriptタグ内で、以下の記述でsocket.ioに接続します。公式にはTIP: io() with no args does auto-discoveryって書いてあったけどこれはargなしで自動でsocket.ioを検出するということで合ってるだろうか。。

<script>
  const socket = io();
</script>

ここまで書けたら、サーバ側、クライアント側ともに通信時の処理を書いていきます。
簡単にまとめちゃうとonとemitでやりとりします。
クライアントが最初にemitでイベント発行→それをサーバ側のonで受け取り→続いてサーバ側でemit→クライアント側のonで受け取るって感じです。
詳しくは次回書きます。

そういえば、Websocket機能付きサーバを用意する過程で、色々console.logに出してみました。

//console.log(express);

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init();
  return app;
}
//console.log(app);

function (req, res, next) {
  app.handle(req, res, next);
}
//console.log(http);

{ _connectionListener: [Function: connectionListener],
  METHODS:
   [ 'ACL',
     'BIND',
     'CHECKOUT',
     'CONNECT',
     'COPY',
     'DELETE',
     'GET',
     'HEAD',
     'LINK',
     'LOCK',
     'M-SEARCH',
     'MERGE',
     'MKACTIVITY',
     'MKCALENDAR',
     'MKCOL',
     'MOVE',
     'NOTIFY',
     'OPTIONS',
     'PATCH',
     'POST',
     'PROPFIND',
     'PROPPATCH',
     'PURGE',
     'PUT',
     'REBIND',
     'REPORT',
     'SEARCH',
     'SUBSCRIBE',
     'TRACE',
     'UNBIND',
     'UNLINK',
     'UNLOCK',
     'UNSUBSCRIBE' ],
  STATUS_CODES:
   { '100': 'Continue',
     '101': 'Switching Protocols',
     '102': 'Processing',
     '200': 'OK',
     '201': 'Created',
     '202': 'Accepted',
     '203': 'Non-Authoritative Information',
     '204': 'No Content',
     '205': 'Reset Content',
     '206': 'Partial Content',
     '207': 'Multi-Status',
     '208': 'Already Reported',
     '226': 'IM Used',
     '300': 'Multiple Choices',
     '301': 'Moved Permanently',
     '302': 'Found',
     '303': 'See Other',
     '304': 'Not Modified',
     '305': 'Use Proxy',
     '307': 'Temporary Redirect',
     '308': 'Permanent Redirect',
     '400': 'Bad Request',
     '401': 'Unauthorized',
     '402': 'Payment Required',
     '403': 'Forbidden',
     '404': 'Not Found',
     '405': 'Method Not Allowed',
     '406': 'Not Acceptable',
     '407': 'Proxy Authentication Required',
     '408': 'Request Timeout',
     '409': 'Conflict',
     '410': 'Gone',
     '411': 'Length Required',
     '412': 'Precondition Failed',
     '413': 'Payload Too Large',
     '414': 'URI Too Long',
     '415': 'Unsupported Media Type',
     '416': 'Range Not Satisfiable',
     '417': 'Expectation Failed',
     '418': 'I\'m a teapot',
     '421': 'Misdirected Request',
     '422': 'Unprocessable Entity',
     '423': 'Locked',
     '424': 'Failed Dependency',
     '425': 'Unordered Collection',
     '426': 'Upgrade Required',
     '428': 'Precondition Required',
     '429': 'Too Many Requests',
     '431': 'Request Header Fields Too Large',
     '451': 'Unavailable For Legal Reasons',
     '500': 'Internal Server Error',
     '501': 'Not Implemented',
     '502': 'Bad Gateway',
     '503': 'Service Unavailable',
     '504': 'Gateway Timeout',
     '505': 'HTTP Version Not Supported',
     '506': 'Variant Also Negotiates',
     '507': 'Insufficient Storage',
     '508': 'Loop Detected',
     '509': 'Bandwidth Limit Exceeded',
     '510': 'Not Extended',
     '511': 'Network Authentication Required' },
  Agent:
   { [Function: Agent]
     super_:
      { [Function: EventEmitter]
        EventEmitter: [Object],
        usingDomains: false,
        defaultMaxListeners: [Getter/Setter],
        init: [Function],
        listenerCount: [Function] },
     defaultMaxSockets: Infinity },
  ClientRequest: { [Function: ClientRequest] super_: { [Function: OutgoingMessag
e] super_: [Object] } },
  globalAgent:
   Agent {
     domain: null,
     _events: { free: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     defaultPort: 80,
     protocol: 'http:',
     options: { path: null },
     requests: {},
     sockets: {},
     freeSockets: {},
     keepAliveMsecs: 1000,
     keepAlive: false,
     maxSockets: Infinity,
     maxFreeSockets: 256 },
  IncomingMessage:
   { [Function: IncomingMessage]
     super_:
      { [Function: Readable]
        ReadableState: [Function: ReadableState],
        super_: [Object],
        _fromList: [Function: fromList] } },
  OutgoingMessage:
   { [Function: OutgoingMessage]
     super_:
      { [Function: Stream]
        super_: [Object],
        Readable: [Object],
        Writable: [Object],
        Duplex: [Object],
        Transform: [Object],
        PassThrough: [Object],
        Stream: [Object],
        _isUint8Array: [Function: isUint8Array],
        _uint8ArrayToBuffer: [Function: _uint8ArrayToBuffer] } },
  Server: { [Function: Server] super_: { [Function: Server] super_: [Object] } }
,
  ServerResponse: { [Function: ServerResponse] super_: { [Function: OutgoingMess
age] super_: [Object] } },
  createServer: [Function: createServer],
  get: [Function: get],
  request: [Function: request] 
}
//console.log(server);

Server {
  domain: null,
  _events:
   { connection: [Function: connectionListener],
     close: [Function: bound ],
     listening: [Function: bound ],
     upgrade: [Function],
     request: [Function] },
  _eventsCount: 5,
  _maxListeners: undefined,
  _connections: 1,
  _handle:
   TCP {
     bytesRead: 0,
     _externalStream: [External],
     fd: -1,
     reading: false,
     owner: [Circular],
     onread: null,
     onconnection: [Function: onconnection],
     writeQueueSize: 0 },
  _usingSlaves: false,
  _slaves: [],
  _unref: false,
  allowHalfOpen: true,
  pauseOnConnect: false,
  httpAllowHalfOpen: false,
  timeout: 120000,
  keepAliveTimeout: 5000,
  _pendingResponseData: 0,
  maxHeadersCount: null,
  _connectionKey: '6::::3000',
  [Symbol(asyncId)]: 4 
}
//console.log(io);
//console.log(server)の内容も含んだサーバオブジェクトです。

Server {
  nsps:
   { '/':
      Namespace {
        name: '/',
        server: [Object],
        sockets: [Object],
        connected: [Object],
        fns: [],
        ids: 0,
        rooms: [],
        flags: {},
        adapter: [Object],
        _events: [Object],
        _eventsCount: 1 } 
    },
  parentNsps: Map {},
  _path: '/socket.io',
  _serveClient: true,
  parser:
   { protocol: 4,
     types:
      [ 'CONNECT',
        'DISCONNECT',
        'EVENT',
        'ACK',
        'ERROR',
        'BINARY_EVENT',
        'BINARY_ACK' ],
     CONNECT: 0,
     DISCONNECT: 1,
     EVENT: 2,
     ACK: 3,
     ERROR: 4,
     BINARY_EVENT: 5,
     BINARY_ACK: 6,
     Encoder: [Function: Encoder],
     Decoder: [Function: Decoder] 
    },
  encoder: Encoder {},
  _adapter: [Function: Adapter],
  _origins: '*:*',
  sockets:
   Namespace {
     name: '/',
     server: [Circular],
     sockets: { de5NSm3ugNqo1achAAAA: [Object] },
     connected: { de5NSm3ugNqo1achAAAA: [Object] },
     fns: [],
     ids: 0,
     rooms: [],
     flags: {},
     adapter:
      Adapter {
        nsp: [Object],
        rooms: [Object],
        sids: [Object],
        encoder: Encoder {} },
     _events: { connection: [Function] },
     _eventsCount: 1 
    },
  eio:
   Server {
     clients: { de5NSm3ugNqo1achAAAA: [Object] },
     clientsCount: 1,
     wsEngine: 'ws',
     pingTimeout: 5000,
     pingInterval: 25000,
     upgradeTimeout: 10000,
     maxHttpBufferSize: 100000000,
     transports: [ 'polling', 'websocket' ],
     allowUpgrades: true,
     allowRequest: [Function: bound ],
     cookie: 'io',
     cookiePath: '/',
     cookieHttpOnly: true,
     perMessageDeflate: { threshold: 1024 },
     httpCompression: { threshold: 1024 },
     initialPacket: [ '0' ],
     ws:
      WebSocketServer {
        domain: null,
        _events: {},
        _eventsCount: 0,
        _maxListeners: undefined,
        options: [Object] },
     _events: { connection: [Function: bound ] },
     _eventsCount: 1 
    },
  httpServer:
   Server {
     domain: null,
     _events:
      { connection: [Function: connectionListener],
        close: [Function: bound ],
        listening: [Function: bound ],
        upgrade: [Function],
        request: [Function] },
     _eventsCount: 5,
     _maxListeners: undefined,
     _connections: 1,
     _handle:
      TCP {
        bytesRead: 0,
        _externalStream: [External],
        fd: -1,
        reading: false,
        owner: [Object],
        onread: null,
        onconnection: [Function: onconnection],
        writeQueueSize: 0 },
     _usingSlaves: false,
     _slaves: [],
     _unref: false,
     allowHalfOpen: true,
     pauseOnConnect: false,
     httpAllowHalfOpen: false,
     timeout: 120000,
     keepAliveTimeout: 5000,
     _pendingResponseData: 0,
     maxHeadersCount: null,
     _connectionKey: '6::::3000',
     [Symbol(asyncId)]: 4 
    },
  engine:
   Server {
     clients: { de5NSm3ugNqo1achAAAA: [Object] },
     clientsCount: 1,
     wsEngine: 'ws',
     pingTimeout: 5000,
     pingInterval: 25000,
     upgradeTimeout: 10000,
     maxHttpBufferSize: 100000000,
     transports: [ 'polling', 'websocket' ],
     allowUpgrades: true,
     allowRequest: [Function: bound ],
     cookie: 'io',
     cookiePath: '/',
     cookieHttpOnly: true,
     perMessageDeflate: { threshold: 1024 },
     httpCompression: { threshold: 1024 },
     initialPacket: [ '0' ],
     ws:
      WebSocketServer {
        domain: null,
        _events: {},
        _eventsCount: 0,
        _maxListeners: undefined,
        options: [Object] },
     _events: { connection: [Function: bound ] },
     _eventsCount: 1 
    } 
}

Microsoft Azureの無料アカウントの作成

Microsoft Azureの無料アカウント作成方法

Node.jsで作成したアプリをデプロイするためにAzureのアカウントを作成しました。
Azureの使い方はこれからいろいろ調べたいですがとりあえず作成→デプロイがうまくいったので手順まとめておきます。

Azureを始める前に

Azureを使うにはMicrosoftアカウントとクレジットカードの登録が必要になります。
今回のように無料アカウントを作成する場合でも本人確認のために必要です。

 手順

まずMicrosoft Azureのサイトトップで「無料で始める」クリック

f:id:tmykndr:20180605011311p:plain

次の画面も同じ

f:id:tmykndr:20180605011315p:plain

Microsoftアカウントにログインするように言われる(持ってなかったらここで作成)

f:id:tmykndr:20180605011319p:plain

ログインすると個人情報入力画面に進みます。 個人情報入力画面。入力したら次へ。

f:id:tmykndr:20180605013300p:plain


(ちなみに既にAzureアカウントを持っている場合はこの画面が表示されます)

f:id:tmykndr:20180605011322p:plain


確認コード入力画面。電話かメッセージか選べるので自分はメッセージを選択。 送られてきたコードを入力します。入力したらコードの確認へ。

f:id:tmykndr:20180605013539p:plain

カード情報入力して次へ

f:id:tmykndr:20180605014037j:plain

これまでの入力情報に間違いなければサインアップ

f:id:tmykndr:20180605014258p:plain

アンケートに答えて送信

f:id:tmykndr:20180605014349p:plain

以上で登録完了です。

以下がAzureアプリのトップ画面です。ここでVMを作成してデプロイする流れになります。

f:id:tmykndr:20180605014618p:plain

vueはじめましたその1

はじめてのvue

仕事でvueを使うことになったので遅ればせながらvueを勉強し始めました!
仕事で使うからにはしっかり覚えたい!ということで、ここでは勉強したことをアウトプットして脳みそに定着させることを目標にしています。
とはいえNode.jsのほうも勉強したいし更新もしていきたいので、当面はvueとnodeを4:6くらいで更新していけたらいいなーと思っております。
素人Web屋の解説なので同じ初心者の方に向けて説明できたらって感じです。

まずは環境構築

自分はnpm+gulp+browserifyで環境構築しました。
今はwebpackのほうが主流だと思いますがbrowserify使い慣れていたので。
Node.jsをインストールしていることが前提です。(Nodeをインストールするとnpmもインストールされます)

まずはpakage.jsonとgulpfile.jsを作ることから始めます。
npm initはnpmを初期化してpackage.jsonを作るコマンドです。
package.jsonはnpmでインストールしたパッケージをリスト化したものでjson形式で記述されています。

npm init
touch gulpfile.js

あとはvueを使えるgulpfile.jsを書いていけば環境構築は完了です。 長いのでgithubに上げておきます。とりあえずvueを動かすことが目標のディレクトリ構成なのでだいぶ散らかってます。(実際のプロジェクトには使えないと思います笑) https://github.com/tomoyukionodera/vue_test

ちなみにこちらのgithubに上がっているソースを参考にさせていただきました!ありがとうございます!
FAST Browserify + Reactify + Babelify

vueをはじめる前に

vueを簡単にまとめると柔軟に設計できるjsフレームワーク
再利用可能なコンポーネントごとに分けて記述できるので柔軟性が高い。
使い方にはざっくり以下の使い方がある。 

cdnを読ませて使う方法

<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>

npmでvueをインストールして使う方法(こっちでやります)

npm install vue

そして

コンポーネントを使わない方法 (今回はこっちについて) コンポーネントを使う方法

※あと大規模な開発になるとvuexというVue.jsアプリケーションのためのフレームワークを使うことになると思います。
こちらは学習コストがvueより高いので自分もまだ勉強始めてません。始めたらこちらについても書こうと思います。

使い方

vueをはじめるときにまずやることは2つあります。
1 vueインスタンスを作成すること
2 データをマウントする箇所にidを振ること
です。

main.jsでvueインスタンスを作成します。

const Vue = require('vue')

const vue = new Vue({
  el: '#app',
  data: {
    text: "hello vue.js"
  }
})

index.htmlでマウントしたい要素にvueインスタンスに対応したidを振ります。

<body>
  <div id="app">{{text}}</div>
</body>

これでブラウザにはhello vue.jsと表示されます。

vueインスタンスの説明

vueインスタンスを作成するときはまずvueのモジュールをrequire()して用意します。
newしてvueインスタンスを作成します。vueインスタンスは基本的に以下のオプションを指定します。

el: マウントポイントを指定。既存のDOM要素にvueインスタンスを与える。 data: vueインスタンスのためのデータオブジェクト。関数を入れることもできる。

このほかにも代表的なオプションとして以下がある。
methods, computed, props, そしてライフサイクルである。

ライフサイクルとは

methods, computed, propsについては実際に使うときに説明しようと思うがライフサイクルはここで説明しておきます。
ライフサイクルは簡単にいうとインスタンスができる前~破壊された後までを8ステージに分割して、そのステージごとオプションとして指定できるよってことです。
ステージごとにフックが用意されているので好きな処理を割り込ませることができます。 ステージは、beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, destroyed です。
順番に、インスタンスができる前、できた後、マウントされる前、された後、データが更新される前、された後、インスタンスが削除される前、された後です。
分かりやすい例としてはインスタンスを作成して初期値を持たせておきたいときです。
上のソースと結果は同じですが、以下の書き方でブラウザにテキストを表示させることもできます。

const Vue = require('vue')

const vue = new Vue({
  el: '#app',
  data: {
    text: ""
  },
  created: function(){
    this.text = "hello vue.js"
  }
})

マウントポイントを設定したHTMLの説明

マウントポイントになる箇所にidを振りますが、インスタンスのdataオブジェクトのtextにアクセスするためには{{}}を使います。
これはマスタッシュ構文というもので、vueをコンポーネント化しない場合、これを使うことが基本的な形になります。

<div id="app">{{text}}</div>

読んでいただいてありがとうございました!素人Web屋の解説なのでご指摘大歓迎!!
次回はディレクティブというvueで使用する特殊なHTML属性とmethodsについて書きたいと思います!

Node.jsのストリームAPIについて

Node.jsのストリームAPIについて

Nodeの勉強をしてストリームAPIを扱う処理がよくわからなかったので調べました。今日はストリームAPIについて書こうと思います。

Node.jsには大きいデータを扱うとき、ストリームAPIを利用することでメモリの消費を抑えることができます。
ストリームAPIは、データを少しずつ読み込んだり、書き込んだり、変換したりできます。

ストリームAPIをざっくり言うと、
・「データの流れ」を抽象化するためのインターフェース
・ストリームはオブジェクト
・すべてのストリームはEventEmitterのインスタンス
・ストリームの内部データはバッファ。(内部バッファにデータを格納していく。)

ストリームを理解するまえにEventEmitterとバッファについて整理する必要があったのでまとめました。

EventEmitter

EventEmitterはオブジェクトに関数をアタッチすることができるオブジェクト。(イベントを発火させることができる機能)
EventEmitterはNode.jsのeventモジュールの中に入っているオブジェクトなので以下で呼び出せる。

var EventEmitter = require('event').EventEmitter;

EventEmitterはイベントを発行する使い方(emit)とイベントを受け付ける使い方(on)の2通りの使い方がある。 例えば以下のソース

var EventEmitter = require('event').EventEmitter;

function hogeFunc() { //イベントを受け取る
    var ev = new EventEmitter;
    ev.on('data', function(data) {
        console.log('on', data)
    });
}

ev.emit('data', 1); //イベントを発行する。dataイベントを発行して1を渡す
// => on 1

EventEmitterを継承することでemitやonを使えるようになる。
Node.jsではイベントを生成するすべてのオブジェクトはEventEmitterのインスタンスである。
そして重要ポイントとして、streamはEventEmitterを継承している!!
一瞬ストリームの話に戻りますが、ストリームには
ReadableStreamとWritebleStreamがある。

そしてReadableStreamによるファイル読み込み処理は次のように書く。 EventEmitterのインスタンスなのでonをつかってイベントを受け取れる。
そして'data'イベントで少しずつデータが流れてくるのが特徴!'data'イベントはデータ読み込み時に発生して、発生するごとにデータの破片がコールバックに渡されます。この破片をchunkという。
chunkについて、こちらも少しまとめる必要があったので詳しくはバッファを解説するときに書きます。

var readableStream = fs.createReadStream('filename');
readableStream.on('data', function(chunk) {
  console.log(chunk);
});

ここで以下のソースを例としてあげる。
これは'data'イベントで少しずつデータが流れてくるときに、データを結合する処理である。詳しくはバッファの解説で説明します。

onはEventEmitterのインスタンスでしか使えないはずなのに、
reqがonを使ってイベントを受け取っているのはなぜ可能か。

req.on("data", function(chunk) {
    data += chunk;
});

reqはhttp.IncomingMassageクラスのインスタンスであり、ReadableStreamインターフェースを持っている。
ReadableStreamインタフェースを持つので情報を取得する場合は'data'イベントを追加すれば取得できる。
したがってここではonと'data'イベントを使っている。

バッファについて

EventEmitterのreadableStreamを使って情報を取得するために'data'イベントを使うが、そのときにバッファのことも理解する必要があるので合わせてこちらにまとめました。

・バッファとはメモリ上に確保されるバイナリデータを格納するための変数
・Node.jsではBufferクラスとして提供されている
・バッファに一時的に情報を保存することをバッファリングという
・内部バッファにデータを格納していく

例として上の説明にも出てきた以下のソースを解説していく。

req.on("data", function(chunk) {
    data += chunk;
});

まず'data'イベントが発生するごとにデータの破片であるchunkがコールバックに渡されます。
このchunkは'data'イベントがデフォルトで提供するBuffer型オブジェクト(bufferクラスのコンストラクタ)になります。
確認方法は以下

console.log('typeof chunk:', typeof chunk); //typeof chunk: object
console.log(Buffer.isBuffer(chunk)); //true

そしてBuffer型オブジェクトをconsole.logで確認すると、そのオブジェクトに格納されたバイナリ列が16進数で表示されます。

console.log('chunk:', chunk); 
//chunk: <Buffer 6e 61 6d 65 3d 25 45 33 25 38 33 25 38 30 25 45 33 25 38 33 25 39 46 25 45 33 25 38 33 25 42 43 25 45 33 25 38 33 25 38 36 25 45 33 25 38 32 25 41 44 ... >

これらを踏まえたうえで以下のソースを説明します。

var data = ""; //文字列型変数として定義
var posts[]; //後ほど使います
req.on("data", function(chunk) {
    data += chunk; //バイナリ列が格納されているBufferオブジェクトのchunkをdataに格納していく。こうすることでBufferを文字列にキャストして結合されていく。
    console.log('data:', data); //data: name=%E3%83%80%E3%83%9F%E3%83%BC%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88
});

バイナリデータが無事にキャストされて文字列としてdataに格納されました。
ここまでだとデータの扱いが難しいのでさらに以下の処理でデータを扱いやすくします。

req.on("end", function(){
    var query = qs.parse(data); //queryStringを使ってdataをオブジェクト形式にパースします。
    console.log(query); //query: { name: 'ダミーテキスト' }
    posts.push(query.name); //これで投稿データをposts配列に格納できる
    renderForm(posts, res); //postsに格納されたデータをrenderFormメソッドに渡してejsでレンダリング
});

Buuferからstringへのキャスト、パースしてオブジェクト形式に変換するあたりは理解するためにtetatailで質問しました。
https://teratail.com/questions/126265

以下は参考サイト

https://www.tutorialspoint.com/nodejs/nodejs_event_emitter.htm

はじめてのNode.js:Node.jsのイベントシステムを知る 3ページ | OSDN Magazine

[Javascript] イベント駆動型の設計ができるEventEmitterに入門 - YoheiM .NET

Stream今昔物語 - from scratch

http://info-i.net/node-js-stream

Node.jsのStream APIの概要

Node.js Streamのリファレンスを読んでみた(Node.js v7.1.0) - M12i.

Stream | Node.js v10.3.0 Documentation

Node.jsの標準モジュールquerystringについて

Node.jsの標準モジュールquerystringについて

Node.jsを勉強する中で理解が追い付かないところがあったので整理してみたいと思います。

querystringの使い方

querystringはクエリ文字列をオブジェクト形式に変換してくれるNode.jsの標準モジュールの一つです。
※標準モジュールとはNode.jsとともに標準でインストールされているもの。つまりnpmインストールしなくても使えます。
標準モジュールには他にもEvents,stream,buffer,httpなどいろいろあります。種類は以下参照。
https://nodejs.org/api/index.html

querystring単体は以下参照
https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options

var qs = require('querystring'); 

//デフォルトでは = を割り当て文字として認識するので以下は変換失敗になる
console.log(qs.parse("name>tom")); // { 'name>tom': '' } parse失敗

//以下は変換成功
console.log(qs.parse("name=tom")); // { name: 'tom' } parse成功

上のソース例はシンプルにパースした例だけど実際もっといろいろできます。
例えば複数のクエリが来てもデフォルトで対応できます。セパレータはデフォルトでは & 。parseメソッド実行時に引数を与えることで好きなセパレータに指定可能。
さらにオプションを使えば複数のクエリの中から任意の個数だけをオブジェクト形式にしたり、デコードしたり色々できます。
以下に詳しくまとまっています!とても参考になりました! https://qiita.com/chuck0523/items/d8cff2ecfd208e8e513c

いったんここまで!疑問点は明日以降も追記していきます!

Node.jsでEJSを使う方法を追記しました!

Node.jsでEJSを使う!

Node.jsでEJSを扱う一例を以下にあげます。
書き方はいろいろあるかと思いますが、ドットインストールのNode.js入門を参考にしました!

まずEJSを読み込むためにfsモジュールとejsモジュールをrequireします。 fs.readFileSyncでEJSを読み込みます。readFileではなくreadFileSyncを使うのが大事です!(非同期ではなく同期処理をする!)
EJSをレンダリングするときに色々と処理をする場合は、レンダリングする処理を関数にしておくとスッキリ書けてわかりやすい!
下のソースでは、変数postsにデータが入っているとして、それを変数内のデータをEJSに渡してレンダリングさせる流れ。
function renderForm()を定義して、引数に変数postsとreqオブジェクトをとります。
reqオブジェクトはブラウザにレンダリングするために必要です。

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

var template = fs.readFileSync(__dirname + '/hoge.ejs', 'utf-8');

function renderForm(posts, res) {
    var data = ejs.render(template, {
        posts: posts
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(data);
    res.end();
}

server.on('request', (req, res) => {
    if(req.method == "POST") {
        //処理書く
    }else {
        renderForm(posts, res);
    }
});

server.listen(3000, '127.0.0.1');
console.log("server listening...");