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にあるjasmine
をjest
に変更。
変更しなくても一応動くっぽい。
... "types": [ "jest", "node" ], ...
動かしてみる
ここまでやればnpm test
でJest
を使ったテストが走るようになる。
$ 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に型が付いてないところがあるけど、getState
とextraArgument
の戻り値なので一旦付けないでおく。
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の型が難解過ぎて苦労した。
今回はなんとか動くように作っただけなので、また何か作って行きたいところ。
それでは、今回はこの辺で。
参考サイト
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(); } }
このメソッドではRequest
とResponse
を返すことができる。
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
例えば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で言うany
、Javaで言う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をインストールして簡単な構文までまとめた。
他にも色々機能はあるけどこれ以上は実際に触りながら覚えていこうと思う。
それでは今回はこの辺で。