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

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

TypeScriptのトランスパイル結果を意識する

はじめに

JavaScriptはTypeScriptではないけど、
TypeScriptはJavaScriptなのでそのことをアピールしたい今日この頃。
例えば下記のようなTSのコード。

const promiseFunction = (): Promise<string> => Promise.resolve('OK');
const asyncFunction = async () => console.log(await promiseFunction());
asyncFunction();

JSにトランスパイルされるとどうなるか気にしたことない人も多いかもしれない。
ちなみにこれは下記のようになる。

"use strict";
const promiseFunction = () => Promise.resolve('OK');
const asyncFunction = async () => console.log(await promiseFunction());
asyncFunction();

実質何も変わっていないが、
JSでできる書き方しかしていないので当たり前といえば当たり前。
ただし、これをasync/awaitが無いes6向けにトランスパイルするとこうなる。

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
const promiseFunction = () => Promise.resolve('OK');
const asyncFunction = () => __awaiter(void 0, void 0, void 0, function* () { return console.log(yield promiseFunction()); });
asyncFunction();

適当に使う分には複雑になるなーくらいで良いが、
ちゃんとしようと思うとバンドルサイズが増えたりとか、
パフォーマンスが劣化するとか言った問題がある。

TypeScript使ってるから大丈夫と慢心しないで、
どうトランスパイルされるかを意識したいのでまとめていく。

余談1

JavaScriptでasync/awaitが使えるのはes2017から。

余談2

es6向けではPromiseが使えるのでまだマシな見た目をしている。
es5まで落とすともう何書いてあるかわからない。

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var promiseFunction = function () { return Promise.resolve('OK'); };
var asyncFunction = function () { return __awaiter(void 0, void 0, void 0, function () { var _a, _b; return __generator(this, function (_c) {
    switch (_c.label) {
        case 0:
            _b = (_a = console).log;
            return [4 /*yield*/, promiseFunction()];
        case 1: return [2 /*return*/, _b.apply(_a, [_c.sent()])];
    }
}); }); };
asyncFunction();

type annotation / interface / type alias

全部TypeScriptの機能。
(interfaceは厳密に言うと違うが)ざっくり全部型を付けてくれるもの。
ただし、TypeScriptの型は実行時に保証するものではないので、
全部トランスパイルすると消える。

実行時に保証しないのはとても重要でこれに苦しめられることも多々ある。
e.g. textboxにnumber入れたと思ってたら、実態がstringだった
実行時にinterfaceを用いて型チェック(instanceof IFooとか動かない)

TypeScript

const hoge: string = 'hoge';
const huga: number = 100;
const isPiyo: boolean = true;

interface IFoo {
    key: string;
    value: string;
}
const foo: IFoo = {
    key: 'keyだよ',
    value: 'valueだよ'
}
    
type Bar = {
    id: number;
    name: string;
}
const bar: Bar = {
    id: 1,
    name: 'hoge'
}

JavaScript

"use strict";
const hoge = 'hoge';
const huga = 100;
const isPiyo = true;
const foo = {
    key: 'keyだよ',
    value: 'valueだよ'
};
const bar = {
    id: 1,
    name: 'hoge'
};

Enum

列挙型。
他の言語にあるものと大体同じだと思うが、一番の欠点はJSにないこと。
JSに無いということはトランスパイル結果がそのままにならないということ。

TypeScript

enum Sample {
    HOGE,
    HUGA,
    PIYO
};
// デフォルトだと実態は数値になる
console.log(Sample.HOGE); // 0
console.log(Sample.HUGA); // 1
console.log(Sample.PIYO); // 2
    
// デフォルト値を指定すればその値になる
enum DefaultSample { 
    HOGE = 'hoge',
    HUGA = 'huga',
    PIYO = 'piyo'
}
console.log(DefaultSample.HOGE); // hoge
console.log(DefaultSample.HUGA); // huga
console.log(DefaultSample.PIYO); // piyo
    
// enumをconstにすると展開されビルド結果には残らなくなる
const enum ConstSample {
    HOGE = 'hoge',
    HUGA = 'huga',
    PIYO = 'piyo'
}
console.log(ConstSample.HOGE);
console.log(ConstSample.HUGA);
console.log(ConstSample.PIYO);

JavaScript

"use strict";
var Sample;
(function (Sample) {
    Sample[Sample["HOGE"] = 0] = "HOGE";
    Sample[Sample["HUGA"] = 1] = "HUGA";
    Sample[Sample["PIYO"] = 2] = "PIYO";
})(Sample || (Sample = {}));
;
// デフォルトだと実態は数値になる
console.log(Sample.HOGE); // 0
console.log(Sample.HUGA); // 1
console.log(Sample.PIYO); // 2
// デフォルト値を指定すればその値になる
var DefaultSample;
(function (DefaultSample) {
    DefaultSample["HOGE"] = "hoge";
    DefaultSample["HUGA"] = "huga";
    DefaultSample["PIYO"] = "piyo";
})(DefaultSample || (DefaultSample = {}));
console.log(DefaultSample.HOGE); // hoge
console.log(DefaultSample.HUGA); // huga
console.log(DefaultSample.PIYO); // piyo
console.log("hoge" /* HOGE */);
console.log("huga" /* HUGA */);
console.log("piyo" /* PIYO */);

Optional Chaining / Nullish Coalescing

最近TypeScriptに追加された機能。
どちらも今のJavaScriptに無い。つまり(ry

ちなみにOptional ChainingはStage4でNullish CoalescingはStage3
※Stage4: 取り込み待ち(Finished)。Stage3: 仕様確定(Candidate)

TypeScript

// Optional Chaining
type Foo = {
    id: number;
    ex?: {
        value: string; 
    }
}
const foo1: Foo = {
    id: 1,
    ex: {
        value: 'hoge'
    }
};
const foo2: Foo = {
    id: 2
}
console.log(foo1.ex?.value); // hoge
console.log(foo2.ex?.value); // undefined
    
// Nullish Coalescing
type Hoge = {
    id: number,
    name: string | null
}
const hoge1: Hoge = {
    id: 3,
    name: 'hoge name'
}
const hoge2: Hoge = {
    id: 4,
    name: null
};
console.log(hoge1.name ?? 'name is null.'); // hoge name
console.log(hoge2.name ?? 'name is null.'); // name is null

JavaScript

"use strict";
var _a, _b, _c, _d;
const foo1 = {
    id: 1,
    ex: {
        value: 'hoge'
    }
};
const foo2 = {
    id: 2
};
console.log((_a = foo1.ex) === null || _a === void 0 ? void 0 : _a.value); // hoge
console.log((_b = foo2.ex) === null || _b === void 0 ? void 0 : _b.value); // undefined
const hoge1 = {
    id: 3,
    name: 'hoge name'
};
const hoge2 = {
    id: 4,
    name: null
};
console.log((_c = hoge1.name, (_c !== null && _c !== void 0 ? _c : 'name is null.')));
console.log((_d = hoge2.name, (_d !== null && _d !== void 0 ? _d : 'name is null.')));

まとめ

最近ではJavaScript使う場面ではTypeScriptを使うことがデファクトになってきていると思う。
そのためJavaScriptを触ったことがない人も多いと思う。(自分もその一人)

ただ、TypeScriptはJavaScriptに取って代わる新言語ではないので、
結局はJavaScriptを知らないと苦しむことになる。

TypeScriptのトランスパイル結果を意識しない人もいるかもしれないですが、
間違った使い方をしないためにも、
少しでもJSでどうなってるか気にかけるきっかけになってくれたら幸いです。

参考サイト

serverless frameworkを使ってAWSにデプロイする

はじめに

前回作ったヤフー天気をスクレイピングするアプリを
serverles frameworkを使ってAWSにデプロイする。
今回はcronで定期実行できるとこまで目指す。

TL;DR.

今回作ったアプリ全量

事前準備

AWS

IAMからユーザを作成する。
アクセス権限にでAdministratorAccessを設定すれば他は何でもOK(のはず)。
アクセスキーIDとシークレットアクセスキーをコピっておく。

AWS側はこれで終了。

serverless framework側

公式のドキュメントがあるので、これを参考にしつつインストールとこから始める。

# cli インストール.
# node6以上じゃないとダメそう
$ npm install -g serverless
    
# インストールできているか確認。バージョンが出てくればOK。
$ serverless --version
    
# awsの設定をする
# デフォルトaliasが設定されているので、そっちを使う。
# $ACCESS_KEYと$SECRET_KEYは先程取得したやつに変える
$ sls config credentials --provider aws --key $ACCESS_KEY --secret $SECRET_KEY

# 後々使いたいpluginをインストールしておく
$ npm install --save-dev serverless-offline serverless-webpack

デプロイする

serverles.ymlに設定を書いた上で、sls deployでデプロイできる。
今回作成したserverless.ymlは下記の通り。

service:
  name: weather-forecast-aws
    
plugins:
  # webpack使うようのプラグイン
  - serverless-webpack
  # ローカルで実行するためのプラグイン
  - serverless-offline
    
provider:
  name: aws
  runtime: nodejs12.6
  # リージョンを東京に変更
  region: ap-northeast-1
  environment:
  # 環境変数設定(デプロイ時に引数で渡す)
  WEBHOOK: ${opt:webhook}
    custom:
      webpackIncludeModules: true
  functions:
    weather-forecast-today:
      # 叩くメソッド。
      handler: app.weatherForecastToday
      events:
        # cron設定。jstじゃないので9時間引いた時間を設定
        - schedule: cron(30 22 * * ? *)
    weather-debug-today:
      handler: app.weatherForecastToday
      events:
               # httpリクエスト受ける用設定。debug用に設定しておく
          - http:
              method: get
              path: debugtoday

これでデプロイできる用になったので実際にやってみる。

# デプロイしてみる
# slackのwebhookを使いたいので、値を渡してあげる
$ sls deploy --WEBHOOK=webhook_hoge
    
# デプロイできたらcurlを叩いてみる。エラーなく動けばOK
$ curl localhost:3000/debugtoday

おまけ

deployしたものを消したい場合:sls removeで可能。

まとめ

今回はServerless frameworkを使ってAWSにデプロイするところまでやった。
lambdaを使うのに色々設定等しなきゃいけない認識だったから、
ここまで手軽にできるのはとてもbe便利。

便利だったのでAWSだけでなく、GCFなどでも使ってみようと思う。

参考リンク

AngularでJestを使えるようにする

はじめに

最近React触るついでにJestをやってみたところ非常に快適だったためAngularでもやってみる。
とりあえず動くとこまで試してみる。

TL;DR.

ソースコード

セットアップ

いつも通りプロジェクトを作るとこから……の前に必要なパッケージをインストールしておく。
セットアップは下記で完了。

$ npm install -g @briebug/jest-schematic
# あとはいつも通りプロジェクトを作って
$ ng new ng-jest
> 聞かれるのはお好みで
$ cd ng-jest
# さっきインストールしたパッケージを使ってjestに変える
$ ng g @briebug/jest-schematic:add
$ ng add @briebug/jest-schematic
> karma用のファイルが削除され、jest用のファイルが作成される

tsconfig.spec.jsonの修正

typesにあるjasminejestに変更。
変更しなくても一応動くっぽい。

...
"types": [
    "jest",
    "node"
],
...

動かしてみる

ここまでやればnpm testJestを使ったテストが走るようになる。

$ npm test
> > ng-jest@0.0.0 test .../ng-jest
> > jest
>  PASS  src/app/app.component.spec.ts
>  AppComponent
>     ✓ should create the app (132ms)
>     ✓ should have as title 'ng-jest' (30ms)
>     ✓ should render title in a h1 tag (28ms)
> Test Suites: 1 passed, 1 total
> Tests:       3 passed, 3 total
> Snapshots:   0 total
> Time:        2.808s, estimated 5s
> Ran all test suites.

ちなみにng testでは動かない。
デフォではkarmaを使おうとして失敗する。

$ ng test
> An unhandled exception occurred: Cannot find module 'karma'
> Require stack:
> ...

おまけ:どうしてもng testで動かしたい場合

パッケージのインストールとangular.jsonの修正が必要。
インストールとangular.jsonの修正は下記の通り。

# @types/jestはなくても可
$ npm i -D jest @angular-builders/jest  @types/jest

...
"test": {
    // @angular-devkit/build-angular:karmaを下記に変更
    "builder": "@angular-builders/jest:run",
...

snapshotテストをやってみる

Jest使うのでせっかくだからsnapshotテストもやってみる。
とりあえずsnapshotテストの項目を追加して実行してみる。

it('snapshot test', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled).toMatchSnapshot();
});

__snapshots__フォルダにapp.component.spec.ts.snapというファイルができていればOK。
ファイルの中身は下記の通り。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`AppComponent snapshot test 1`] = `
<div
  id="root3"
  ng-version="8.2.9"
>
  <div
    style="text-align:center"
  >
    <h1>
       Welcome to ng-jest! 
    </h1>
    <img
      alt="Angular Logo"
      src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
      width="300"
    />
  </div>
  <h2>
    Here are some links to help you start: 
  </h2>
  <ul>
    <li>
      <h2>
        <a
          href="https://angular.io/tutorial"
          rel="noopener"
          target="_blank"
        >
          Tour of Heroes
        </a>
      </h2>
    </li>
    <li>
      <h2>
        <a
          href="https://angular.io/cli"
          rel="noopener"
          target="_blank"
        >
          CLI Documentation
        </a>
      </h2>
    </li>
    <li>
      <h2>
        <a
          href="https://blog.angular.io/"
          rel="noopener"
          target="_blank"
        >
          Angular blog
        </a>
      </h2>
    </li>
  </ul>
</div>
`;

テストに失敗する or テストコードの警告を消したい場合

import 'jest'を追記すればOK.
ちゃんとした解決法じゃないのかもしれないけど、一旦これで解決できるので良しとする。

まとめ

AngularでJestを使えるようにした。
snapshotもできるしメッセージもわかりやすい(と思っている)ので、
これからはJestを使っていきたいところ。
それでは今回はこの辺で。

参考にしたサイト

React + ReduxでRssリーダーもどきを作ってみる

はじめに

前にReact + Reduxはやったことがあったけど、型を付けてなかったり、非同期処理を入れてなかったりするので、今回はその辺を踏まえてやってみる。
RSSリーダーとは言ったけど、
自分が望んだ形式で返ってくる前提で作っているのでRSSリーダーもどきとしてる。

TL;DR.

ソースコード

事前準備

まずはcreate-react-appを使ってプロジェクトの雛形を作るとこから。
作れたら必要なライブラリをインストールする

# プロジェクト作成
$ create-react-app react-sample -typescript
$ cd react-sample
# ライブラリインストール
# redux, react-reduxはreduxやるのに入れるいつもの
# redux-thunkはreduxで非同期処理をする際によしなにしてくれるライブラリ
$ npm install --save redux react-redux redux-thunk
# 必要に応じて型定義も
$ npm install --save-dev @types/redux @types/react-redux @types/redux-thunk

実装

Action

よくあるActionと非同期用のActionを定義しておく。
ThunkActionに型が付いてないところがあるけど、getStateextraArgumentの戻り値なので一旦付けないでおく。

import {Action, Dispatch} from 'redux';
import {RssReaderStates} from '../types/RssReaderStates';
import axios from 'axios';
import {ThunkAction} from 'redux-thunk';
    
// type定義
export enum RSS_READER_TYPES {
    ADD_LIST = 'ADD_RSS_LIST',
    FETCH = 'FETCH_RSS'
}
    
// アクション定義
interface IAddRssListAction extends Action{
    type: typeof RSS_READER_TYPES.ADD_LIST;
    payload: RssReaderStates[]
}
    
// ActionCreator群
// リストに追加用Action
export const addRssList = (rssList: RssReaderStates[]): IAddRssListAction => ({
    type: RSS_READER_TYPES.ADD_LIST,
    payload: [...rssList]
});
// Fetch用Action
export const fetchRss = (): ThunkAction<void, any, any, IAddRssListAction> => {
    return async (dispatch: Dispatch<IAddRssListAction>) => {
                // 今回取得先は`json-server`で返すJSONから取得
        const result = await axios.get<RssReaderStates[]>('http://localhost:4000/result');
        dispatch(addRssList(result.data));
    }
};
    
// Actionの型を返す
export type RssActions = (
    ReturnType<typeof addRssList>
    );

Reducer

よくあるReducerのままだと思う。
今回はAction1つしか用意していないので、意味ないけど一応switchで書いておく。

import {RssReaderStates} from '../types/RssReaderStates';
import {RSS_READER_TYPES, RssActions} from '../actions/RssReaderAction';
    
export const rssReaderReducer = (state: RssReaderStates[] | never = [], action:RssActions): RssReaderStates[] => {
    switch (action.type) {
        case RSS_READER_TYPES.ADD_LIST:
            return [...state, ...action.payload];
        default:
            return state;
    }
};
    
// =================
// 以降index.tsに記載
// =================
import {combineReducers} from 'redux';
import {rssReaderReducer} from './RssReaderReducer';
    
export default combineReducers({rssReaderReducer});

Store

今回ReduxThunkを使用したので、middleWareを追加しておく。

import ReduxThunk, {ThunkMiddleware} from 'redux-thunk';
import {applyMiddleware, createStore} from 'redux';
import rootReducer from '../reducers';
import {RssReaderStates} from '../types/RssReaderStates';
import {RssActions} from '../actions/RssReaderAction';
    
export default createStore(
    rootReducer,
    applyMiddleware(ReduxThunk as ThunkMiddleware<RssReaderStates, RssActions>)
);

Container

非同期のリクエストをする用のボタ用の結果を表示する用のリストの2つを用意する。

Button

import React from 'react';
import {fetchRss, RssActions} from '../actions/RssReaderAction';
import {connect} from 'react-redux';
import {ThunkDispatch} from 'redux-thunk';
import {RssReaderStates} from '../types/RssReaderStates';
    
type DispatchProps = {
    onClick: () => void;
};
// Action発火用ボタン
class FetchButton extends React.Component<DispatchProps> {
        // 初期表示用に読み込まれたときにイベントを発火させる
    componentDidMount(): void {
        this.props.onClick();
    }
    render() {
        return (
            <button
                onClick={() => {
                    this.props.onClick();
                }}
            >
                追加
            </button>
        );
    }
};
    
// props返却用
const mapDispatchToProps = (
    dispatch: ThunkDispatch<RssReaderStates, undefined, RssActions>
): DispatchProps => ({
    onClick: () => {
        dispatch(fetchRss());
    },
});
    
export default connect(null, mapDispatchToProps)(FetchButton);

List

受け取ったStateをPropsに変換して表示用のListに渡す。
正直、ここなくても良い気がするけど一旦噛ませない方法がわからなかったので入れておく。

import React from 'react';
import {connect} from 'react-redux';
import {RssReaderStates} from '../types/RssReaderStates';
import ListComponent from '../components/ListComponent';
import {RssReaderProps} from '../types/RssReaderProps';
    
const stateToProps = (state: RssReaderStates[]): RssReaderProps => {
    return {rssList: state};
};
export default connect(stateToProps)(ListComponent);

Component

表示用の処理群。表示する以外の役割を内容にする。
propsのrssListが何故か1段ずれて入ってくるので、対応している。(型内容は後述)
なんとかしたいけど、ぱっと解決できなかったのとサンプルだからという言い訳で放置する。
解決策ご存知の方教えて頂けると幸いです。

import React from 'react';
import {RssReaderStates} from '../types/RssReaderStates';
import {RssReaderProps} from '../types/RssReaderProps';
    
export default (props: RssReaderProps) => (
        <ul>
            {props.rssList['rssReaderReducer']
                .map((rss: RssReaderStates, index: number) =>
                    <li key={index}>
                        <a href={rss.url} target="_blank">{rss.title}</a>: {rss.description}
                    </li>)
            }
        </ul>
    );

型一覧

RssReaderProps

props用の型。
ここで指定した型と何故かずれるので↑のような解決策をしている。

import {RssReaderStates} from './RssReaderStates';
    
export type RssReaderProps = {
    rssList: RssReaderStates[]
}

RssReaderState

Stateの型。

export type RssReaderState = {
    title: string,
    description: string,
    url: string
};

実行結果

画像のような結果になればOK!
ちなみにボタンを押したところで同じ内容が追加されるだけになる。

まとめ

今回はReact + ReduxでRssリーダーを作ってみた。
Reduxで非同期処理ができないことがびっくりだったり、型付けたらReact,Reduxの型が難解過ぎて苦労した。
今回はなんとか動くように作っただけなので、また何か作って行きたいところ。
それでは、今回はこの辺で。 f:id:minase_mira:20190822222944p:plain

参考サイト

Bazel + Ivyを試してみる

はじめに

話題に上がるし、触ろ触ろうと思って触ってなかったBazelとIvyを試してみる。
どちらもAngularのドキュメントを参考に進める。

導入の前に

CLIのバージョンが7から上がってなかったので、CLIのバージョンを上げるところから。

# グローバルを更新する場合
# 今入っているものを削除する
$ npm uninstall -g @angular/cli
# キャッシュ削除
$ npm cache verify
# 再インストール
$ npm install -g @angular/cli

# 既存プロジェクトを上げる場合
$ ng update @angular/cli @angular/core

導入方法

Bazel

Bazel自体のインストール

$ brew install bazel
$ bazel version
> Build label: 0.28.1-homebrew
> Build target: bazel-out/darwin-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
> Build time: Fri Jul 19 16:09:50 2019 (1563552590)
> Build timestamp: 1563552590
> Build timestamp as int: 1563552590

プロジェクト作成前に入れる場合

$ npm install -g @angular/bazel
$ ng new <プロジェクト名> --collection=@angular/bazel

途中から入れる場合

$ ng add @angular/bazel

Ivy

$ ng new <プロジェクト名> --enable-ivy

どっちも入れる

今回はどっちも入れたいので、下記のように指定した。

$ ng new <プロジェクト名> --collection=@angular/bazel --enable-ivy

ビルドしてみる

エラーにならなければOK。何か色々生成されると思う。
ちなみにファイルパスに日本語が含まれるとビルドに失敗するっぽい。
気づかずに数時間ハマった。

$ ng build
> INFO: Analyzed target //src:prodapp (4 packages loaded, 65 targets configured).
> INFO: Found 1 target...
> Target //src:prodapp up-to-date:
>   dist/bin/src/prodapp
> INFO: Elapsed time: 23.606s, Critical Path: 0.70s
> INFO: 0 processes.
> INFO: Build completed successfully, 1 total action

実行したい場合

普段と同じくng serveでOK。
問題なければURL表示されると思うので、アクセスしてページが表示されれば問題なし。
ちなみにこっちも日本語ダメ。

$ ng serve
> Starting...
> Triggering live reload
> requesting reload: reload
> Serving livereload script from http://localhost:35729/livereload.js?snipver=1
> Server listening on http://hogehoge.local:4200/

まとめ

今回は何かと話題になるBazelとIvyを試してみた。
Bazelは動いているからわかりやすいけど、Ivyはわかりにくいと思う。
多分動いていると思うので、今回は導入するまで。
それでは今回はこの辺で。

参考サイト

Aqueduct事始め

はじめに

Dartを触り始めた今日このごろ。
Dartをサーバサイドで使いたかったのでやってみた。
調べた感じaqueductっていうFWが有名っぽかったのでやってみる。

TL;DT.

ソースコード

前提条件

Dartのインストールが終わっている

インストール

$ pub global activate aqueduct

テンプレート作成

$ aqueduct create aqueduct_tutorial
$ cd aqueduct_tutorial

とりあえず起動してみる

$ aqueduct serve
> -- Aqueduct CLI Version: 3.2.1
> -- Aqueduct project version: 3.2.1
> -- Preparing...
> -- Starting application 'aqueduct_tutorial/aqueduct_tutorial'
>     Channel: AqueductTutorialChannel
>     Config: /Users/Tetsuya/Document/Program/aqueduct_tutorial/config.yaml
>     Port: 8888
> [INFO] aqueduct: Server aqueduct/1 started.  
> [INFO] aqueduct: Server aqueduct/2 started.
    
$ curl localhost:8888/example | jq .
> {
>   "key": "value"
> }

Initialization

AqueductのアプリケーションはApplicationChannelから始まる。
アプリケーションに1つサブクラス化し、ルーティングやDBへの接続の初期化を行う。
生成されるやつは下記の通り。(コメントだらけなのでそこは削除。)

import 'aqueduct_tutorial.dart';
    
class AqueductTutorialChannel extends ApplicationChannel {
  // サーバの初期化を行う
  @override
  Future prepare() async {
    logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));
  }
    
  // Routingの設定を行う
  @override
  Controller get entryPoint {
    final router = Router();
    
    router
      .route("/example")
      .linkFunction((request) async {
        return Response.ok({"key": "value"});
      });
    
    return router;
  }
}

routeの指定は他にも下記のようにしてワイルドカードなりの指定ができる。

// projects/1, projects/2などにマッチする
router
    .route("/projects/[:id]")
    .link(() => ProjectController());
    
// `/file/`から始まる全パスにマッチする
router
    .route("/file/*")
    .link(() => FileController());
    
// /healthにマッチする
router
    .route("/health")
    .linkFunction((req) async => Response.ok(null));

Controllers

Controllerはリクエストをハンドリングするところ。
Controllerはorverrideされたhandleメソッドでリクエストをハンドリングしてくれる。

class ProjectController extends Controller {
  @override
  FutureOr<RequestOrResponse> handle(Request request) {
    if (request.raw.headers.value("x-secret-key") == "secret!") {
      return request;
    }
    
    return Response.badRequest();
  }
}

このメソッドではRequestResponseを返すことができる。
Responseを返すとその名の通り、Clientにresponseが返る。
Requestを返すと後続?のControllerへと渡っていく。
expressで言うとこのnext()みたいな挙動っぽい。
ControllerからControllerに渡すのは下記みたいにすればできる。

router
    .route("/file/*")
    // ここの`return request`とすれば後続のControllerが呼ばれる
    .link(() => SelfFileController())
    .link(() => NextFileController());

class SelfFileController extends Controller {
  @override
  FutureOr<RequestOrResponse> handle(Request request) {
      print('selfFileController');
      return request;
  }
}

ResourceControllers

ResourceControllerは最もよく使われるControllerとのこと。
Postで/project, Getで/projectなど1つのクラスで複数のリクエストを受けたい時は多々あるはず。
アノテーションでよしなにやってくれる。
これはSpringなりnestjsなりでみるよくあるやつだと思う。

// router
router
    .route("/operation")
    .link(() => RestController());
router
    .route("/operation/:id")
    .link(() => RestController());

// Controller
class RestController extends ResourceController {
  @Operation.get('id')
  Future<Response> getProjectById(@Bind.path("id") int id) async {
    // GET /operation/:id
    return Response.ok(id);
  }
    
  @Operation.post()
  Future<Response> createProject(@Bind.body() Object body) async {
    // POST /operation
    return Response.ok(body);
  }
    
  @Operation.get()
  Future<Response> getAllProjects({@Bind.query("limit") int limit: 10}) async {
    // GET /operation
    return Response.ok({'result': [1,2,3]});
  }
}

叩いてみる

$ curl localhost:8888/operation
> {"result":[1,2,3]}
$ curl localhost:8888/operation/1
> 1
$ curl -XPOST -H "Content-Type: application/json" localhost:8888/operation -d '{"test": "test"}'
> {"test":"test"}

ManagedObjectControllers

ManagedObjectControllerはRestのオペレーションを自動的にDatabaseのクエリにマッピングしてくれるResourceController。
例えばPOSTは行を足してくれるし、GETはSELECTしてくれる。
ResourceControllerと違ってControllerを用意しなくても大丈夫。
ただ、用意してカスタマイズをすることもできる。

router
  .route("/users/[:id]")
  .link(() => ManagedObjectController<Project>(context));

Configuration

Applicationの設定はYAMLファイルに書くことができる。

database:
  host: api.projects.com
  port: 5432
  databaseName: project
port: 8000

読み込む時はYAMLファイルのキーを指定すればOK。

class TodoConfig extends Configuration {
  TodoConfig(String path) : super.fromFile(File(path));
    
  DatabaseConfiguration database;
  int port;
}

Configを使う時は下記のようにする。
デフォルトではYAMLファイル名はconfig.yamlになっている。

// 読み込むYAMLファイルまでのパスを渡して上げれば良い
TodoConfig config = TodoConfig(options.configurationFilePath);

Running and Concurrency

Aqueductのアプリケーションはaqueduct serveで実行することができる。
Aqueductはマルチスレッドで動かすことができるので、デバッグ用のツール、計測用のツールを付けてアプリケーションを実行するスレッド数を指定して実行できる。

$ aqueduct serve --observe --isolates 5 --port 8888

各スレッドは同じ設定を使いますがそれぞれ別なので、
同じWebサーバーのレプリカを起動するような挙動になるらしい。
このおかげでDBのコネクションプールみたいな動作が暗黙的になるとのこと。

PostgreSQL ORM

Query<T>を使うことでDBのクエリを発行することができる。

下記のような場合だとこれでSELECTを実行できる。

class DbController extends ResourceController {
  DbController(this.context);

  final ManagedContext context;

  @Operation.get()
  Future<Response> getAllProjects() async {
    final query = Query<TutorialProject>(context);

    final results = await query.fetch();

    return Response.ok(results);
  }
}

Defining a Data Model

ORMを使うには、ManagedObject<T>を継承したテーブルと同じ構造を持つクラスを作る必要がある。
Javaとかで言うEntityだと思う。
下記はid,name,dueDateというフィールドを持つテーブルのサンプル。
privateのクラスを作成し、それをベースにManagedObjectを継承したクラスを作成した。

class TutorialProject extends ManagedObject<_TutorialProject> implements _TutorialProject {
  bool get isPastDue => dueDate.difference(DateTime.now()).inSeconds < 0;
}

class _TutorialProject  {
  @primaryKey
  int id;

  @Column(indexed: true)
  String name;

  DateTime dueDate;
}

ManagedObject同士はリレーションを持つことができる。
持てる関係はhas-one,has-many,many-to-manyの3つ。
関係を指定するときには必ずどちらのクラスにも記載が必要になる。
何を言ってるかわからないと思うので、下記のようにすれば良いということ。

class Project extends ManagedObject<_Project> implements _Project {}
class _Project {
  ...

  // has-manyの指定
  ManagedSet<Task> tasks;
}

class Task extends ManagedObject<_Task> implements _Task {}
class _Task {
  ...

  // has-manyで持たれる側
  @Relate(#tasks)
  Project project;
}

Database Migrations

aqueductのCLIは、ManagedObjectを変更した際に自動でdatabaseのmigration用Scriptを生成してくれる。
下記のように実行するとScriptを生成するだけでなく、DBの更新もやってくれる。

$ aqueduct db generate
$ aqueduct db upgrade --connect postgres://user:password@host:5432/database

OAuth2.0

OAuthにもデフォルトで対応してくれているらしい。
ただ残念なことにOAuthの仕組みを良く分かっていないので、なるほど・・・?って感じ。
一応使い方はまとめる。

アプリケーションのサービスとしてAuthServerとそのdelegateを作成する。
delegateトークンの生成方法と保管方法を設定できる。
デフォルトではアクセストークンはランダムな32byteの文字列で、
クライアントID、トークン、アクセスコードはORMを使用してDBに保存される。

import 'package:aqueduct/aqueduct.dart';
import 'package:aqueduct/managed_auth.dart';

class AppApplicationChannel extends ApplicationChannel {
  AuthServer authServer;
  ManagedContext context;

  @override
  Future prepare() async {
    context = ManagedContext(...);

    final delegate = ManagedAuthDelegate<User>(context);
    authServer = AuthServer(delegate);
  }  
}

アクセストークンとユーザー資格情報を交換するための組み込み認証Controllerは、
AuthControllerおよびAuthCodeControllerという名前。

Controller get entryPoint {
  final router = Router();

  // POST /auth/token with username and password (or access code) to get access token
  router
    .route("/auth/token")
    .link(() => AuthController(authServer));

  // GET /auth/code returns login form, POST /auth/code grants access code
  router
    .route("/auth/code")
    .link(() => AuthCodeController(authServer));

  // ProjectController requires request to include access token
  router
    .route("/projects/[:id]")
    .link(() => Authorizer.bearer(authServer))
    .link(() => ProjectController(context));

  return router;
}

aqueductのCLIにはクライアント識別子とアクセス範囲を管理するためのツールがある。
下記のようにすれば良さそう。

    $ aqueduct auth add-client \
      --id com.app.mobile \
      --secret foobar \
      --redirect-uri https://somewhereoutthere.com \
      --allowed-scopes "users projects admin.readonly"

Logging

全リクエストのログを取ることができる。
下記のようにApplicationChannelにloggerを設定すればOK。

class WildfireChannel extends ApplicationChannel {
  @override
  Future prepare() async {
    logger.onRecord.listen((record) {
      print("$record");
    });
  }
}

まとめ

今回はaqueductのTourを進めてみた。
まだTourしかやってないけど、悪くはなさそうな感じ。
実際に何かを作ってみてわかることもあると思うので、とりあえず何か作ってみます。
それでは、今回はこの辺で。

参考サイト

Dart事始め

はじめに

Dartが気になっている今日このごろ。
気になっているなら触ってみようという回。

TL;DR.

ソースコード

インストール

# dart 本体のinstall
$ brew tap dart-lang/dart
$ brew install dart

# cliツールinstall
$ pub global activate stagehand

プロジェクト作成

$ mkdir dart-sample
$ cd dart-sample
$ stagehand console-full
> 下記のようなファイルが生成される
.
├── CHANGELOG.md
├── README.md
├── analysis_options.yaml
├── bin
│   └── main.dart
├── lib
│   └── dart_sample.dart
├── pubspec.yaml
└── test
    └── dart_sample_test.dart

生成されたファイル確認

bin/main.dart

メインになるDartファイル。
トップレベルのmain()関数を含む。これがアプリケーションのエンドポイントになる。

lib/dart_sample.dart

追加のDartファイル。
main.dartからimportされる関数などを含む。

pubspec.yaml

metadataやインストールしたpackageの情報が記載される。
npmで言うとこのpackage.jsonだと思う。

packageインストール

pub getでOK。
npmで言うとこのnpm installと同じだと思う。
インストールするとpubspec.lockというファイルも生成される。
依存関係と依存しているバージョンが記載されている。
これもpackage-lock.jsonと同じだと思う。

実行してみる

ビルドなどは特に不要なので、叩けばOK。

$ dart bin/main.dart
> Hello world: 42!

ファイルを変更してみる

lib/dart_sample.dartを下記のように変更する。

int calculate() {
  return -1;
}

変更が反映されていることの確認する

$ dart bin/main.dart
> Hello world: -1!

出力される内容がちゃんと変わっていることが確認できる。

プロダクション用ビルドをする

さっきやったようにビルドをしなくても動くが、AOTコンパイルがかけられるのでやってみる。

# ビルド
$ dart2aot bin/main.dart bin/main.dart.aot
> なにも表示されないがファイルができていればOK

# 実行してみる
$ dartaotruntime bin/main.dart.aot
> Hello world: -1!

基本構文まとめ

とりあえず動かすことはできたので、公式サイトを参考にしながら基本構文をまとめておく。

基本的にはどこにもあるような感じ。
型推論をしてくれるので、指定しなくても大丈夫。

// 型は推論してくれるので指定しなくてもOK
var number = 42;
// 指定すればそれになる
String hoge = 'hoge';
bool isHuga = false;
int intNumber = 10;
double doubleNumber = 3.141592;
List<String> list = ['hoge', 'huga', 'piyo'];
Map<String, String> map = {'key1': 'hoge', 'key2': 'huga', 'key3': 'piyo'};
Set<String> set = {'hoge', 'huga', 'piyo'};

Objectとdynamic

DartにはTSで言うanyJavaで言うObjectを指定するのに2つある。
Effective Dartによると本当になんでもいい場合はObject
A or Bの型が欲しいとかの場合はdynamicを使う。

// 型がなんでも良い場合は`Object`を使う
Object obj = 'なんでもいい場合';
// AかBの型が欲しいけどって言う場合は`dynamic`を使う
dynamic dynamicVariable = '限定したい場合';

finalとconst

どっちも指定することで再代入を禁止できる。
ざっくり言うと、finalはjsのconst
constは更にObject.freeze()をした状態。
ただし、jsと違ってリストにも効く。

// 型がなんでも良い場合は`Object`を使う
Object obj = 'なんでもいい場合';
// AかBの型が欲しいけどって言う場合は`dynamic`を使う
dynamic dynamicVariable = '限定したい場合';
// 再代入不可にするにはfinalをつける
final List<int> finalList = [1, 2, 3];
// なので、下記はエラー
// finalList = [4, 5, 6];
// 再代入はできないが中身は変えられる
finalList[0] = 10;
// compile時定数にしたい場合は`const`をつける
const List<int> consList = [1, 2, 3];
// constの変数は中身も変えられない
// 実行時エラーになる
// consList[1] = 10;

制御構文

これもほとんどよくあるものなので、知らなかったやつだけまとめる。

// nullじゃなければstrを返却。nullなら`??`より後の値が返却。
var str; // 初期値はnullなので、この場合は後半の文字列が帰る。
var hoge = str ?? 'nullだったよ';

関数

これもほとんどよくあr(ry。

名前付きパラメータ

関数を呼び出すときに仮引数名をつけて関数を呼び出せる。
何が言いたいかと言うと下記みたいにできるということ。

// 名前付きパラメータ。呼び出すときに仮引数名をつけて関数を呼び出せる
// @requiredをつけると変数が必須になる(付いてないのは省略可)
String namedParameter({@required String firstName, String lastName}) {
  return '$firstName $lastName';
}

namedParameter(firstName: 'hoge', lastName: 'huga');

Closure

関数内に関数をかける。
よく言われているClosureってやつと同じだと思う。
ちなみにラムダとかアロー関数とか言われるやつもかける。

String closure(int number) {
  // ラムダもかける
  int square(int num) => pow(num, 2);
  int half(int num) => (num / 2).round();

  return 'square: ${square(number)} / half: ${half(number)}';
}

Class

ここも知らなかったとこだけまとめる。

名前付きコンストラク

文字通り、名前付きのコンストラクタがかける。
呼び出す時はClass.namedConstructor()みたいにすればOK。

class SampleClass {
  // `_`をつけるとprivateになる
  String _name;
  int _age;

  SampleClass(String name, int age) {
    this._name = name;
    this._age = age;
  }

  // 名前付きのコンストラクタ
  SampleClass.initialized() {
    this._name = 'sample';
    this._age = 20;
  }

  String get name => this._name;
  int get age => this._age;

  void selfIntroduction() {
    print('name: $_name, age: $_age');
  }
}

// 呼び出す側
// newはいらない
final sampleClass = SampleClass();
// 名前付きコンストラクタを呼び出す
final sampleClass2 = SampleClass.intialized();

まとめ

今回はDartをインストールして簡単な構文までまとめた。
他にも色々機能はあるけどこれ以上は実際に触りながら覚えていこうと思う。
それでは今回はこの辺で。

参考サイト