daikiojm’s diary

ブログを書くときがやってきたどん!

Nest.jsでgRPCサービスのハンドリング

Nest.jsでは microservices パッケージの1機能として、gRPCによる通信をサポートしている。

Nest.jsでgRPCのサーバ実装をされてる方がいて、この記事がすごく参考になった。 https://qiita.com/jnst/items/27b6a0cd3813b34f98e4

microservices パッケージにはクライアント実装のラッパーも含まれてるようなので、今回はこれを試してみる。
Nest.js公式の sampleの実装 をみるとなんとなく雰囲気はわかる。

サーバ

上記で挙げたQiitaの記事のサンプルリポジトリGitHubに公開されていたので、こちらを使わせてもらった。
以下の .proto に定義されたサービスをハンドリングする前提で進める。

https://github.com/jnst/x-nestjs-grpc/blob/master/protos/rpc/rpc.proto

プロジェクトの雛形作成

$ node -v
v10.14.1
$ yarn global add @nestjs/cli

必要なパッケージをインストール

$ yarn add @nestjs/microservices @grpc/proto-loader grpc protobufjs

今回はNest CLIでプロジェクトを作成し、rest moduleというモジュールを定義した。

$ nest new x-nestjs-grpc-client
$ nest g mo rest

main.ts でhttp待受ポートを変更

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Serverに使うプロジェクトが3000を使っているのでずらした
  await app.listen(3001);
}
bootstrap();

ClientOption

@Client Decoratorに渡すconfigは以下の通り。

import { ClientOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';

const protoDir = join(__dirname, '..', 'protos');
export const grpcClientOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    url: '0.0.0.0:5000',
    package: 'rpc',
    protoPath: '/rpc/rpc.proto',
    loader: {
      keepCase: true,
      longs: Number,
      enums: String,
      defaults: false,
      arrays: true,
      objects: true,
      includeDirs: [protoDir],
    },
  },
};

options.loader 以下のオブジェクトは protoLoader.loadSyncOptions オブジェクト内容として扱われる。

gRPCサーバの機能をREST APIとして提供するコントローラー

今回のサンプルでは、Nest.jsで作成したWebサーバから別のgRPCサーバを呼び出して、REST APIとしてその機能を提供するというユースケースを想定している。

以下は、ControllerからgRPCをハンドリングするサービスの機能を呼び出している部分。 @Client DecoratorでgRPCクライアントを取得して、ライフサイクルフック onModuleInit のタイミングで、実際に使用するサービスを動的に取得している。
(このライフサイクルフックを使わずにConstructor内でサービスの取得を行おうとしたところエラーが発生したので、おとなしく使ったほうが良さそう)

import { Controller, Get, Query, OnModuleInit } from '@nestjs/common';
import { Client, ClientGrpc } from '@nestjs/microservices';
import {
  RpcService,
  Empty,
  GetChampionResponse,
  GetChampionRequest,
  ListChampionsResponse,
  GetBattleFieldResponse,
} from '../../types';
import { grpcClientOptions } from '../grpc-client.options';

@Controller('rest')
export class RestController implements OnModuleInit {
  @Client(grpcClientOptions) private readonly client: ClientGrpc;
  private rpcService: RpcService

  onModuleInit(): void {
    this.rpcService = this.client.getService<RpcService>('Rpc');
  }

  @Get('champion')
  async getChampion(
    @Query() request: GetChampionRequest,
  ): Promise<GetChampionResponse> {
    return this.rpcService.getChampion(request);
  }

  @Get('champions')
  async getChampions(@Query() request: Empty): Promise<ListChampionsResponse> {
    return this.rpcService.listChampions(request);
  }

  @Get('battle_field')
  async getBattleField(
    @Query() request: Empty,
  ): Promise<GetBattleFieldResponse> {
    return this.rpcService.getBattleField(request);
  }
}

実際の利用場面では、gRPCサーバから取得した情報をREST サーバー側で加工してからレスポンスするパターンなどが想定される。
その場合、今回のように直接コントローラーでgRPC Clientを扱うのではなくService層で扱うことになるかと思う。

動作確認

次のように、gRPCサーバーの内容をREST APIとして呼び出せるようになっていることがわかる。

$ curl -s 'localhost:3001/rest/champion?champion_id=1' | jq
{
  "champion": {
    "champion_id": 1,
    "type": 3,
    "name": "Akali",
    "message": "If you look dangerous, you better be dangerous."
  }
}
$ curl -s 'localhost:3001/rest/champions' | jq
{
  "champions": [
    {
      "champion_id": 1,
      "type": "ASSASSIN",
      "name": "Akali",
      "message": "If you look dangerous, you better be dangerous."
    },
    {
      "champion_id": 2,
      "type": "MAGE",
      "name": "Kennen",
      "message": "The Heart of the Tempest beats eternal...and those beaten remember eternally."
    },
    {
      "champion_id": 3,
      "type": "FIGHTER",
      "name": "Tryndamere",
      "message": "Rage is my weapon."
    }
  ]
}
$ curl -s 'localhost:3001/rest/battle_field' | jq
{
  "battle_field": {
    "battle_field_id": 2,
    "name": "The Twisted Treeline",
    "description": ""
  }
}

終わりに

Nest.jsの microservices パッケージ自体、まだ発展途上な感じられる(インターフェースは定義されいるが実装がされていないなど)が、Nest.jsライクな操作(Decoratorでサービスのメソッドを定義するなど)でgRPCのマイクロサービスを構築できるのはかなりのメリットだと思うので、今後も使っていきたい。Server, Clientともにnodeの grpc パッケージのインターフェースを事前に触っていると取っつきやすいように感じる。

リポジトリはこちら
https://github.com/daikiojm/x-nestjs-grpc-client

TypeScriptでgRPCのstreaming RPCを使ったチャットのサンプル

はじめに

Node.jsのgRPCサンプルコードを解説されている方がいたのですが、この記事で触れられているのはUnaryなリクエストのみで、Streem通信に関しては触れられていなかったのでChartを例に試してみた。

tomokazu-kozuma.com

Node.jsは現時点(2018/12/23)でのLTSv10.14.2を使っています。 gRPCの通信方式の違いに関しては、公式ドキュメントの RPC life cycleの項目が参考になります。

Protpcol Buffersの定義

gRPC公式サンプルの中に含まれているstreamを使った例 route_guide.proto を参考にchartアプリケーションを想定した簡単なProtpcol Buffersの定義を作成しました。 https://github.com/grpc/grpc/tree/master/examples/protos

// proto version.
syntax = "proto3";

// package name.
package example;

service Chat {
  rpc join(stream Message) returns (stream Message) {}
  rpc send(Message) returns (Message) {}
}

message Message {
  string user = 1;
  string text = 2;
}

上記.protoファイルのmessageと対応するTypeScriptのInterfaceも作成しました。

// .protoファイルの message定義に合せてtypescriptのinterfaceを用意
export interface Message {
  user: string;
  text: string;
}

Server

今回は、Node.jsの grpc パッケージのインターフェースを一通り眺めながら進めて行きたかったので、protoファイルからコードを自動生成を行うことはしていません。protoLoaderに.protoファイルを渡してサービス定義を読み込みます。
今回のように実行時に動的にコード生成を行う方法を dynamic codegen といい、事前にコード生成(静的にコード生成)をおこなう方法を static codegen と呼ぶようです。

import * as grpc from 'grpc';
import { ServerWriteableStream } from 'grpc';
import * as protoLoader from '@grpc/proto-loader';

import { Message } from './types';

const server = new grpc.Server();
const serverAddress = '0.0.0.0:5001';

// .protoファイルを動的に読み込み
const proto = grpc.loadPackageDefinition(
  protoLoader.loadSync('./chat.proto', {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  })
);

const users: ServerWriteableStream<Message>[] = [];

function join(call: ServerWriteableStream<Message>): void {
  users.push(call);
  notifyChat({ user: 'Server', text: `new user joined ...` });
}

function send(call: ServerWriteableStream<Message>): void {
  console.log(`send message from ${call.request.user}: ${call.request.text}`);
  notifyChat(call.request);
}

function notifyChat(message: Message): void {
  // すべてのユーザーにメッセージを送信
  users.forEach((user) => {
    user.write(message);
  });
}


// .protoファイルで定義したserviceと上記実装をマッピング
server.addService(proto.example.Chat.service, {
  join: join,
  send: send
});

// Serverのconfig設定 & 起動
server.bind(serverAddress, grpc.ServerCredentials.createInsecure());
server.start();

Client

Client側もServer側と同様に、protoLoaderに.protoファイルを渡してサービス定義を読み込みます。

import * as grpc from 'grpc';
import * as protoLoader from '@grpc/proto-loader';
import * as readline from 'readline';

import { Message } from './types';

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const proto = grpc.loadPackageDefinition(
  protoLoader.loadSync('./chat.proto', {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  })
);

const remoteServer = '0.0.0.0:5001';

let username = '';

const client = new proto.example.Chat(
  remoteServer,
  grpc.credentials.createInsecure()
)

function startChat(): void {
  let channel = client.join({ user: username });

  channel.on('data', onDataCallback);

  // 標準入力に新しい行が入力されるごとにServerに送信
  rl.addListener('line', (text: string) => {
    client.send({ user: username, text: text }, () => {});
  });
}


function onDataCallback(message: Message): void {
  if (message.user === username) {
    return;
  }

  console.log(`${message.user}: ${message.text}`);
}

rl.question('User name: ', (answer: string) => {
  username = answer;

  startChat();
});

動作確認

今回は、REPLで実行する。

まずはServer側を実行しておく

$ ts-node server.ts

その後、Client側(1)を実行
ユーザー名を入力しEnterを押すと入力待受状態になる

$ ts-node client.ts
User name: daikiojm
Server: new user joined ...

Client(2)も同様に実行

$ ts-node client.ts
User name: test-user
Server: new user joined ...

その後は双方向の通信が確立される

$ ts-node client.ts
User name: test-user
Server: new user joined ...
daikiojm: はらへった
me too

終わりに

gRPCのstreaming RPCはClient - Serverで真意を発揮しそうなので、機会があればgRPC-webでも試してみたい。
従来httpとwsを併用していたような箇所をgRPC1本にまとめつつ型安全を保てるのはかなり実用的な印象がある。
Node.jsでのServer実装の例は少なそうなので、そのうちもう少し大きめのサンプルを作ってみたいかもしれない。

参考

https://tomokazu-kozuma.com/run-grpc-sample-code-with-nodejs/
https://grpc.io/docs/guides/concepts.html
https://github.com/improbable-eng/ts-protoc-gen

フロントエンドエンジニアのAWSとの付き合い方について

この記事は、ex-handslab Advent Calendar 2018 16日目の記事です(大分遅れています)。 私が以前在籍していたハンズラボはAWS関連技術に強みを持った会社だったので、今回はAWS関連の小ネタを書きたいと思います。

adventar.org

対象の読者

ざーっと、書いてみて以下のような方が対象の読者になるのではないかと思っています。

  • AWS以外のクラウドサービスを使って開発を行っているフロントエンドエンジニア
  • これからAWSをやっていく気持ちを持っているフロントエンドエンジニア

※ 普段からAWSを使って開発している人にとっては当たり前の内容ばかりかもしれないです

この記事を書こうと思ったきっかけ

フロントエンドエンジニアと一口に言ってもいろいろなバックエンドを持つ人がいる

ここでは、言葉の定義を深掘りすることはしないが、以下のようにフロントエンドエンジニアと言ってもいろいろなバックエンドを持つ人がいるなぁという印象を受けている。

  • サーバーサイドインジニア
  • Webコーダー
  • デザイナー
  • プログラミングスクールを経てフロントエンドエンジニアとして働き始めたばかりの人
  • などなど

自分自身も、新卒で入社した会社ではWindowsのネイティブアプリケーションの開発/インフラの保守のような仕事を行っていた後、exしたハンズラボでは、AWSのマネージドサービス/アプリケーションサービスを使い、主にフロントエンド仕事をしてきた経緯がある。

フロントエンド/バックエンドともに専門性が増している

フロントエンド(特にSPA)フレームワーク/ライブラリのが普及していることや、ツールチェーン周りのエコシステムの発達などで、フロントエンドエンジニアの備範囲が広がっている印象がある。 一方サーバーサイドインジニアもまた、マイクロサービスアーキテクチャAWSなどのクラウドサービスに依存するアプリケーションサービスのキャッチアップなど、それぞれの領域で求められる専門性が増している印象がある。 専門性と同時に多少性もましている気がしていて、これに関してはすごく共感できる記事を書いている方がいた。

note.mu

今回は、上記を踏まえてAWS上に構築されるWebアプリケーションを開発するフロントエンドエンジニアが最低限知っておきたいAWSサービスに関する内容をざっくりまとめていきたいと思う。

関わりが深そうなAWSサービス一覧

フロントエンドエンジニアと関わりが深そうなAWSサービス一覧とユースケースを並べていく。

S3

Cloud Front

  • Webフロントエンド配信CDNとして
    • エッジキャッシュサーバーを利用してオリジンからのレスポンスをキャッシュする
    • オリジンのアクセス削減
    • エッジサーバーを利用するため地理的に離れた環境でも高速な読み込みを実現
    • 上記で挙げたS3と合わせて使われることが多い

参考

http://aws.clouddesignpattern.org/index.php/CDP:Cache_Distribution%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

Lambda (Lambda@Edge)

  • SSRレンダリングサーバとして使う
    • EC2などの常駐型サーバーではCPU負荷が高く同時にさばけるリクエスト数が少ないといった問題が起きることも
    • APIを提供しviewは持たないWebサーバーと、SPAの構成でSSRを実現する際にLambdaを構成に追加するだけなので導入が手軽
  • Lambda@Edgeでコンテンツヘッダーのカスタマイズ
    • CloudFront の Behaviorに対してLambda@EdgeをトリガーすることでCloudFrontのレスポンスヘッダーを操作することができる
    • セキュリティ関連ヘッダー(CSPなど)の不可やキャッシュコントロールなども行える

参考 https://dev.classmethod.jp/cloud/aws/lambda-edge-design-best-practices/ https://tech-blog.abeja.asia/entry/front-engineer-re-invent

Route53

  • S3 + CloudFrontで配信するコンテンツに独自のDNS名を付ける必要がある際に利用する
    • AWSとの親和性が高いのが特徴だが単純なDNSサービスなのでバックエンドが必ずしもAWSである必要はないので他のクラウドサービスなどと組み合わせることも可能

Cognito

  • フロントエンドアプリケーションとAWSだけで完結する認証/認可
    • AWSから提供されいてるCognitoのSDKをフロントエンドアプリケーションに組み込むことで、アプリケーションの認証/認可を行うことができる

API Gateway

  • Lambdaと組み合わせてWebフロントエンドアプリケーション用のAPIの構築に利用
  • サーバーレスアーキテクチャの文脈でLambdaと組わせて使われることが多い

Code Build

  • フロントエンドアプリケーションのビルド・テスト

circle CIを使ったことがあれば、同じ感覚でyamlを書けばAWS上でCodeのビルド・テスト(デプロイ)が可能 ソースコードホスティングまで含めてAWSで行っている場合、AWSに閉じた環境で一貫したフロントエンドアプリの開発・デプロイができるので便利っぽい

AppSync

  • GraphQLでサーバーレスバックエンドを提供するアプリケーションサービス
    • ネイティブアプリの他にWebフロントエンド向けのSDKを提供しているので、最新のSPAフレームワークにも組み込みやすい

Amplify Console

  • WebフロントエンドアプリケーションのCI/CDを簡単に構築出るサービス
    • 今年のre:Inventで発表されたらしい

参考

https://dev.classmethod.jp/cloud/aws/amplify-console/

フロントエンド周りのAWS構築/運用は誰がやるのか問題

上記で挙げたようなフロントエンドアプリケーションを構築するために利用するAWSサービスは様々。 チームの規模感なんかによっても、変わってくるとはいもうけど、AWS特有のアプリケーションサービスが多いので、インフラの管理を行うチームでは対応しきれずフロントエンドエンジニアがここらへんの面倒を見るパターンも多そうというという印象。 AWSに限らず、ある程度の規模感をもって開発しているチームでフロントエンド配信CDNの運用とかをどうやっているかは単純に興味がある。

最後に

ここまで挙げてきたように、AWSではフロントエンド開発にも欠かせないツールを数多く提供している印象。 上記で挙げたようなアプリケーションサービスはサーバーレスアーキテクチャの文脈で話に出ることも多い。 サーバーサイドの開発経験がないフロントエンドエンジニアでもこのようなAWSサービスを組み合わせることで簡単にWebアプリケーション全体を構築することができたりする。

(とりあえず書いてみた感が否めないのとかなり遅れてしまったけど、書けたのでほっとしている😇)

bitbankのCandlestick APIを使ってAngularでチャートを描画する

多くの仮想通貨取引所では、APIトレーダー向けに取引のための注文APIや価格情報を取得するためのAPIが公開されている。
今回は、bitbankのpublic APIのうちcandlestickを使ってAngularと ngx-chartsを使ってチャートを描画してみたので、ポイントとサンプルコードを記録しておく。

f:id:daikiojm:20180929225952p:plain

Candlestickについて

f:id:daikiojm:20180929225821p:plain

今回使用するcandlestick APIは、ろうそく足チャートを描画するのに必要な情報を取得できるAPIで、以下の情報が含まれる。

  • 期間開始時の価格
  • 期間内の最高値
  • 期間内の最安値
  • 期間終了時の価格
  • 期間内の出来高合計

OHLCVデータ

CandlestickデータはOHLCVというフォーマット(フォーマットというより並び順)で配信されていて、それぞれは以下の情報とマッピングすることができる。
実際にはOHLCV + 期間を示すタイムスタンプが配信されている。
ちなみに、OHLVまでを 四本値 と呼んだりもするらしい。

  • open(O) :期間開始時の価格
  • high(H) :期間内の最高値
  • low(L) :期間内の最安値
  • close(C) :期間終了時の価格
  • volume(V) :期間内の出来高合計
  • ※timestamp :UnixTime

参考: https://docs.bitbank.cc/#!/Candlestick/candlestick

OHLCVとngx-chartsのseriesとのマッピング

今回作ったサンプルでは、ロウソクやヒゲは描画せず、単純なlineチャートとして表現したいので、OHLCVから必要な価格情報とtimestampのみを使用して、ngx-chartsのseriesの形にマッピングする。
といっても、以下のようなTypeScriptのインターフェースを定義して、RxJSのmapオペレータの中で単純なマップをするだけ。

// ngx-chartsに渡すデータのtype (nameがx,valueがy軸に対応する)
export interface Series {
  name: number;
  value: number;
}

次のようなマッピング用のメソッドを用意して、cnaclestickの6番目の要素とのtimestampと1番目の要素の開始時の価格でlineチャートを描画する情報を作っていく。

private mapSeries(candleStick: CandleStick): Series[] {
  return candleStick.ohlcv.map((data) => ({
    name: data[5],
    value: +data[0],
  }));
}

実際にはRxJsのmapオペレータの中で上記のマッピング用のメソッドを呼び出す。

  private fetchCandlestick(
    pair: string,
    unit: string,
    period: string
  ): Observable<Series[]> {
    const url = this.getCnadlestickUrl(pair, unit, period);
    return this.http.get<BbApiResponse<BbCandleStickResponse>>(url).pipe(
      map((data) => data.data.candlestick[0]),
      map((candlestick) => this.mapSeries(candlestick))
    );
  }

完成したもの

リポジトリはこれ
daikiojm/angular-bitbank-public-chart -GitHub

StackBlitzで動いてるやつ
daikiojm/angular-bitbank-public-chart -StackBlitz

通過ペアと期間を指定して、現在から1日前、1週間前、1ヶ月前、1年前それぞれの価格lineチャートが取得できるようになっている。
かなりざっくりな実装だが、価格推移の雰囲気を表現するのには十分じゃないかと思う。
各取引所でこういったpublicデータを配信するAPIがが公開されているので、リアルタイムデータと合わせてまとめサイトみたいなのを作ってみても面白そう。

参考

ErgoDox EZのファームウェアをMacでビルドしようとしたら辛かったのでDcokerでビルドした

ErgoDox EZは、GUIからキーマップを作成して、.hex ファイルのファームウェアをダウンロードできるサイトを用意してくれている。

ErgoDox EZ Configurator

ただ、ここから変更できる内容には制限があり、少し凝ったキーマップに変更しようとすると、キーマップファイルを自分で修正してソースからビルドする必要がある。 キーマップの作成時には、以下の記事が大変参考になった。

qiita.com

自分の場合、MacCommand キーを複数のキーに割り当てたくなりGUIからのキーマップ作成を諦め、ソースからビルドすることを決心した。

Macでのローカルビルドを試みた

MacでErgoDox EZのファームウェアをローカルビルドするには avr-gcc というavrマイコン用のビルドツールをインストールすればいいことがわかった。
brewで一発インストールできるとの記事を見て、軽い気持ちで試みるもmake実行中にコンパイルエラーが発生し、brewやビルドツール周りの環境を確認するも未だに解決していない状態。
一刻も早くキーマップを変更して快適なErgoDoxライフを送りたかったので、homebrew-avrリポジトリにIssueを投げつつ、Dockerで用意されたビルド環境を使うことにした。

今回の状況は、以下のIssueの通り

github.com

Dockerでビルド

ErgoDox EZのファームウェアが配布されているリポジトリ qmk/qmk_firmware にビルド用のDockerfileがあったので、これを使い使ってみる。 ひとまず、リポジトリに含まれているデフォルトのキーマップをビルドする。

クローンしておく

$ git clone git@github.com:qmk/qmk_firmware.git
$ cd qmk_firmware

Dockerfileからイメージをビルド
あとで気づいたが、 edasque/qmk_firmware を使ってもいいかもしれない。

$ docker build -t qmk_firmware . 

対象のキーボードの種類と作成した(今回はデフォルト)キーマップを環境変数に指定して、dockerを起動する

$ docker run -e keymap=default -e keyboard=ergodox_ez --rm -v (pwd):/qmk:rw qmk_firmware

正常にビルドできれば、 ergodox_ez_default.hex というファイルが出力されているはず。

終わりに

ひとまず、当初考えていたキーマップに変更することができて概ね満足です。
まだまだ、ErgoDox自体に慣れるにまでに時間がかかりそうで、暇な時にタイピングゲームとかしている状況。
(学生の頃にイキってUS配列のMacを使い始めたものの、なかなか慣れなかったときの感覚)
このブログもErgoDoxで書いてるけど、正直苦行なので早く慣れていきたい。

ScottyでAngularSPAをS3+CloudFrontにデプロイ

CI環境上または、開発環境上でビルドしたファイルをS3にデプロイする際には、s3 syncなどのコマンドを使うことが多いかと思います。
そんな時、Scottyが割と便利だったのでAngular CLIで作成したSPAをデプロイする前提で簡単に使い方を紹介します。

前提

  • AWS credentialsが設定されている
  • デプロイ先のS3バケットは作成済みである
  • Node.js(v6.9以上)が使える
  • Angular CLIがインストールされている

ひとまずやってみる

今回は、ng newした直後のAnguarプロジェクトをng buildした結果生成されるファイルをScottyを使ってデプロイします。

Scottyをグローバルインストール

$ npm install -g scottyjs

ビルド

$ ng build --prod

scottyコマンドを叩きデプロイ

$ scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --source ./dist

CloudFrontを作成しない場合

$ scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --nocdn --source ./dist

※ 実行時のオプションパラメータはREADMEを参照

f:id:daikiojm:20180313093858p:plain

scottyコマンドを実行した後数分で、CloudFront ウェブディストリビューションの状態が有効になり、CloudFrontに設定されたURLでS3にでデプロイされたAngular SPAにアクセスできるようになります。

npm scriptsにビルド+デプロイコマンドを定義

上記の手順をnpm scriptsにまとめてしまいます。

{
  "name": "scotty-test",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "deploy": "npm run build && scotty --bucket scotty-angular-test --region ap-northeast-1 --spa --update --source ./dist",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
...

また、Scottyがグローバルインストールされていない環境でも利用できるよう、devDependencyに登録しておきます。(この場合グローバルにインストールされている必要はない)

$ npm install --save-dev scottyjs

所感

現状S3への簡単なデプロイ方法として s3 syncコマンドを使う方法があるけど、Scottyの場合、devDependencyでプロジェクトに追加して、npm scriptsから呼び出せるので別でビルド(orローカル開発)環境にAWS CLIが必要ないのがよい。
「手元でサクッとビルドしてとりあえず確認環境を構築したい!」といった用途に向いているかなぁという印象。

参考

Googleドキュメントを使って音声ファイルを文字起こしする小技

Google ドキュメントの音声入力機能を使い、mp3ファイルやブラウザ上でストリーミング再生した音声ファイルを文字起こしする方法(小技)を紹介します。
ドキュメント エディタ ヘルプ -Google

標準機能では、入力元の音声はマイク入力のみの対応となっていて、直接音声ファイルを入力することはできませんが、Soundflowerというフリーソフトを使い、音声ファイルをマイク入力として扱うことで音声を直接文字起こしすることができます。

必要なもの

  • Mac (SoundflowerはMac用App)
  • Soundflower
  • 入力音声ファイル or Webストリーミング音声

手順

手順はいたって簡単です。

Soundflowerのインストール

今回はGitHubからdmgファイルをダウンロードしてインストールしました。 https://github.com/mattingalls/Soundflower/releases/tag/2.0b2

f:id:daikiojm:20180207223711p:plain

サウンド設定

Soundflowerのインストールが済んだら、システム環境設定 -> サウンド を開き、「出力」と「入力」をそれぞれ Soundflower (2ch) に変更します。

出力 f:id:daikiojm:20180207223616p:plain

入力 f:id:daikiojm:20180207223638p:plain

Googleドキュメントで使用する入力デバイスを選択する

Googleドキュメントを開き、ブラウザのアドレスバーの右端に表示されるビデオマークをクリックして、入力デバイスを選択します。
(Google Chromeの場合) f:id:daikiojm:20180207224400p:plain

音声ファイルを再生する

文字起こしをしたい音声ファイルを再生します。
※ この時、出力はSoundflowerになっているため、内蔵スピーカやイヤフォンから音声は聞こえません。

f:id:daikiojm:20180207224608p:plain

Googleドキュメントで音声入力

Googleドキュメントを開き、通常の音声入力を行う手順で、マイクをOnにします。
※ ウィンドウが非アクティブになると音声入力が停止されるので注意が必要です。

f:id:daikiojm:20180207224623p:plain

参考

https://support.google.com/docs/answer/4492226?hl=ja https://qiita.com/Sasakky/items/09e3bc1536f0569fc893