bff

フルスタックエンジニアを目指して学んだことなどを記録しています

Typescriptの利用

NuxtでTypescriptを利用しようとして調べたところ、2019/01/28のv2.4.0で公式対応された手順での利用方法があまりまとまっていなかったので、簡単にまとめてみました。 https://ja.nuxtjs.org/guide/typescript にアクセスしたら404エラー!ということで簡単に手順をまとめました。(2019/04/18時点。そのうちこちらも404エラーではなくなるでしょう。)

まず、Typescriptを利用できるようにライブラリを追加します。npm/yarnの環境にあわせて実行してください。

npm i -D @nuxt/typescript
npm i ts-node
# OR
yarn add -D @nuxt/typescript
yarn add ts-node

nuxt/typescriptはコンパイルや型チェックを別プロセスで実行するために必要です。 ts-nodeは、nuxt.config.tsとserverMiddlewaresにランタイム時のTypescriptのサポートをするために必要です。

また、空のtsconfig.jsonファイルがプロジェクトのルートディレクトリに必要です。この空のファイルは最初にnuxtコマンドを実行した時に自動でデフォルト値によってアップデートされます。

touch tsconfig.json
yarn dev
> yarn run v1.13.0
> $ nuxt --port 3333
> 08:53:55 ℹ Generating /tsconfig.json <-- ここで生成されています。

typescriptを利用する際には、vue-property-decoratorとvue-class-componentの利用が推奨されています。 tsconfig.jsonの初期値にも、"experimentalDecorators": trueが入っていますね。

また、ESLintを使いたい場合は、追加でプラグインの導入が必要です。

npm install -D @typescript-eslint/eslint-plugin
# OR
yarn add -D @typescript-eslint/eslint-plugin

その後、.eslintrc.jsへの設定追加とlintコマンドのセットアップを行います。 .eslintrc.jsに以下の設定を追加する必要があります。plugins/parserOptions.parser/extends/rulesのキーはすでに存在すると思うので、コピペではなくそれぞれ手入力で追加してください。

module.exports = {
  plugins: ['@typescript-eslint'],
  parserOptions: {
    parser: '@typescript-eslint/parser'
  },
  extends: [
    '@nuxtjs'
  ],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error'
  }
}

lintコマンドのセットアップはpackage.jsonのscriptsに以下を追加してください。ここでは.gitignoreを追加することでnode_modulesや.nuxtがlintの対象から外れるようにしています。

"lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."

これでlintをかけるときはnpm run lint/yarn lintで実行できるようになりました。

apt-get時の不要ファイル削除

Dockerイメージを作成する際には不要ファイルを削除しましょう。 apt-getでパッケージを入れる際は、以下のように/var/lib/apt/lists/の中を削除することでイメージサイズの削減ができます。

apt-get update && apt-get install -y --no-install-recommends xxxx \
    && rm -rf /var/lib/apt/lists/*

debianにてbuildtoolをインストールした例ですと、改善前(no-cleanup)と改善後(cleanup)で16MBほどの差が出ました。

docker images | grep fed925
no-cleanup                                 latest              fed925c7f836        About a minute ago   380MB
docker images | grep 9811
cleanup                                    latest              981137e40665        38 seconds ago      364MB

Dockerで始めるFastAPI

最近話題になっているPythonのWebフレームワークであるFastAPIを少し試してみたいと思い、Dockerイメージを作成しました。

Dockerhubには登録していないので、こちらから利用してください。 https://github.com/tsbkw/fastapi

FastAPIとは

NodeJSやGoと同等のパフォーマンスを出すことができる高速なWebフレームワークです。

FastAPIは、標準のPythonタイプヒントに基づいてPython 3.6+でAPIを構築するための最新の高速(高性能)Webフレームワークです。 * Fast:NodeJSやGoと同等の非常に高いパフォーマンス(StarletteとPydanticのおかげで)。利用可能な最速のPythonフレームワークの1つ。 詳しくは(公式サイト)https://fastapi.tiangolo.comをご覧ください。

イメージファイルの中身

以下二つのライブラリを利用しています。   uvicorn: (http://www.uvicorn.org/)http://www.uvicorn.org/   gunicorn: (http://www.uvicorn.org/)http://www.uvicorn.org/

fastapiを利用するにはASGI(Asynchronous Server Gateway Interface、WSGIの後継。詳しくは(こちら)http://asgi.readthedocs.io)サーバが必要ということで、公式で例示されているuvicornを使っています。 また、uvicornのサーバおよびプロセス管理をするツールとして公式サイト上でgunicornが勧められていたのでこちらも合わせて導入しています。

OSは、当初alpineを考えていましたが、libcの実装の違いから速度が遅いようなので、debianを採用しています。 参考: https://superuser.com/questions/1219609/why-is-the-alpine-docker-image-over-50-slower-than-the-ubuntu-image  * タイトルではubuntuと書かれていますが、比較に使われているpython:3-slimのベースイメージは(debianになっていました)https://github.com/docker-library/python/blob/ce69fc6369feb8ec757b019035ddad7bac20562c/3.7/stretch/slim/Dockerfile

vue-cliでローカル実行する際のポート番号変更方法

開発中はvue-cliでローカル実行しながらデバッグを行うかと思います。 vue-cliはデフォルトで8080のポートを利用していますが、他の用途でこの番号を使っている場合は別の番号を指定する必要があります。

デフォルトの挙動

$ yarn serve # yarnを利用していない場合は vue serveで実行
  App running at:
  - Local:   http://localhost:8080/
  - Network: http://xxx.xxx.xxx.xxx:8080/

対応方法

プロジェクトのルートディレクト1にvue.config.jsを作成し、以下のように記載します。ここでは8089を指定していますが、好きな値に変更してください。

module.exports = {
  devServer : {
    port: 8089
  }
}

すでにvue.config.jsがある場合は、devServer: { port:8089 }の部分を追記してください。

これでポート番号が変更されます。

$ yarn serve # yarnを利用していない場合は vue serveで実行
  App running at:
  - Local:   http://localhost:8089/
  - Network: http://192.168.162.51:8089/

#### 備考
vue-cli 3からはvue.config.jsを参照するように変更されました。
古い記事ですとconfig/index.jsを作成する例がありますが、設定方法が変更されているので注意しましょう。(stackoverflow)[https://stackoverflow.com/questions/47219819/how-to-change-port-number-in-vue-cli-project]でも回答が古く、コメント欄のみでvue.config.jsを使った方法が言及されていました。
その他、設定ファイル内に記載できる値は(公式サイト)[https://cli.vuejs.org/config/]をご確認ください。

  1. package.jsonやsrcフォルダなどがあるディレクトリです。

「クラス・ファイルのバージョンXX.Xは不正です」の対処方法

WebAuthnを勉強しようと思って、()[]を動かそうとしていたところ、Javaのクラスファイルのバージョンが不正というエラーが出てしまいました。 解決までにすこし手間取ったので、簡単にまとめておきます。

ひとことで言うと

JDKのバージョンがMavenで指定されているものと違ったためエラーがでました。 適切なJDKのバージョン(今回は1.8)をインストールし、環境変数JAVA_HOMEのパスを1.8に向けることで解消できます。 なお、Javaのバージョン表記はややこしいので(java8まではjava --versionで見ると1.8と表示される)、以下ではjava8という表記で統一しています。

発生したエラー

[ERROR] COMPILATION ERROR : 

[INFO] -------------------------------------------------------------

[ERROR] /Users/foobar/local/webauthndemo/src/main/java/com/google/webauthn/gaedemo/objects/PublicKeyCredentialEntity.java:[19,8] java.lang.Objectにアクセスできません
  クラス・ファイル/modules/java.base/java/lang/Object.classは不正です
    クラス・ファイルのバージョン55.0は不正です。53.0である必要があります
    削除するか、クラスパスの正しいサブディレクトリにあるかを確認してください。

[INFO] 1 error

[INFO] -------------------------------------------------------------

[INFO] ------------------------------------------------------------------------

[INFO] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Total time:  4.872 s

[INFO] Finished at: 2019-02-20T08:26:46+09:00

[INFO] ------------------------------------------------------------------------

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile(default-compile) on project gaedemo: Compilation failure

[ERROR] /Users/foobar/local/webauthndemo/src/main/java/com/google/webauthn/gaedemo/objects/PublicKeyCredentialEntity.java:[19,8] java.lang.Objectにアクセスできません

[ERROR]   クラス・ファイル/modules/java.base/java/lang/Object.classは不正です

[ERROR]     クラス・ファイルのバージョン55.0は不正です。53.0である必要があります

[ERROR]     削除するか、クラスパスの正しいサブディレクトリにあるかを確認してください。

前提

OS: macos インストール済みのJavaのバージョン: 11, 8(所用があってもともと2バージョンいれていました。以下のような構成です。)

$ ls /Library/Java/JavaVirtualMachines/

jdk-11.0.2.jdk   jdk1.8.0_201.jdk

原因

WebAuthnDemoのpom.xmlを見ると以下のような記載があり、java8でコンパイルするようになっています。

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

一方で、Javaのバージョンを確認すると11となっていました。

$ javac --version

javac 11.0.2

対応方法

Java11が利用されてしまっていたので、Java8に向き先を変えます。 環境変数JAVA_HOMEの設定によるものなので、以下のように向き先を変更しております。

一時的にJava8に向き先変更

$ export JAVA_HOME=`/usr/libexec/java_home -v "1.8"`
$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
$ PATH=$JAVA_HOME/bin:$PATH

なお、すでにJAVA_HOMEが設定されている場合は、最後の1行は不要です。 今回は一時的にビルドするために必要だったので上記コマンドにしていますが、常にJava8を利用したい場合は、以下のコマンドを実行するか~/.bash_profileにJAVA_HOMEとPATHを追記してください。

永続的にJava8に向き先変更

$ echo "JAVA_HOME=`/usr/libexec/java_home -v "1.8"`
PATH=$JAVA_HOME/bin:$PATH" >> ~/.bash_profile

gitで必要なcommitだけをpushする方法

githubでプルリクを作ろうとしたときに、不要なcommitが含まれてしまったときの対処法です。 不要なcommitを含む操作前の状態に戻し、必要なcommitだけをチェリーピックして、プッシュします。

1.origin/masterとローカルのHEADとの差を確認(ハッシュ値は適当な長さで切っています)

$ git cherry -v origin/master
+ 7c1092149 Feature/AAAA
+ ebf2b02aa Feature/BBB
+ 909d5dada Feature/CCC
+ 46739a0fc Feature/DDD <-- 今回必要なcommit

2.今回のコミットの一つ前の状態を確認する 

$ git log --oneline
(HEAD -> feature/SAFIEP-991_ecmail_ship_in_3days)
46739a0fc (HEAD -> feature/mybranch) Feature/DDD <-- 今回必要なcommit
909d5dada Feature/CCC
ebf2b02aa Feature/BBB
7c1092149 Feature/AAAA
8eb9csa01 Previous state <-- この状態に戻したい。

3.2.で確認した状態に戻す(この例では、8eb9csa01 Previous state)

$ git reset --hard 8eb9csa01

4.必要なコミットだけを取り込む(この例では、46739a0fc Feature/DDD)

$ git cherry-pick 46739a0fc

5.リモートにプッシュする(HEADはbranch名でも構いません)

$ git push origin HEAD

これで必要なcommitだけを取り込むことができました。 不安な方は、5.の前に以下のコマンドで今回のcommitだけが差分であることを確認できます。

$ git cherry -v origin/master
+ 46739a0fc Feature/DDD <-- 今回必要なcommit

また、5. の結果うまくいかなかったという場合は、以下のコマンドでプッシュを取り消せます。

$ git push --delete origin <branch名>

JavaScriptでのPromiseの概要、使い方と利点

JavaScriptでのPromiseの概要、使い方と利点を簡単にまとめてみました。

概要

Promiseとは非同期で実行するコールバック関数を登録するためのオブジェクトです。
Promiseを返す関数は、非同期で実行する処理を行ったあと、登録されたコールバック関数を呼び出してくれます。

使い方

Promiseと従来のコールバックを使った実装をそれぞれ書きました。開発者ツールなどで実行できるので試してみてください。

let isCallA = true;  // Bを実行する場合はfalseにしてください
// 従来のコールバック関数の渡し方
oldWay(funcA, funcB);

// Promiseを利用した渡し方
const promise = newWay();
promise.then(funcA, funcB);

// 以下、実行に必要な関数
function funcA() {
  console.log('a');
}

function funcB() {
  console.log('b');
  return "return of B"; // あとで使います
}


function oldWay(a, b) {
  if (isCallA) {
  setTimeout(a, 1000);
  } else {
setTimeout(b, 1000);
  }
}

function newWay() {
  return new Promise(function (resolve, reject){
    if (isCallA) {
    return setTimeout(resolve);
    } else {
        throw new Error();
    }
  });
}

利点

非同期処理のチェーン(順次実行)をわかりやすく書くことができます。 また、エラー時の処理を.catch()で完結に書くことができます。

newWay()
.then(funcA)
.then(funcB)
.then(finalResult => console.log('final result: ' + finalResult))
.catch(failureCallback);

function failureCallback() {
  console.log('failed');
}

また、Promiseに登録処理を行っているイベントループ中はthenで登録された非同期処理が実行されることがないため、実行順序を把握しやすいです。 例えば、以下の処理は必ず'a'が表示されてから'b'が表示されます。

// Promise.resolve()はresolveされたPromiseを作る関数です。
Promise.resolve().then(_ => console.log('b')); 
console.log('a');