electronでキャプチャーソフトつくってみるpart2

キャプチャーソフトを作成中に、録画経過時間を表示する機能が必要になった。フラグのON/OFFでタイマーの開始・リセット出来るならば、他のアプリにも活かせるかもと思い、タイマーコンポーネントを作ってみた。

要件

  1. タイマーつくる
  2. propsであるisRecordを切り返ると、タイマーが開始・リセットされる

propsTypeの代わりに、flowで型チェックを行っている

// @flow
import * as React from 'react';

type PropType = {
  isRecord: boolean
};

type StateType = {
  time: number,
  timerId: number | null
};

export default class Timer extends React.Component<PropType, StateType> {
  constructor(props: PropType) {
    super(props);
    this.state = {
      time: 0,
      timerId: null,
    };
  }

  shouldComponentUpdate(nextProps: PropType): boolean {
    if (nextProps.isRecord !== this.props.isRecord) {
      (nextProps.isRecord) ? this.clearTimer() : this.setTimer();
    }
    return true;
  }

  setTimer() {
    const timerId = setInterval(() => {
      const { time } = this.state;
      this.setState({ time: time + 1 });
    }, 1000);
    this.setState({ timerId });
  }

  clearTimer() {
    const { timerId } = this.state;
    timerId && clearInterval(timerId);
    this.setState({ time: 0, timerId: null });
  }

  formatTime(time: number): string {
    const zeroPad = (num: number): string | number => {
      if (num < 10) { return `0${num}`; }
      return num;
    };

    const hour = zeroPad(Math.floor(time / 60 / 60));
    const minuite = zeroPad(Math.floor(time / 60));
    const second = zeroPad(time % 60);

    return `${hour}:${minuite}:${second}`;
  }

  render(): React.Node {
    const { time } = this.state;
    return (
        <span>
          {this.formatTime(time)}
        </span>
    );
  }
}

timerIdについて、eslint/flowでエラーが吐かれてしまう。 なんだこれ。あとで調べよう。。。

electronでキャプチャーソフトつくってみるpart1

electronに最近はまってます(笑)。キャプチャーソフトも作れるということなので、
reactとmaterial-uiを練習かねて、つくりました。

f:id:poppon555:20180415005255p:plain


electronのキャプチャー機能と動画保存機能を実装してみたかっただけなので、
ものすごく単純です。この調子で仕上げていきたい~

参考サイト

Electronでデスクトップを録画するアプリが簡単に作れました - なになれ
デスクトップを録画するアプリを書いた - Qiita

mochaでテスト実行時にflowの記述を自動で取り除く

はじめに

mochaでテスト実行するときに、flowの記述が残されたままだとテストできない。
だから、前までは前処理としてflowの記述を取り除いたフォルダを作成し、
そのディレクトリに対してテストを実行させていた。

しかし、余計なフォルダを作成させずにmochaを実行できる方法をたまたま見付けたので、 残しておこうと思う。

流れ

  1. テスト環境の容易
  2. flowのインストール
  3. flowの記述追加
  4. テスト失敗の確認
  5. flow-remove-typesの追加と設定
  6. テスト成功

0. テスト環境の用意

mochaとpowerassertを使ったテスト環境構築方法は、以前作成した記事power-assertとmochaを使ったnodejsのテストを参考にしてください。 今回もこの環境を使用します。

1. flowのインストール

前の記事では, flowをインストールしてなかったので追加します。

 npm install --save-dev flow-bin
 ./node_moduleds/.bin/flow init

2. flowの記述追加

前回作成したファイルにflowの記述を追加

// @flow

class Calc {
  
  // flowの記述追加
  static add(x: number, y: number): number {
    return x + y;
  }

  // flowの記述追加
  static sub(x: number, y: number): number {
    return x - y;
  }

}

module.exports = Calc

3. テスト失敗の確認

npm testと叩くと下図の通り。

f:id:poppon555:20180408194909p:plain

Unexpected token :と表示されてる通り、余計なコロンがあるのでテストは失敗します。

4. flow-remove-typesの追加と設定

 npm install --save-dev flow-remove-types

package.jsonの修正

//package.json
scripts: {
  "test": "mocha --require intelli-espower-loader --require flow-remove-types/register ./test/"
}

複数のモジュールを--requireしたい時は、モジュールごとに--requireをつければよいよう。これで問題なくテストは動きます。

5. テスト成功

f:id:poppon555:20180408195523p:plain

electronでReactを使うためのwebpackの設定

はじめに

electron。javascriptでデスクトップアプリケーションを作れるフレームワーク
使ってみたかったけど、なかなかよい作りたいものも思いつかずsample demo appで遊ぶくらいでした。 最近ちょっと良いアイデアも思いついたので、思い切ってelectronを触ってみました。 また合わせてreactの勉強もしてみたかったので、React x electronの環境構築の備忘録を残しておこうと思います。 reduxは使いません。まだ扱えきれるレベルじゃないないので。。。

ポイント

  • node_modulesを呼び出せるelectronのjsをどうやってバンドルするの?
  • main・rendererプロセス用の2種類のjsが必要

electronのjsはfsなどnodeで使えるモジュールを呼び出せるので、
webのReactと同様にimportできるのか。。。

またelectronには、mainプロセスとrendererプロセスが存在し、それぞれにjsファイルがあるのに対して、多分普通のReactのプロジェクトでは、htmlで読み込むのは、一つのjsだけ。
この違いをどうやって、対応するか。。。

結論急げば、webpackの設定でどちらの問題も解決できます。まじ優秀!!

流れ

  1. electronのインストール
  2. electron起動
  3. reactの環境構築
  4. build失敗の確認
  5. webpackの設定(nodeモジュールをimportさせる)
  6. webpackの設定(2つのjsファイルを作成)
  7. buildしてみる

1. electronのインストール

これだけ。はは、便利

npm install -g electron 

2. electron起動

プロジェクトを作って、mainプロセス用のjsと、rendererプロセスで必要なhtmlとそれが呼び出すrenderer.jsを作成します。 js,htmlが用意できたら、electronコマンドでmain.jsを指定します

mkdir sampleApp
cd sampleApp
npm init -y
touch main.js     
touch index.html   
electron main.js

作成したhtml,jsは、tutorialのWriting Your First Electron Appからコピペしてます。

f:id:poppon555:20180402195318p:plain

こんな感じ。

3. reactの環境構築

他の色々なサイトに導入方法は書いてあるが、自分は【Reactではじめるフロントエンド開発入門】1 を参考にさせてもらいました。npm, yarn両方の構築方法が丁寧に書かれてあるので、勉強になりました。

// react
npm install react react-dom

// webpack
npm install -D webpack webpack-cli

// babel
npm i babel-core -D
npm i babel-preset-es2015 -D
npm i babel-preset-react -D
npm i babel-loader -D

webpackの設定も最低限

// webpack.config.js
const webpack = require("webpack");
const path = require('path');

const config = {
  entry: './src/app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'app.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: path.resolve(__dirname, 'node_modules'),
      loader: 'babel-loader',
      query:{
        presets: ['react', 'es2015'],
      }
    }]
  }
};

module.exports = config;

npm buildでapp.jsができると、後はindex.htmlでロードすればおしまい。

f:id:poppon555:20180402195006p:plain

4. build失敗の確認

では、webのプロジェクト同じやり方でjsファイルを作成しトランスパイルさせてみます

nodeモジュールfsを利用した以下のファイルを作成する。

import React, { Component } from 'react';
import ReactDom from 'react-dom';
// 追加: nodeのモジュールfsをimportする
import fs from 'fs'

export default class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      file : ''
    }
  }

  componentWillMount() {
    // fsモジュールを使用する
    this.setState({file:fs.readFileSync('index.html','utf8')})
  }

  render() {
    return <h1>{this.state.file}</h1>
  }
}

ReactDom.render(
  <App />,
  document.getElementById('root')
);

npm buildするとこちら。

f:id:poppon555:20180402195010p:plain

はい、失敗します! fsが読み込めませんと。 なので、これの対応がwebのときと異なり必要となってきます。

5. webpackの設定(nodeモジュールをimportさせる)

google先生に聞いてみると、ここに electron + webpack + react + sass ほとんど書いてました。targetプロパティなるものが、重要ということ。 webpack公式targetによると、targetには,electron-mainとelectron-rendererを 指定できるようなので、これを使いましょう。

では、targetプロパティを追加して、electron用であることを指定します。

// webpack.config.js
const webpack = require("webpack");
const path = require('path');

const config = {
  entry: './src/app.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'app.js'
  },
  target:'electron-renderer',   // 追加 renderer用
  module: {
    rules: [{
      test: /\.js$/,
      exclude: path.resolve(__dirname, 'node_modules'),
      loader: 'babel-loader',
      query:{
        presets: ['react', 'es2015'],
      }
    }]
  }  
};

module.exports = config;

6. buildしてみる

f:id:poppon555:20180402195016p:plain

build成功

f:id:poppon555:20180402195021p:plain

electron起動

7. webpackの設定(2つのjsファイルを作成)

もう一つ課題が残っていました。
mainプロセスとrendererプロセス用の二つのjsをトランスパイルさせましょう。 renderer用のみトランスパイルするなら特に不要な作業だが、main.jsだけはcommonjs記法を採用するという
気持ちの悪い感じがするので、自分はmain.jsもトランスパイルさせました。

設定方法は実に簡単。
main用とrenderer用の設定を記述して、配列にしたものをconfigに格納するだけになります。

// webpack.config.js
const config = [
  {
    // mainプロセス用
    entry: './src/main/main.js',
    output: {
      path: path.resolve(__dirname, 'dist/main'),
      filename: 'main.js'
    },
    target: 'electron-main',
    module: {
      rules:[
      {
        test: /\.js$/,
        exclude: path.resolve(__dirname, 'node_modules'),
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015']
        }
      }
      ]
    }
  },
  {
    // renderer用
    entry: './src/renderer/renderer.js',
    output: {
      path: path.resolve(__dirname, 'dist/renderer'),
      filename: 'renderer.js'
    },
    target: 'electron-renderer',
    module: {
      rules:[
      {
        test: /\.js$/,
        exclude: path.resolve(__dirname, 'node_modules'),
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015'],
          plugins:['transform-class-properties']
        },
      }
      ]
    }
  }
]

その結果、main用とrenderer用の二つのjsファイルを作成されます。

dist
├─main
│      main.js
│
└─renderer
        index.html
        renderer.js

最後に

webpackの設定は奥が深い...。
フロントエンドの勉強は学習範囲が多すぎて、どこか不安になってしまう。
これが仕事となると、本当に効率よい学習が必要なんだろうな~と思う。 けど、jqueryも使う気がしないのは確かだ。

eslintでflowの検証結果も表示させる

そんなにプログラミングするわけではないが、
javascriptは本当に色々変更が激しいなと最近感じてる。
今回は、eslintとflowを使って静的検証が行える環境構築手順を残そう。
eslintを実行させると、flowのチェックと結果を取得できるようにする

目次

  1. eslintを使ってみる
  2. flowを使ってみる
  3. npm run lintでflowの検証結果も表示
  4. npm run lintでflowのエラー結果も表示
  5. flow-remove-typesで実行用ファイルを作成する

1. eslintを使ってみる

  npm install --save-dev eslint

  // .eslintrcを作成
  ./node_modules/.bin/eslint --init

あとはコマンドライン上の質問に答えると、.eslintrc.jsonなどの設定ファイルができる

npm run lintでeslintが起動できるようにpackage.jsonの修正

package.json
  scripts : {
     lint: "eslint ./src"
  }

ディレクトリ、ファイルを作成する

  mkdir src
  touch eslinttest.js
eslinttest.js
  function sum(x,y) {
     return x+y;
  }

  let result = sum(1,1);

これでnpm run lintと入力すると検証結果が表示される

  3:11  error  Infix operators must be spaced                                                                space-infix-ops
  7:5   error  'result' is assigned a value but never used                                                   no-unused-vars
  7:5   error  'result' is never reassigned. Use 'const' instead                                             prefer-const
  7:27  error  A space is required after ','                                                                 comma-spacing
  7:31  error  Newline required at end of file but not found                                                 eol-last

2. flowを使ってみる

  // npmで追加
  npm install --save-dev flow-bin

  // .flowconfigを作成する
  ./node_modules/.bin/flow --init

npm run flowでflowが起動できるようにpackage.jsonの修正

package.json
   ...
   scripts :{
      flow : "flow"
   }
   ...
   touch ./src/flowtest.js
flowtest.js
  //@flow
  function sum(x: number, y: number): number {
     return x+y;
  }

  // あえて間違える
  let result: string = sum(1,1);

npm run flowでflowの検証結果が表示される

Error ----------------------------------------------------------

Cannot assign `sum(...)` to `result` because number [1] is incompatible with string [2].

   src/flowtest.js:7:24
   7| const result: string = sum(1, 1);
                             ^^^^^^^^^

References:
   src/flowtest.js:2:37
   2| function sum(x: number, y: number): number {
                                          ^^^^^^ [1]
   src/flowtest.js:7:15
   7| const result: string = sum(1, 1);
                    ^^^^^^ [2]

3. npm run lintでflowの検証結果も表示

flowの記述が残されたままだと、eslintは失敗する
なので、いくつか前準備が必要になる。

  npm install --save-dev babel-eslint eslint-plugin-flowtype

.eslintrc.jsonも修正し、eslint-plugin-flowtypeのconfigurationをコピペ。

これでnpm run lintを実行すると、eslint単体のときと同様のエラーが表示される。

4. npm run lintでflowのエラー結果も表示

しかし、表示されるのはeslintの結果だけで、flowのエラーはこのままでは表示されない。
なので、もう一つplugin追加が必要となる。

  npm install --save-dev eslint-plugin-flowtype-errors
.eslintrc.json
  ...
    "plugins": [
      "flowtype",
      // 追加
      "flowtype-errors"
    ],
    rules: {
      // 追加
       "flowtype-errors/show-errors": 2,
       "flowtype-errors/show-warnings": 1
    }
  ...

あとはnpm run lint

これでeslintのエラーに加えてflowのエラー内容も表示される.

5. flow-remove-typesで実行用のファイルを作成

npm installl --save-dev flow-remove-types

npm run buildで起動できるようにpackage.jsonの修正

package.json
 scripts: {
  "build": "flow-remove-types src -d lib --pretyy"
 }

npm run buildを実行すると、flowの型情報を削除したファイルがlibに出来る。

最後に

jsはインタプリタ言語なのに静的検証やら型検査が普通になってきているのようだ。これもCIなどでbuildまでの時間を短くできるようになったのが、背景なのかもなと感じた。 これでバシバシ書いていけるぞ

async/awaitの学習

以前Promiseを使ったループ処理について記述したが、これは再帰関数を利用していた。
普段扱いなれているfor文でループ処理はかけるのかなと感じていたが、
どうやらasync/awaitを使えばできることがわかったので、残しておく。

jQueryの$.ajaxからPromiseへの理解に時間を要したので、async/awaitも同様理解するのに
時間がかかるのかと予想していたのだが、この技術はPromiseをさらに便利にする仕組みだ。
だから、Promiseの使い方がわかっていれば簡単に理解できるし、便利になる。

目次

  • asyncを関数名につけるとreturnされるものは、必ずPromiseになる
  • async/awaitを使うと、非同期処理を同期的に記述できる
  • Promise.allも同期的にかける
  • 前回のPromiseでのループ処理をasync/awaitで記述してみる
  • 非同期処理には全てasyncをつけるのか?

asyncを関数名の前につけるとreturnされるものは、必ずPromiseになる

  async function fn() {
    return 1;
  }
  console
  > fn()
  > Promise {1}

async/awaitを使うと、非同期処理を同期的に記述できる

  async function AsyncTest() {
    let result = await fn(); // awaitを使うには必ずasync関数内でないとだめ。
    console.log(result);
    return 1;
  }
  console
  > AsyncTest()
  > 1
  > Promise {1}

Promise.allも同期的にかける

  async function AsyncTest2() {
    let [r1, r2] = await Promise.all([fn(), fn()]);
    console.log(r1);
    console.log(r2);
    return r1 + r2
  }
  console
  > AsyncTest2() 
  > 1
  > 1
  > Promise {2}

前回のPromiseでのループ処理をasync/awaitで記述してみる

  async function fn(i) {
    console.log( (i+1) + '回目')
  }

async function loop (count) {
  for ( let i = 0; i < count; i += 1 ) {
    await fn(i);
  }
  return 'end'
}
  console
>loop(5).then(r=>{console.log(r)})
 1回目
 2回目
 3回目
 4回目
 5回目
 end
Promise {<resolved>: undefined}

最後に

Promiseについて理解できていたら、async/awaitはすごい便利な技である。 nodeで使う場合、v7以上であればasync/awaitがつかえる。

条件付き確率の話

今日は確率について。僕は数学は割と好きな方だったけど、確率にはあまりよい思い出はない。久しぶりに確率の勉強をしていて、気にかかることに出会ったし、数式使ってみたかったのでブログに載せようと思う。

プログラミングのための確率統計1第2章の冒頭に、以下の会話がある。

A: 私の調査によるとゲーム機所持者の犯罪者は50%以上です。何らかの規則をするべきでしょう。
B: 何そのやたら高い数字?
A: 最近の少年犯罪では犯人の半数以上がゲーム機を所持していました。
B: ええと、つっこみ所ありすぎて困るんだけど、とりあえず犯罪関係なしで最近の少年のゲーム機所持率から調べなおしてくれない?

会話の流れからAさんの主張がおかしいことは読み取れるが、
なぜ間違っているのかちゃんと説明できるだろうか?
少なくとも僕は即答できなかったので、今日はこの説明をする。

日本語を数式に

それでは会話をもう少し理解するために、これらを数式で表現してみよう。
(ややこしい問題はまず数式で表現してみるだけでも少し整理できるものだから)

「ゲーム機所持者の犯罪者が50%以上」をどう表現する?

Aさんのはじめの発言の数式表現は正直難しいと思っている。というのは、
- ゲーム機所持者でかつ犯罪をおかす確率が50%以上なのか?(同時確率)
- ゲーム機所持しているならば、その人が犯罪を犯す確率は50%以上なのか?(条件付き確率)
のどちらか判定できないからだ。
ひとまず話をこのさきを進めやすくするため、後者だと想定する。
だから、数式にすると

P(Y=犯罪をおかす| X=ゲーム機所持) \geq \frac{1}{2} \tag{1}

「最近の少年犯罪では犯人の半数以上がゲーム機を所持していました」をどう表現する?

こちらは簡単。

{
    P(X=ゲーム機所持 | Y=少年犯罪を犯す) \geq \frac{1}{2} \tag{2}
  }

もう一度会話を整理

Aさんは、(1)故にゲームを所持しないように規制すべきだという。
つづいてBさんがその根拠は何かという疑問に対して、(2)が根拠だとわけだ。

もう少しシンプルに表現すれば、
P(X=ゲーム機所持| Y=犯罪をおかす) \geq \frac{1}{2}
がなりたつとき、
P(Y=犯罪をおかす| X=ゲーム機所持) \geq \frac{1}{2}
も成り立つから、ゲームを持たせるなという主張をしている。
つまり、条件付き確率の条件を入れ替えても確率が同じだという主張だ。
(もっとも少年犯罪も犯罪も同じと考えているが...)

Aさんの判断の間違いはどこか?

では、条件付き確率の条件を入れ替えても確率が同じとなるのは、
P(X=ゲーム機所持)やP(Y=犯罪をおかす)がどのような関係にあるときだろうか調べてみよう。

条件付き確率の公式

P(A|B) = \frac{P(B|A)P(A)}{P(B)}

を利用すると、(1)は、

{P(X=ゲーム機所持| Y=犯罪をおかす) =
  \frac{P(Y=犯罪をおかす|X=ゲーム機所持)P(X=ゲーム機所持)}
  {P(Y=犯罪をおかす)}
}

さらに変形して、

{
  P(Y=犯罪をおかす|X=ゲーム機所持)
  \geq
  \frac{1}{2}\frac{P(Y=犯罪をおかす)}{P(X=ゲーム機所持)}
}

仮にP(Y=犯罪をおかす|X=ゲーム機所持)が50%であったとすると、

{
  \frac{1}{2}
  \geq
  \frac{1}{2}\frac{P(Y=犯罪をおかす)}{P(X=ゲーム機所持)}
}

{
  P(Y=犯罪をおかす) \geq P(X=ゲーム機所持)
}

これは普通当てはまらないと容易に想像できる。
だからBさんが最後にゲーム機の所持率から調べなおしてというのである。


  1. プログラミングのための確率統計

    プログラミングのための確率統計