水無瀬のプログラミング日記

プログラムの勉強した時のメモ

Slackの会話をWatsonで分析する

はじめに

ある日Watsonを使ってみようと思い何をするか悩んでました。
また別のある日Slackの会話を何かに使えないかと思って、データを抜くことを考えてました。
そしたらなんとSlackのデータは公式で取れるじゃないですか。
※詳細はSlackの公式から
これは組み合わせるしか無いと言うのが今回のテーマ。

やりたいこと

WatsonでSlackの会話を分類。
どのような話題がされているか分析する。
という名目でホントはWatsonとPython使いたいが本音。

使うもの

Watson

使うサービスはWatson Assistant
会話(文章)の意図を分類するのはNLC(Natural Language Classifier)があるけど、
ライトアカウントでは使えなかったのでAssistantを使った。
多分、正しい使い方では無いけど、やりたいことはできるので今回はよしとする。
ちなみにNLU(Natural Language Understanding)は別物。
略称だけ覚えてるとややこしくなるので注意(自分だけかも知れないけど)

Python

Assistantに会話を投げるのはPythonからやる。
後、Slackの履歴を扱うのもやる。
Python使うのは単純に勉強したいから。
cURLでもJSでもJavaでもRubyでもなんでも良いと思う。

やり方

  1. ダウンロードしてきた履歴から分類に活かせそうなチャンネルを選別する
  2. 選別したチャンネルから学習データとして使えそうなデータを選別する
  3. 選別した学習データをAssistantに学習させる
  4. 分類をかける文章をAssistantに投げる
  5. 分類された結果を分析する

チャンネル選別

比較的内容が偏りそうなものを選ぶ。
今回は技術の話とアニメの話と食事の話と+α(完全に身内ネタで例えることも無かったもの)。

学習データ選別

個人的にはWatsonは学習させられるのが最大のメリット&デメリットだと思う。
メリットと感じるのは学習データに特化して頭が良くなるから。
逆にデメリットと感じるのはゴミが混じると順調に頭が悪くなるから。
と、言う理由で学習データを選別する。
ただし、残念ながらその手の話題に詳しく無いので今回はざっくり以下を基準とした。

  1. 10文字以上100文字以下であること
  2. 名詞と動詞が含まれること
  3. URLでないこと
  4. その他いらないと思ったもの(Slack固有のメッセージとか)

1.の理由としては短すぎるのは言葉になっていない可能性があるから。(うん。そうだね。と言った相槌など)
逆に長過ぎるのも学習データとして参考にならなそうだから(おそらく会話のような文章では無いと考えられるから)。
2.の理由は単語のみだけじゃなくてちゃんと日本語っぽいものを選定したかったから。
名詞動詞は形態素解析書けて単純に有る無しだけを見た。(形態素解析の話はまた今度。)
3.はURLなんかあってもしょうがないから。

Assistantに学習させる

Assistantの学習はGUIでできるから、学習データ(CSV形式)を用意する。
生成したプログラムはGitに上がっているのを参照。(そのうちきれいに作り直す)
最終的には以下の用にデータ, Intent名という形にする。

Angularとjsでフロント作るよ言われた,Development
scriptがデフォルトでタイマー起動ができるから1時間おきに走らせてる,Development

ちなみにIntentは会話の意図を指定するところ。
本来の用途のChatbotならパソコンが壊れたんだけど or パソコンの画面がつかないんだけど→故障みたいな意図を設定するんだと思う。
今回は用途が違うから、Angularとjsでフロント作るよ言われた→Development(技術的な話)の様に分類として使う。
AssistantはEntityとか色々設定できるけど今回はこれだけ使う。

詳しい設定は公式ドキュメントがあるからそちらを参照。
この手のドキュメントには珍しく、日本語で書いてある。
※ただし、最新版であるとは限らない。
 仕方ないけど、英語の方が更新早い上に別に同期とか取られて無いと思う。

Assistantに文章を投げる

今回は雑談メインのチャンネルをAssistantに投げる。
全部投げようと思ったらアホほどあったから、一旦自分のデータに絞ることにした。
自分のデータに絞るにはusers.jsonを参照しないといけなくて地味にめんどくさい。
jsでサラッとやってしまった。エラー処理は特に気にしない。
(ホントはこんな書き方しないと思う。。。)
Assistantに投げる処理は公式リファレンスを参照。
コードはGitに。
一応Assistantに投げるテキストは8文字以上 & URLじゃないものにした。

'use strict'

const fs = require('fs');
const usersjson = require('./users.json');
const myId = usersjson.find(user => user.name === 'ユーザー名').id;

const getJsonData = (path) => {
    const jsonData = [];
    fs.readdirSync(path).forEach(file => {
        jsonData.push(JSON.parse(fs.readFileSync(`${path}/${file}`)));
    });    
    return jsonData
}
const getText = (jsonData) => {
    const myText = [];
    jsonData.forEach(data => {
        data.filter(detail => detail.user === myId).map(filtered => filtered.text).forEach(text => myText.push(text));
    });
    return myText;
}
fs.writeFileSync('./myText.txt', getText(getJsonData('チャンネルフォルダ')).join('\n'));

結果を分析する

おそらく数学的な統計学的な正しい分析方法があると思う。
残念ながらその辺りがわからないので、単純にカテゴリ/総数の単純な割合で取得する。
今回は前述の通りの4種類 + Assisに投げなかったテキスト(その他とする)の5種類で分類する。
Assistantの確信度が低いものもその他に振り分けて良いと思ったけど、
想像以上に分類される事がなかったので今回はやめる。 結果は下記の通り。

カテゴリ 分類数 出現率(%)
技術 997 26.47
アニメ 710 18.85
295 7.83
食事 111 2.95
その他 1653 43.89
合計 3766 -

ちょっとした考察もどき

今回その他に分類する条件(Assistantに投げない条件)を8文字未満にしていた。
どうやら想像以上に8文字って打たないらしい。
加えてAssistantの確信度について今回全く見ないでこの結果だったという事は実際はもっと少ない値が出ることになる。
実際Assistantの結果の中には確信度98%です!って言ったハズレの分類や58%くらいですっていうアタリもあった。
という事で次回?に向けてのToDoは大体下記の通り。

ToDo

  • 投げる文字数選定
    少なすぎるのは意味ないから、投げなかったデータから最低値を考える。
    現時点の予想では5.6文字何じゃないかなと
  • Assistantの学習
    今回の学習データは無駄では無いけど、おそらく質としてはあまり良くないと思われる。
    使ったデータをクレンジングするだけでも結構変わると思うからまずはそこから。
    (記号入っている。明らかにジャンル違うとかを消せれば良いかなと)
  • 学習データを均等に用意する
    実はIntentに登録されてるデータ数がまばら。
    現状、技術話題が一番多かった気がするから今回の結果はある種当たり前かもしれない。(データが多い→判別されやすい。)
    単純に分類しやすい話題なだけだったかも知れないけど。
  • ちゃんとテストする(Assistant)
    今回何もテストしないでデータ入れただけなので、次は用意したデータの8割学習、2割テストとかにしたい。

まとめ

一応やりたいことはできた。(Watson使うだけ。Python使うだけ)
データ計測もイマイチだけど、将来的はbotなり、WebなりCLI以外で動くようにしたいなとか考えている。
そもそもなんちゃって解析からレベルを上げるところが大事そう。

Vue.jsを使ってみる

TL;DR.

ソースはここ

はじめに

主に使っているのはAngularだけど、フロントエンドやっているのに他の名前しか知らないですはどうなんだろうと思い勉強を決意。
目標は色んな所に乗ってるVueのコードが読めること。
後は、Angularとの違いとかもちゃんと自力で説明できると良いなくらい。
※ちなみに公式サイトに他FWとの比較は載ってる。

ついでに前々からやろうと思ってたSCSSも触れてみる。

Vue.jsとは

JavascriptのUIを構築するためのFW。
UIメインだけど、SPAも構築可能。
作成者はEvan You。
詳しくはVue.jsの公式サイトに書いてある。

インストール~セットアップ

# cliのインストール
$ npm install -g @vue/cli

# プロジェクトの作成
$ vue create 【プロジェクト名】

Vue CLI v3.0.5
? Please pick a preset:
  default (babel, eslint)
> Manually select features
# 色々効かれるのでお好みで
? Check the features needed for your project: TS, Router, CSS Pre-processors, Linter, Unit, E2E
.
.
.
# インストールが始まり、下記が表示されて終わる
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project ts-vue.
👉  Get started with the following commands:

 $ cd ts-vue
 $ npm run serve

アプリ作成

よくサンプルとして作られるToDoアプリを作ってみる。
ただし、やるのはUIだけ。テキストボックスに入れてボタンを押したらデータが入るところで満足する。 上で作成されたプロジェクトをベースにファイルは書き換えじゃなくて追加でやる。

Component作成

とりあえず書いたものから。
ファイルはcomponents配下にList.vueという名前で作成。

Vue.jsでは1つのファイルにHTML(templateタグ内)とjavascript(scriptタグ内。ts使う時はlang="ts"で書ける)、css(scssとか他のを使いたければ、これもlangを変える)を書くらしい。
Angularでも同じファイルに書けないことは無いだろうけど、あんまり見ないしこれは新鮮だった。
ちなみにtemplateタグ内にはDOMを1つしか書けないっぽい。なので、サンプルでは<div></div>で囲って1つだけにしてる。

作ったアプリでやっていることは、テキストボックスの内容をボタンを押したときにリストに追加するだけ。
作り方としては、テキストボックスの中身はscriptと双方向でバインド。 ボタンを押した時にテキストの内容をリストへ追加している。
リストはループで全件表示。
Angularみたく、addTodo(data.target.value)みたいな書き方をしたかったけど、やり方が分からなかったからこのやり方。

List.vue(components/List.vue)

<template>
<div>
    // v-model="todo"と書くことで変数todoと双方向でバインドできる。
    // angularで言うとこの[(ngModel)]="todo"
    <input type="text" id="data" v-model="todo">
    // @clickでclickイベントをハンドリングできる。
    // angularで言うとこの(click)="addTodo"と同じ。
    <button @click="addTodo()">登録</button>
    <ul id="list">
        // v-forは普通のループ。
        // angul(ry *ngForと同じ。
        <li v-for="todoItem in todoList">
            {{todoItem}}
        </li>
    </ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop } from 'vue-property-decorator';

// @Component デコレータはクラスが Vue コンポーネントであることを示します
@Component({})
export default class ListComponent extends Vue {
  // Componentの呼び出し元から値を受け取りたいときはProp()でできる。  
  @Prop() public addItems!: string[];
  private todo: string = '';
  private todoList: string[] = [];

  constructor() {
      super();
      this.todoList = this.todoList.concat(this.addItems);
  }

  private addTodo() {
      this.todoList.push(this.todo);
      this.todo = '';
  }
}
</script>
<style lang="scss" scoped>
ul {
    margin-left: auto;
    margin-right: auto;
    width: 300px;
    li:nth-child(even) {
        color: blue;
    }
}
#list {
    text-align: left
}
</style>

Todo.vue(views/Todo.vue)

<template>
  <div class="todo">
    // LitComponentを呼び出し、addListを渡してる。  
    <ListComponent :addItems="addList"/>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ListComponent from "@/components/List.vue";

@Component({
  components: {
    ListComponent,
  },
})
export default class Todo extends Vue {
    public addList = ['test1', 'test2'];
}
</script>

実行する

package.jsonにscriptが追加されているのでそれを使う。
npm run serveで起動する。挙動はng serveと同じ感じ。
ちなみ他にはnpm run buildnpm run lintが追加されてた。
以下画像が実行イメージ。意味もなく偶数列の色を変えてみた。

f:id:minase_mira:20181103001354p:plain f:id:minase_mira:20181103001402p:plain

まとめ

思ってた以上にとっつきやすかった。
ちょっと勉強すれば簡単なSPAなら作れそうな感じ。
1つのファイル(.vueファイル)にHTML、JavascriptCSSがまとまってるからわかりやすい気がする。
人気が出るのもなるほどなーって思った。

Angularのリアクティブフォームまとめ

はじめに

ReactiveFormこそAngularの花的な話をどこかで見た気がしたから軽くまとめる。

リアクティブフォームとは

リアクティブフォーム は、時間とともに入力値が変わるフォームを扱うためのモデル駆動なアプローチを提供します。
このガイドでは、シンプルなフォームコントロールの作成と更新から、グループ内の複数コントロールの使用、フォームのバリデーション、高度なフォームの実装の方法を説明します。

Angular日本語ドキュメントによると上記の通りらしい。
使った感じ、入力フォームとか動的にNG出したいときとかに使えそう。
Angular日本語ドキュメントをなぞりながら簡単にまとめる。

前提条件

Angular使えてCLIも使える前提

準備

とりあえずCLIでプロジェクト生成する。

$ ng new reactive-form-test

生成されたapp.module.tsに下記を追加する。
やることはReactiveFoms用にmoduleをインポートするだけ。

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    // other imports ...
    ReactiveFormsModule
  ],
})
export class AppModule { }

使ってみる

生成されてるapp.component.htmlapp.component.tsを下記の通りに変える。

import { Component } from '@angular/core';
import { FormControl,Validators } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  public control = new FormControl('', [
    Validators.required
  ]);
}
入力欄:<input type="text" [formControl]="control" required>
<div *ngIf="control.invalid && (control.dirty || control.touched)">
  <span *ngIf="control.hasError('required')">必須です。</span>
</div>

やってること説明

とりあえず上記サンプルはここから動きを確認できる。
やっていることはフォームの必須チェック。
一旦テキストボックスにカーソル入れた後、フォーカスアウトをすると必須のメッセージが表示される。
htmlでdivを入れているのは、フォーカスが当たるまで、メッセージを表示しないようにするため。

<div *ngIf="control.invalid && (control.dirty || control.touched)">

これが無いと、初期表示時未入力 = バリデーションNGということでいきなりメッセージが出てくる。
大抵の場合、それは本意じゃ無いと思うから上を入れておくのが無難だと思う。

おまけ:ngModelで代用得してみる。

public requiredNg = false;
public text = '';

public valid(target: string) {
  this.requiredNg = target.length === 0;
}
入力欄:<input type="text" [(ngModel)]="text" (blur)="valid($event.target.value)"><br>
<span *ngIf="requiredNg">必須です。</span>

やっていることとしては、テキストボックスの内容を双方向でバインドして、フォーカスアウト時に長さをチェックするということ。
上の様にすればできなくは無いけど、リアクティブフォームを使ったときに比べるとわかりにくいし冗長な気がする。

ちょっと使いやすくする

リアクティブフォームは使いやすい気がするけど、個人的に使いやすいようにちょっと変える。
理由としてはチェックしたいフォームが複数になるとhtmlが見にくくなっていくから。
おまけとして、formControlをグループにまとめる。
とりあえずエラーメッセージをコンポーネントを作るところから始める。
コンポーネント作成はng g component ng-messageでOK。

<div>
  ユーザID:<input type="text" formControlName="userID" required>
  <div *ngIf="userID.invalid && (userID.dirty || userID.touched)">
    <div *ngIf="userID.hasError('required')">
      必須です。
    </div>
    <div *ngIf="userID.hasError('maxlength')">
      最大5文字です。
    </div>
  </div>
</div>
<div>
  パスワード:<input type="text" formControlName="password" required>
    <div *ngIf="password.invalid && (password.dirty || password.touched)">
    <div *ngIf="password.hasError('required')">
      必須です。
    </div>
    <div *ngIf="password.hasError('minlength')">
      最低8文字です。
    </div>
  </div>
</div>
<div>
  e-mail:<input type="text" formControlName="email" required>
    <div *ngIf="email.invalid && (email.dirty || email.touched)">
    <div *ngIf="email.hasError('email')">
      e-mailアドレスのフォーマットにしてください。(xxx@hogehoge.com)
    </div>
  </div>
</div>

個人的に見やすくした結果

結果から書くと下記の様にした。

app.component.html

<form [formGroup]="formGroup">
  <div>
    <div>
      ユーザID:<input type="text" formControlName="userID" required>
      <app-ng-message [form]="userID"></app-ng-message>
    </div>
    <div>
      パスワード:<input type="text" formControlName="password" required>
      <app-ng-message [form]="userID"></app-ng-message>
    </div>
    <div>
      e-mail:<input type="text" formControlName="email" required>
      <app-ng-message [form]="email"></app-ng-message>
    </div>
  </div>
</form>

app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, ValidatorFn, AbstractControl } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  public formGroup = new FormGroup({
    userID: new FormControl('', [
      Validators.required, //必須
      Validators.maxLength(5) // 最大5文字
    ]),
    password: new FormControl('', [
      Validators.required,
      Validators.minLength(8) // 最低8文字
    ]),
    email: new FormControl('', [
      Validators.email, // e-mailフォーマットチェック
      this.duplicateEmailValidator() // 任意のバリデーション(今回は重複チェック)
    ])
  });

  constructor() { }

  ngOnInit() {
  }

  private duplicateEmailValidator(): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const ngList = [
        'hoge@example.com',
        'huga@test.co.jp'
      ];
      const duplicate = ngList.includes(control.value);
      return duplicate ? {'duplicateEmail': {value: control.value}} : null;
    };
  }

  public get userID() {
    return this.formGroup.get('userID');
  }
  public get password() {
    return this.formGroup.get('password');
  }
  public get email() {
    return this.formGroup.get('email');
  }
}

ng-message.component.html

<div *ngIf="form.invalid && (form.dirty || form.touched)">
  <span *ngIf="form.hasError('required')">
    必須です。
  </span>
  <span *ngIf="form.hasError('maxlength')">
    最大5文字です。
  </span>
  <span *ngIf="form.hasError('minlength')">
    最低8文字です。
  </span>
  <span *ngIf="form.hasError('email')">
    e-mailアドレスのフォーマットにしてください。(xxx@hogehoge.com)
  </span>
  <span *ngIf="form.hasError('duplicateEmail')">
    入力頂いたアドレスは重複しています。
  </span>
</div>

ng-message.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-ng-message',
  templateUrl: './ng-message.component.html',
  styleUrls: ['./ng-message.component.css']
})
export class NgMessageComponent implements OnInit {
  @Input() form: FormControl;

  constructor() { }

  ngOnInit() {
  }
}

app.component.tsFormGroupFormCotntrolをまとめている。
まとめたControlは個別にバリデーションを設定できるので、一応全部バラバラにしてみた。
emailのところでは自作のバリデーションを追加している。
Angular日本語ドキュメントによると(英語のドキュメントも同じだけど)Heroの名前にbobが使えないいじめ仕様だから変えてみた。
+そもそも公式の例だとValidators.pattern(/bob/i)って書けば良いはずなので、もうちょっと有り得そうなパターンにしたかったから。
ちなみにapp.co,ponent.tsにgetterを作っているのは、
FormGroupから特定のデータを取るにはFormGroup.get('フォーム名')としなきゃ行けなくて、html他で参照するときに使いづらいから。
(+公式に書いてあるから。)

エラーが有った場合には作成したng-message.componentにFormControlをそのまま投げている。
ng-message.componentで受け取った値に応じたメッセージを表示するという仕組み。
これでメッセージ追加する時はここに足せばいいし、呼び出し側でも長い条件書かなくていいから楽になったのではないかと。

まとめ

リアクティブフォームについて簡単にまとめて簡単に使ってみた。
今までは双方向バインドでええやんって思ってたけど、これは便利かもしれない。
今後は積極的に使っていきたい所存。

参考サイト

Angular7アップデート内容ざっくり和訳

はじめに

Angular7がリリースされたらしい。
とりあえず更新内容をざっくり確認。
英語は得意じゃないので間違いもあるかも。

原文

https://blog.angular.io/version-7-of-angular-cli-prompts-virtual-scroll-drag-and-drop-and-more-c594e22e7b8c

主なアップデート

  • CLIプロンプト
  • バーチャルスクロール
  • D&D
  • etc... Angularの半年おきのメジャーバージョンアップ。
    コアフレームワークCLI、Material全体に渡るアップデートらしい。

アップデート方法

Angular6以降はng updateが叩けるのでそれで行う。
それ以前のバージョンなら一旦6に上げるか、最初から7を入れるのが良いのかも?

# アップデートコマンド
$ ng update @angular/cli @angular/core

アップデート詳細

CLIプロンプト

CLIプロンプトにSchematicsが追加された。
Schematicsコレクションにx-promptkeyを追加することでパッケージを使える様になるっぽい。
Schematicsが何かわからなかったから調べないと恩恵を受けられなそう。

アプリケーションパフォーマンス

開発でのみ必要なreflect-metadataPolyfillがそのままproductビルドに組み込まれていたため、
v7からはpolyfill.tsから自動で削除されるようになった。
JITモードでビルドする時は組み込まれ、productビルド時にはデフォルトででは削除するようになったとのこと。
更にv7からは初期バンドルが2MBを超えると警告、5MBでエラーとなるようになった。
既存に組み込む場合は、angular.jsonに下記を加えればいいと思う。

"budgets": [{
  "type": "initial",
  "maximumWarning": "2mb",
  "maximumError": "5mb"
}]

Angular Material & CDK

v7でVirtual ScrollingDrag & Dropが実装された。
ただし、v7にすると既存の部分で少し見た目が変わるかもとのこと。

Virtual Scrolling

リストの見えてる部分についてDOMからロード、アンロードをするようになった。
そのため、大量スクロールがあるページでは、とても高速化されたっぽい。
詳しくはこちら

Drag & Drop

Angular Component Dev Kitで動作する。
要素のD&Dができる。よく見るやつだから説明もいらないと思う。
一応詳しくはこちら

Improved Accessibility of Selects

mat-form-field内で普通のselectを使った時のアクセシビリティが改善された。
素のselectにはフォーマンス、アクセシビリティユーザビリティのメリットがあるけど、
オプション制御ができるmat-selectも残すらしい。

Angular Elements

Angularの要素はカスタム要素のWeb標準を使用したコンテンツ投影をサポートするようになったらしい。
何のことかよくわからなかった。

その他

ドキュメントが更新された

公式ドキュメントにAngular CLIが追加。
今まで(おそらく)githubにしか情報が無かったからこれはありがたい。

依存パッケージが更新された

  • TypeScript:3.1
  • RxJS:6.3
  • Node10(Node8も継続サポート)

まとめ

機能的には大きなアップデートは無いっぽい?
パフォーマンスとか容量とか良く話題になる気がするから、そこら辺が改善されるのは良さそう。
どっちかって言うとTSとかRxJSのアップデートの方が影響ありそう。
(それぞれ何が今と変わるかまでは調べてない) とりあえず試しにサンプルプロジェクト作ってバージョン上げてみようと思う。

express-generatorの結果をTypeScriptにリファクタリングしてwebpackでビルドする

TL;DR.

コード

初めに

最近Expressを使うときが多々ある。
generatorがあるから雛形は一瞬で作れる。
当たり前だけど生成されるのはjs。
このままでも良いには良いんだけどTypeScriptで書きたい。
そこで、TSにリファクタリングした上でWebpackでビルドしてみる。
webpackなのはtscでビルドするのがめんどくさいから。

準備

express-generatorをインストールしてプロジェクトを作成するとこまでやっとく。
上のサイトから見ればすぐ。
生成されたpackage.jsonのinstallされているものはコピーしておく。

リファクタリング

とりあえず拡張子を全部.tsにするとこからリファクタリングスタート。
方針としては、tsで動くようにするのとvarとかやめるのをメインにする。
※所々jsのままかも知れない...

app.ts

生成されたファイルの中でサーバの設定を書いてあるファイル。
一応クラス化して似たような初期化はfunctionにまとめた。
error処理周りはそのまま返却するよう修正。
ソースは下記の通り。

import * as createError from 'http-errors';
import * as express from 'express';
import * as path from 'path';
import * as cookieParser from 'cookie-parser';
import * as logger from 'morgan';

import { router as indexRouter } from './routes/index';
import { router as usersRouter } from './routes/users';

class App {
  public express: express.Application = express();

  constructor() {
    this.middleWareInit();
    this.routerInit();
    this.errorHandlerInit();
  }

  /**
   * middleware系初期化
   */
  private middleWareInit() {
    // viewEngineは今回使わないのでスルー
    // view engine setup
    // express.set('views', path.join(__dirname, 'views'));
    // express.set('view engine', 'jade');

    this.express.use(logger('dev'));
    this.express.use(express.json());
    this.express.use(express.urlencoded({ extended: false }));
    this.express.use(cookieParser());
    this.express.use(express.static(path.join(__dirname, 'public')));
  }

  /**
   * router初期化
   */
  private routerInit() {
    this.express.use('/index', indexRouter);
    this.express.use('/users', usersRouter);
    this.express.get('*', (req, res) => {
      res.send(`${req.url}にアクセスしたね。`);
    });
  }

  /**
   * error handling系初期化
   */
  private errorHandlerInit() {
    // catch 404 and forward to error handler
    this.express.use(function (req, res, next) {
      // next(createError(404));
      res.send(createError(404));
    });

    // error handler
    this.express.use(function (err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};

      // render the error page
      res.status(err.status || 500);
      res.send('error');
    });
  }
}

// appをexport
export default new App().express;

index.ts & users.ts

生成されたファイルの中でルーティング系の設定、処理を書くところ。
ここはサラッと。ただtsに書き換えただけ。
両方共内容は大して変わらないから下のサンプルはindex.ts

import * as express from 'express';
const router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('in index');
});

export { router };

www.ts

生成されたファイルの中でサーバの実行ファイル。
こいつを叩くと起動する。
ほぼそのまま。起動するだけだから良いかなと。

/**
 * Module dependencies.
 */
import { default as app } from '../app';
import * as debug from 'debug';
import * as http from 'http';

/**
 * Get port from environment and store in Express.
 */
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */
const server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */
function normalizePort(val) {
  const port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */
function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

Webpackでビルドする

必要なものはnpm install --save ts-loader typescript webpack webpack-cliで入れとく。
webpack.config.jsは下記の通り。
実行するwww.tsをentryにしておく。
出力はまとまっているのでserver.jsという名前にした。

const path = require('path');

module.exports = {
  mode: 'development',
  entry: './tsserver/bin/www.ts',
  target: 'node',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  resolve: {
    modules: [
      path.resolve(__dirname, "tsserver"),
      'node_modules'
    ],
    extensions: [
      '.ts', '.js'
    ]
  },
  output: {
    // 出力するファイル名
    filename: 'server.js',
    path: path.join(__dirname, 'dist')
  }
};

実行 & 確認してみる

事前にコピったpackage.jsonの内容をexpressコマンドを叩いたフォルダのpackage.jsonに戻してインストール。
その後、node server.jsで実行。
結果は画像の通り。

f:id:minase_mira:20181003225713p:plainf:id:minase_mira:20181003225723p:plainf:id:minase_mira:20181003225811p:plain

まとめ

念願だったtsでExpressを書くことができた。
正直そもそもtsにする必要があるのか。
webpackでまとめる必要があるのかとかとかあるけど、気にしない方向で。

おまけ

静的ファイル返したい

res.sendFile('ファイル')でOK。
今回の例だとしたみたいな感じ。

// app.tsのrouterInit内
this.express.use('/index', indexRouter);
this.express.use('/users', usersRouter);
this.express.get('*', (req, res) => {
    res.sendFile(path.join(path.resolve(''), './dist/index.html'));
    // res.send(`${req.url}にアクセスしたね。`);
});

フォルダ構成変えないでビルドする

できたのでまとめた。

TypeScript(Javascript)の型判定まとめ

TL;DR.

TypeScript(javascript)で配列の型を調べる時は要注意。
IE滅ぶべし。
今回のソース

はじめに

ある日偉い人は言いました。
「これ、IEで動かしたいんだけど。」
運良く特に苦労も困りもしなかったけど、TS(JS)の型について改めて調べ直したから軽くまとめる。

型を調べる方法(前座)

typeof hogeで調べる事ができる。
結果はコメントの通り。

const string = '';
const number = 0;
const boolean = true;
const nul = null;
const object = {};
const symbol = Symbol('hoge');
let a;

console.log(`string: ${typeof string}`); // string
console.log(`number: ${typeof number}`); // number
console.log(`boolean: ${typeof boolean}`); // boolean
console.log(`nul: ${typeof nul}`); // object
console.log(`symbol: ${typeof symbol}`); // symbol
console.log(`a: ${typeof a}`); // undefined
console.log(`object: ${typeof object}`); // object

ちなみにjavascriptに用意されている型は'object'と下記6個。

  • string
  • number
  • boolean
  • null
  • symbol
  • undefined

nullがnullじゃない罠。
ちゃんと見たければ下記のようにする。
結果は[object hoge]の様になる。
これで基本的には型がちゃんと分かるはず。

const string = '';
const number = 0;
const boolean = true;
const nul = null;
const object = {};
const symbol = Symbol('hoge');
let a;

console.log(`string: ${Object.prototype.toString.call(string)}`); // [object String]
console.log(`number: ${Object.prototype.toString.call(number)}`); // [object Number]
console.log(`boolean: ${Object.prototype.toString.call(boolean)}`); // [object Boolean]
console.log(`nul: ${Object.prototype.toString.call(nul)}`); // [object Null]
console.log(`symbol: ${Object.prototype.toString.call(symbol)}`); // [object Symbol]
console.log(`a: ${Object.prototype.toString.call(undefined)}`); // [object Undefined]
console.log(`object: ${Object.prototype.toString.call(object)}`); // [object Object]

前座その2(配列編)

本編の前に最後の前座。
配列の場合はどうなるのよという話。
typeofではダメ。Object.prototype.toString.call()でようやく配列だとわかる。
ちなみに配列か知りたいかどうかならArray.isArray()を使えば困らない。

const arrayString = new Array<string>();
const arrayNumber = new Array<number>();
const arrayObject = new Array<object>();

console.log(`arrayString:${typeof arrayString}`); // object
console.log(`arrayNumber:${typeof arrayNumber}`); // object
console.log(`arrayObject:${typeof arrayObject}`); // object
console.log(`arrayString:${Object.prototype.toString.call(arrayString)}`); // [object Array]
console.log(`arrayNumber:${Object.prototype.toString.call(arrayNumber)}`); // [object Array]
console.log(`arrayObject:${Object.prototype.toString.call(arrayObject)}`); // [object Array]

本編

で、今回困ったことは上記とは全く関係ない。
配列に特定の要素が含まれているか判定するのにArray.includesというものがある。

const array = [1, 3, 5];
console.log(`array has 3 ? ${array.includes(3)}`); // true
console.log(`array has 4 ? ${array.includes(4)}`); // false

実はこの関数がIEで動かない。IE11でも動かない。
これが困ったこと。
以下、今回やった解決策。

const array = [1, 3, 5];

/**
 * Array.includes()の代替
 * @param array 検索される配列
 * @param target 検索対象
 */
const arrayIncludes = (array: any[], target: any ) => {
    // 配列無いならリターン
    if (array == null) {
        return false;
    }
    // 配列をループし、一致するものがあれば終了
    return array.some(item => {
        return item === target;
    });
}

console.log(`array has 3 ? ${arrayIncludes(array, 3)}`); // true
console.log(`array has 4 ? ${arrayIncludes(array, 4)}`); // false

その他代替案

普通は下記のようにするのかも?

const array = [1, 3, 5];

// 見つかればindex.見つからなければ-1を返す。
if (array.indexOf(3) != 0) {
    console.log('array has 3');
}

なんで型の話をしたか

型が違うときにループする前にリターンしたいとか考えていたから。
記事書きなら思ったけど、別にtrueになることは無いから気にしなくて良い気がしてきた。

まとめ

javascriptの型判定は意外とめんどくさい。
IEは滅んでほしい。

参考

RxJSまとめ #1

TL;DR.

ソースコード
動作サンプル

はじめに

Angularを使っているので、RxJS自体は嫌でも使っていた。
必要なときに必要そうなものを検索して使っていただけなので、いい加減まともに勉強する。

RxJSとは

RxJSは'Reactive Extensions for JavaScript'の略。
Observablesと言うアーキテクチャを用いたリアクティブ・プログラミング用のライブラリである。

便利な点

  • 非同期処理やイベント処理を簡単に書ける
  • エラーおよび終了を上手くハンドリングできる
  • 関数型のコレクションライブラリのようなもの

そもそもRxとは

Rx(Reactive Extensions)はERP(関数型リアクティブプログラミング)を実現するライブラリ。
もともとはMicrosoftが.Netで開発したもの。
現在はMicrosoft以外にもNetflixが様々な言語に移植している。

サンプルコード

// 既存のボタンイベント設定
const button1 = document.getElementById('button1');
button1.addEventListener('click', () => {
    console.log('Cliked!');
});

// RxJSを用いたボタンイベント設定
const button2 = document.getElementById('button2');
fromEvent(button2, 'click').subscribe(
    () => {
        console.log('RxJSClicked!!');
    }
);

上記ではボタンクリックをしたら、クリック数をコンソールに出力するもの。
fromEventオペレータを使用し、ボタンのクリックイベントを取得。
取得したイベントデータをsubscribe(購読)メソッドを使って取り出す。
これだけだとあんまり良くわからないから細かい特徴とかまとめる。

特徴

Purity

RxJSで値を返すときは、pureな関数を通ってくる。
そのためバグが起こりにくいよと公式サイトに書いてあった。
下記の例ではscanがpureな関数なので、問題ない。
(scanが本当にpureかみたいな話を見たことがある気もするけど)

サンプルコード

// 既存
let count = 0;
const button3 = document.getElementById('button3');
button3.addEventListener('click', () => {
    console.log(`oldCount:${++count}`);
});

// RxJS
const button4 = document.getElementById('button4');
fromEvent(button4, 'click').pipe(
    mapTo(1),
    scan((count, value) => count + value, 0)
)
.subscribe(
    (count) => {
        console.log(`RxJSCount:${count}`);
    }
);

Flow

RxJSにはobservableを制御する多くの関数が用意されている。
下記サンプルコードではカウントボタン押下に1秒のインターバルをセットする例。
連打してもカウントが増えないことが確認できるはず。

サンプルコード

// 既存
let count = 0;
const rate = 1000;
let lastClick = Date.now() - rate;
const button5 = document.getElementById('button5');
button5.addEventListener('click', () => {
    if (Date.now() - lastClick >= rate) {
        console.log(`Clicked ${++count} times`);
        lastClick = Date.now();
    }
});

// RxJS
const button6 = document.getElementById('button6');
fromEvent(button6, 'click').pipe(
    throttleTime(1000),
    mapTo(1),
    scan(count => count + 1, 0)
)
    .subscribe(count => console.log(`Clicked ${count} times`));

Values

RxJSを使うとObservableを通ってきたデータを変換することができる。
下記サンプルコードはクリックしたときのマウスのX軸位置を取得する。

サンプル

// 既存
let count = 0;
const button7 = document.getElementById('button7');
button7.addEventListener('click', (event) => {
    count += event.clientX;
    console.log(`position x: ${count}`)
});

// RxJS
const button8 = document.getElementById('button8');
fromEvent(button8, 'click').pipe(
    map(event => event['clientX']),
    scan((count, clientX) => count + clientX, 0)
)
    .subscribe(count => console.log(`position x by RxJS: ${count}`));

まとめ

ざっくり概要に書いてある特徴をまとめました。
これをまとめただけだけど、思ったより長くなったから複数回に分けます。
それでは、今回はこの辺で。

参考サイト