TypeScriptにeslint + prettierを導入する
はじめに
毎回新しいプロジェクトを作るたびにeslintとprettierの設定方法を忘れるので、
良い加減忘れないようにまとめておく。
インストール
# eslintとeslintのplugin $ npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin # prettierとplugin $ npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier
設定ファイルの追加
.eslintrc.js
TypeScriptの対応に必要な箇所だけまとめる。
ルールはtslintとの対応表やeslint公式のルールを見ながら好みのものを入れればOK。
module.exports = { 'parser': '@typescript-eslint/parser', 'env': {'browser': true, 'node': true, 'es6': true}, 'parserOptions': { 'sourceType': 'module' }, 'plugins': ['@typescript-eslint'], 'rules': { // 好みのルール } };
.prettierrc
こちらも同じく好みのものを入れればOK。
設定項目は公式を参照。
{ "singleQuote": true, "printWidth": 120 }
まとめ
eslintとprettierの設定をまとめた。
eslintの設定とかはプロジェクトによると思うので、
詳細については都度覚えていこうと思う。
参考リンク
ReactでStorybookを導入する
はじめに
前回、AngularでStorybookを導入したので、今回はReactでやってみる。
ついでに前から気になってたstyled-omponentsも試してみる。
TL;DR.
Reactプロジェクト作成
create-react-app
を使ってサクッと作る。
$ npx create-react-app react-component --typescript
Storybook導入
Reactについても公式に手順があるので参考にしながら進める。
# create-react-appを使っている場合こっち $ npx -p @storybook/cli sb init --type react_scripts # 使わなかった場合は $ npx -p @storybook/cli sb init --type react
TypeScriptに対応させる
上記手順で導入した場合、js向けに作成されるのでtsに対応させる。
main.jsを修正する
初期状態だとstories: ['../src/**/*.stories.js']
となっているので、
stories: ['../src/**/*.stories.ts']
と修正しておく。
module.exports = { stories: ['../src/**/*.stories.ts'], addons: [ '@storybook/preset-create-react-app', '@storybook/addon-actions', '@storybook/addon-links', ], };
tsconfig.jsonの追加
ざっくり対象になるよう./storybook
配下に追加。
{ "extends": "../tsconfig.json", "compilerOptions": { "types": [ "node" ] }, "exclude": [ "../src/test.ts", "../src/**/*.test.tsx" ], "include": [ "../src/**/*" ] }
Storyを作成する
src/stories
配下にButton.stories.ts(ファイル名は任意)
を作成。
公式のサンプルとはちょっと違うが動いているので一旦これで良しとする。
※元のComponentは後述
import { ButtonComponent } from '../component/Button'; export default { title: 'Button', component: ButtonComponent }; export const ButtonDefault = () => ButtonComponent({text: 'default'}); export const ButtonSmall = () => ButtonComponent({text: 'small', size: 'small'}); export const ButtonLarge = () => ButtonComponent({text: 'default', size: 'large'}); export const ButtonPrimary = () => ButtonComponent({text: 'primary', types: 'primary'});
作成したComponent
Button.tsx
内容はAngularでやったものと同じ。
import React from 'react'; import styled, { css } from 'styled-components'; type ButtonProps = { text: string, size?: 'small' | 'large', types?: 'primary' | 'error' | 'warning' }; const Button = styled.button<Pick<ButtonProps, 'types' | 'size'>>` ${({size}) => getSize(size)} ${({types}) => getColor(types)} `; const getSize = (size: ButtonProps['size']) => { switch (size) { case 'small': return css` width: 72px; height: 30px; font-size: 10px; `; case 'large': return css` width: 300px; height: 100px; font-size: 24px; `; default: return css` width: 150px; height: 50px; font-size: 16px; `; } } const getColor = (type?: ButtonProps['types']) => { switch(type) { case 'primary': return css` background-color: #007bff; border: 1px solid #007bff; border-radius: 5px; color: #fff; `; default: return css` background-color: #fff; border: 1px solid #000; color: #333; `; } } export const ButtonComponent = (props: ButtonProps) => <Button size={props.size} types={props.types}>{props.text}</Button>;
実行してみる
今回も追加されたscriptがあるので、yarn storybook
を実行すれば良い。
画像の様にブラウザが立ち上がり、表示されればOK。
おまけ
styled-componentsの導入は下記コマンドでOK。
$ yarn install --dev styled-components
まとめ
今回はReactでStorybookを導入した。
Storybookよりもstyled-components使ってComponent作るほうが大変だった。。。
Angular,ReactとやったのでそのうちVueもやってみようかな。
参考リンク
AngularでStorybookを導入する
はじめに
storybookが良いと聞く今日このごろ。
Angularでも使えることを知ったので、今回はそれを試してみる。
TL;DR.
Angularプロジェクト作成
CLIでサクッと作成する。
$ ng new ng-component
Storybook導入
公式サイトに手順があるので、それを参考に進める。
これだけでセットアップが完了する。
$ npx -p @storybook/cli sb init --type angular
サンプルプログラムを追加してくれるが、
パッケージがなくうまく動かなかったりしたのでstoryを作っていく。
Storyを作成する
src/stories
配下にbutton.component.stories.ts(任意)
を作成する。
※ButtonComponent
の中身は後述
import { ButtonComponent } from '../app/component/button/button.component'; export default { title: 'button component', }; export const ButtonDefault = () => ({ component: ButtonComponent, props: { text: 'default' } });
作成したcomponent
button.component.ts
import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-button', templateUrl: './button.component.html', styleUrls: ['./button.component.scss'] }) export class ButtonComponent implements OnInit { @Input() text: string; buttonClasses: string[] = ['button']; ngOnInit() { } }
button.component.scss
.button { width: 150px; height: 50px; background-color: #fff; border: 1px solid #000; color: #333; font-size: 16px; }
button.component.html
<button [ngClass]="buttonClasses">{{text}}</button>
実行してみる
自動で追加されたnpm script
があるので、それを実行すればOK。
$ npm run storybook > ブラウザが勝手に立ち上がり表示される
画像の様に出てくれば完了。
まとめ
今回は前々からやろうと思ってたstorybookの導入をやった。
component単位で見れるものがあるのは、やっぱり楽だと思うので今後は作っていきたいところ。
+github pagesで見れると楽なのでいつか対応したい。
参考リンク
Firebase Functionsを定期実行する
はじめに
Firebase Functionsで定期実行をできることを知ったので今回はそれを試してみる。
TL;DR.
試したソースコード
(既存のプロジェクトでやったため、最小構成ではない)
Firebase(+ GCP)の設定
プロジェクト作成
WebUIからポチポチ設定していく。
名前決めるくらいなのでサクッとすすめる。
アナリティクスはいらなかったので無効にする。
プロジェクト作成できたら左上の歯車からSetting画面を開く。
定期実行する場合Google Cloud Platform(GCP)リソース ロケーション
を設定する必要があるので、
好きなロケーションを選択する。(今回はasia-northeast1
(東京)にしておく)
作成したプロジェクトが無料プラン(Spark)だった場合、従量課金制のBlazeにしておく。
(定期実行するのに必要)
GCPの設定
Cloud Scheduler API
とCloud Pub/Sub API
を有効にする必要がある。
APIライブラリ
から有効にしておく。
Firebase CLIの設定
# firebaseにログイン $ firebase login # firebase functions 初期設定 $ firebase init functions > 色々聞かれるので好みの設定をする > .firebasercとfirebase.json、functionsディレクトリが作成されていればOK
デプロイするフォルダを変更する
デフォルトの設定では作成されたfunctions
ディレクトリにあるpackage.json
を参照してデプロイされる。
このpackage.json
で依存とか解決しているらしい。
※初期設定だとfunctions/lib/index.js
がデプロイ先になる。
ただ、今回はすでにあるプロジェクトをデプロイしたかったので、そのへんを変更していく。
(+階層が1段ずれるのもデプロイのためだけに変更するのも嫌だったので)
変更するにはfirebase.json
に"source": "."
を追加すれば良い。
(今回はカレントディレクトリにしたかったため.
)
合わせてpredeploy
がfunctions
配下のpackage.json
を見るようprefixが付いていたので消しておく。
{ "functions": { "source": ".", "predeploy": "npm run build" } }
定期実行するコードを書いていく
公式のサンプルを参考にしながら書いていく。
export const sampleJob = functions // regionはGCPリソース ロケーションと合わせる .region('asia-northeast1') // 時間設定。よくあるcronの書き方もできる .pubsub.schedule('every day 07:00') // 時間指定しているので、一応タイムゾーンを設定しておく .timeZone('Asia/Tokyo') .onRun(context => { // 処理 }
webpackの設定
webpackでバンドルした結果をデプロイしたかったので設定していく。
多分firebase用の設定はあまりないはず。
const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { mode: 'production', entry: path.join(__dirname, '/app.ts'), target: 'node', module: { rules: [ { test: /\.ts$/, use: [ {loader: 'ts-loader'}, {loader: 'eslint-loader'} ], exclude: /node_modules/ }, { test: /\.node$/, use: 'node-loader' } ] }, resolve: { extensions: ['.js', '.json', '.ts'], modules: [path.join(__dirname, 'src'), 'node_modules'] }, output: { // これを設定しないとうまくデプロイされなかった libraryTarget: 'this', path: path.join(__dirname, 'dist'), filename: 'app.js' }, externals: [nodeExternals()] };
環境変数を設定する
パスワードなどコード上に書きたくないものを環境変数に設定していく。
公式のサンプルを真似てすすめる。
# 環境変数にslackのwebhookURLを設定 $ firebase functions:config:set slack.url=https://hooks.slack.com/services/XXX
取り出すには下記のようにする。
import { config } from 'firebase-functions'; const slackUrl = config().slack.url;
デプロイする
firebase deploy —only functions
でOK。
ただし、deploy complete!
とか言いながら失敗することがあるので、
Webコンソールを確認してデプロイが完了してることを確認した方が良いかも。
まとめ
Firebase Functionsで定期実行する方法をまとめた。
今まで定期実行したい処理がある時はLambdaを使ってたけど、今後はfirebaseで良さそう。
(個人的にGCPに集約したいだけ。)
参考リンク
IntelliJでファイル保存時にactive fileにのみPrettierをかける
はじめに
一身上の都合により、ファイル保存時にアクティブファイルにのみPrettierをかけることになった。
調べればすぐ出てくるかなと思ったけど、意外と出てこなかったのでメモを残す。
TL;DR.
準備
File Watchers
とPrettier
のプラグインをインストールしておく。
File Watchersの設定
Prettierを叩く設定を行う。
Preference > Tools > File Watchers
を開く。
左下の+
を押すとTemplateを選べるので、Prettier
を選択する。
出てきたポップアップのFiles to Watch > File Type
で好みのファイルを選ぶ。
(今回はTypeScriptを指定。)
Tool to Run on Changes > Arugments
の—write
の後ろを$FilePathRelativeToProjectRoot$
に変更する。
(INSERT MACRO...
を選択し、FilePathRelativeToProjectRoot
を選択でもOK)
これで設定は完了。
まとめ
保存するたびに全部変えても問題ないと思うけど、
逐一全部変わるのもっていうのと触ってないファイルまで変わるのが嫌だったので、
今回はアクティブファイルに絞った。
(VSCodeのPrettierと同じ挙動だと思う)
調べてもぱっと出てこなくてちょっとハマったけど、
こういうことやる人そこまで多くないんだろうか。。。
それでは、今回はこの辺で。
Github Actionsを使ってfirebaseへデプロイする
はじめに
最近Github Actionsをよく聞くので使ってみたい今日このごろ。
ちょうど最近自分のポートフォリオ?のようなマイページのサイトを作っていたので、
これをfirebaseにデプロイするようにGithub Actionsを設定してみる。
TL;DR.
Github Actions準備
firebaseに上げる前にGithub Actionsの準備を行う。
始めるにはWebからやるか、自分で作りたければ.github/workflow/xxx.yml
を用意する。
Webからやる場合
Code
,Pull requests
が並ぶところにActions
があるのでそこを開く。
テンプレートがたくさんあるので、ここから選べばよしなにymlファイルを作成してくれる。
今回は自分で作って見たかったので、こちらの手順は割愛。
自分で作る場合
自力でテンプレにあるようなymlファイルを書けば良い。
構文は公式のドキュメントがあるので、それを見ながら書いていく。
# ワークフローの名前 # 書かないとデフォルト値(リポジトリのルートに対するワークフローファイルの相対パス)になる name: inform-weather-confirm-flow # ワークフローをトリガーするGithubイベントの名前 on: # 1個の場合そのまま書く # 2個以上の場合、リスト形式で書く→[push, pull_request] pull_request: # ブランチを指定すれば絞ることができる # ワイルドカード、!が使える branches: # マスター以外 - '!master' # ワークフローで実行するジョブを書いていく jobs: # ジョブ名 deploy: # ジョブを実行する環境 # 環境については公式ドキュメントを参照 runs-on: ubuntu-latest # ジョブに紐づくタスク steps: # アクションを実行する # アクション = 再利用可能なコードの単位 - uses: actions/checkout@v1 # nameはstepの名前 - name: setup uses: actions/setup-node@v1 with: node-version: 12 # パッケージインストール - name: install run: npm install # lint実行 - name: lint run: npm run lint:format # テスト実行 - name: test run: npm test
firebaseへのデプロイ
プロジェクト作るところは割愛。
GUIでポチポチすればよかったはず。
# ciで使うトークンを取得 $ firebase login:ci > browserでログインを求められるのでログイン > Tokenが出てくるのでそれをコピーしておく # コマンドは下記の通り $ firebase deploy --token=とったトークン
これをymlに書けば良い。
今回はnpm script
に下記の通り追加してこのコマンドを使う。
"script": { "deploy": "firebase deploy" }
# deployするsample workflow name: deploy-sample on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: setup uses: actions/setup-node@v1 with: node-version: 12 - name: install run: npm install - name: build run: npm run build - name: deploy # tokenはsecretsに追加し、それを参照する run: npm run deploy -- --token=${{secrets.token}}
おまけ
特定のジョブが終わっていることを条件にしたい場合
testが通った後にデプロイをしたい場合などで割とよくあるパターンだと思う。
実行したいジョブにneeds
で事前に完了していてほしいジョブを指定する。
例は下記の通り。
jobs: job1: job2: # job1が完了していること needs: job1 job3: # job1とjob2が完了していること needs: [job1, job2]
パスワード/Tokenなどを設定したい場合
デプロイする時にパスワードなりtokenなり他の人に見せたくないけど、
ci上で使いたい!っていうものが多々ある。
circleciとか他のci/cdサービスでこのようなことは普通にできると思うが、
Github Actionsでももちろん設定できる。
登録するにはWebUIからポチポチしていけばできる。
Settings > Secrets > Add a new secret
から追加できる。
使う時は下記のようにすればOK。
- name: deploy # ${{secrets.XXX}}で取得できる run: npm run deploy -- --token=${{secrets.TOKEN}}
Slackへの通知
デプロイ結果やビルドエラーになった場合など、
どこかに通知したいこともあると思う。
今回は個人的によく使うので、Slackに通知することにする。
既にSlackに通知をするActionがあるので、これを使うことにする。
webhookのトークンが必要なので予め取得しておく。
設定項目は公式にも書いてあるが下記の通り。
... - name: slack-notice-success uses: rtCamp/action-slack-notify@v2.0.0 env: SLACK_CHANNEL: 'チャンネル名' SLACK_COLOR: '#3278BD' SLACK_ICON: 'icon url' SLACK_MESSAGE: 'メッセージ' SLACK_TITLE: 'タイトル' SLACK_USERNAME: '名前' SLACK_WEBHOOK: ${{ secrets.SLACK_TOKEN }} ...
画像みたいな通知が飛んでくればOK。
まとめ
今回はGithub Actionsでfirebaseにデプロイするワークフローを作成した。
前まではCircleCIを使っていたけど、
Githubで完結するのがだいぶ楽なのでとても良かった。
いずれもっと複雑なものを書いていこうと思う。
それでは今回はこの辺で。
参考サイト
TypeScriptのDecoratorまとめ
はじめに
Decorator使ってみることになったが何もわからないのでまとめる。
TL;DR.
準備
デフォルトだと使えないので、下記の通りtsconfig
を修正する必要がある。
おそらくtsc —init
の結果に"experimentalDecorators": true
の追加で問題ないはず。
(cliの場合同様のオプションを指定すればOK)
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
使い方
@hoge
の形式でclass
, method
, accessor
, property
, parameter
につけられる。
ただし、hoge
はdecoratorを付けた場所の情報と共に実行時呼び出される関数である必要がある。
+型定義とかdeclare class
にはつけられない。
何言っているか自分でもわからないので、使い方はサンプルを添えてまとめる。
Class Decorators
Class Decoratorはクラスのコンストラクターに適用され、
クラス定義の監視、変更、置換のために使用できる。
使うときはclassの前に付ければ良い。
// classDecoratorは引数に付けたクラスのconstrouctorを受け取る function classDecorator(constructor: Function) { } @classDecorator class SampleClass { }
使い方は下記の様にする。
下記の例ではconstructor
とそのprototype
にObject.seal
をかけている。
function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } }
Class Decoratorが値を返却する時は、その値でconstructorの内容を上書きできる。
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) { return class extends constructor { newProperty = "new property"; hello = "override"; } } @classDecorator class Greeter { property = "property"; hello: string; constructor(m: string) { this.hello = m; } } console.log(new Greeter("world"));
出力結果は下記の通り。
constructorの結果が上書きされていることがわかる。
class_1 { property: 'property', hello: 'override', newProperty: 'new property' }
Method Decorators
method decoratorはメソッドに適用でき、
メソッド定義の観察、変更、または置換に使用できる。
使う時はmethodの前に付ければ良い。
/** * 下記のpropertyを受け取る * @param target decoratorを付けたmethodのclassのprototype * @param propertyKey decoratorを付けたmethodの名前 * @param descriptor methodのproperty descriptor ※targetがes5未満だとundefinedになる */ function methodDecoratorSample(target: any, propertyKey: string, descriptor: PropertyDescriptor) { } class SampleClass { @methodDecoratorSample greet(message: string) { return `Hello! ${message}`; } }
使い方は下記の通り。
descriptor.value
の結果を書き換えることで、メソッドの戻り値を変更できる。
arguments
を使うことでdecorator付けたメソッドの引数を取れる。
function greetDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value = function () { return `こんにちは。${arguments[0]}`; }; } class MethodDecoratorGreeter { @greetDecorator public greet(name: string) { return `Hi! ${name}`; } } console.log(new MethodDecoratorGreeter().greet('Tom')); // こんにちは。Tom
元のメソッドを実行したい場合は、Reflect
を使うことでできる。
また、decoratorで引数を受け取りたい場合は、
必要な引数3つを受け取る関数を返す関数を作ることでできる。
function reflectDecorator(type: 'original' | 'change') { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { // 後で実行したいので退避しておく const originalMethod = descriptor.value; switch(type) { case 'original': descriptor.value = function() { return Reflect.apply(originalMethod, this, arguments); } break; case 'change': descriptor.value = function() { return '変更した'; } } } } class ReflectSample { @reflectDecorator('original') noCange() { return '元のメソッド'; } @reflectDecorator('change') change() { return '元のメソッド'; } } const reflectSample = new ReflectSample(); console.log(`noChange: ${reflectSample.noCange()}`); // 元のメソッド console.log(`change : ${reflectSample.change()}`); // 変更した
Accessor Decorators
method decoratorのaccessor版。
ほぼ同じなので割愛。
Property Decorators
今までのDecoratorとは違い、あまりできることが多くないかも?
使う時はpropertyの前に付ければ良い。
下記のようにPropertyDescriptor
を指定することで、
propertyの内容を変更できる。
ただし、decoratorの戻り値はvoid
or any
なので実質型は消え去る。
(ここだけなのでこだわる必要もあまりないと思うけど)
/** * 下記の引数を受け取る * @param target decoratorを付けたpropertyのclassのprototype * @param member プロパティ名 */ function propertyDecoratorSample(target: any, member: string): any { const propertyDescriptor: PropertyDescriptor = { configurable: false, enumerable: false, value: 'huga', writable: true } return propertyDescriptor; } class SampleClassProperty { @propertyDecoratorSample private name?: string; constructor(name: string) { // 上書きする前は、decoratorの戻り値で指定した内容になっている this.name = name; } } console.log(new SampleClassProperty('hoge'));// SampleClassProperty { name: 'hoge' }
Parameter Decorators
ParameterDecoratorsは、メソッドでパラメータが宣言されたことを確認するためにのみ使用できる。
ParameterDecoratorsの戻り値は無視される。
使う時はparameterの前に付ければ良い。
import 'reflect-metadata'; const metaDataKey = Symbol('sample'); /** * 下記のpropertyを受け取る * @param target decoratorを付けたclassのprototype * @param member memberの名前 * @param parameterIndex 関数のパラメーターリスト内のパラメーターのインデックス */ function parameterDecoratorSample(target: any, member: string, parameterIndex: number) { const parameters = Reflect.getOwnMetadata(metaDataKey, target, member) || []; parameters.push(parameterIndex); // metadata付与 Reflect.defineMetadata(metaDataKey, parameters, target, member); } function methodDecorator(target: any, propKey: string, desc: PropertyDescriptor) { const method = desc.value; desc.value = function() { // 付与したメタデータ取得 const parameters = Reflect.getMetadata(metaDataKey, target, propKey); // パラメータチェック if (parameters) { for (const parameterIndex of parameters) { if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined ) { throw new Error('Missing required argument!'); } } } // 問題なければ元のメソッドを実行する return method.apply(this, arguments); } } class SampleClassParameter { @methodDecorator greet(@parameterDecoratorSample name: string) { return `Hello! ${name}`; } } console.log(new SampleClassParameter().greet('Tom'));
まとめ
TypeScriptのDecoratorsについてまとめた。
思ったより便利ではあったが、
一部Reflect
を使わないと行けないところもあり、 複雑なところもある印象。
ただ、便利なので使える場所では使っていきたいところ。
ちなみに、DecoratorはECMAScriptでもstage2なので、いずれJSにも来るかもしれない。
それでは今回はこの辺で。