尋常でないもふもふ

a software engineer blog

JavaScriptのオブジェクトリテラルの前後にスペースを含めるべきか否か

波括弧(curly brace)のオブジェクトリテラルを 1 行で書く場合の話。

const withSpace = { name: 'yamada', locale: 'JP' };  // 空白あり
const withoutSpace = {name: 'yamada', locale: 'JP'}; // 空白なし

🤔 実は規定がない

どれを見ても『オブジェクトリテラルを 1 行で書く場合』の規定がない。

ただし、いろいろなオープンソースのコードを見る限りでは、スペースありで書くケースの方が多いようだ。

🦉 スペースなしの方が一貫性がある

配列リテラルとオブジェクトリテラルを比べた場合、複数行で書く場合はこうなって同じ形式が使えるけど

const array = [
  'apple',
  'google',
  'facebook',
  'amazon',
];

const obj = {
  name: 'apple',
  ceo: 'timothy donald cook',
};

配列リテラルを単一行で書く場合は前後にスペースを含めないので、オブジェクトリテラルもこれに合わせた方が一貫性があるという考え。

const array = ['apple', 'google', 'facebook', 'amazon'];
const obj = {name: 'apple', ceo: 'timothy donald cook'};

📝他の言語の場合

🍷 Ruby

Ruby Style Guide にはハッシュリテラルJavaScriptでいうオブジェクトリテラルのこと)の書き方について記述されているが、好きな方を使えというスタイル。
Ruby 界隈でも JavaScript と同じようにスペースありで記述されているケースが多い。しかし、Ruby にはブロック構文が存在する。

array = ['apple', 'google', 'facebook', 'amazon']

array.each do |name|
  p name
end

array.each { |name|  p name }

ブロックを複数行で書く場合は do...end で記述することが推奨されるが、単一行で書く場合は波括弧が推奨される。
ブロック構文とハッシュリテラルの明確な区別のため、スペースなしの方が良いと考える。

🐍 Python

Python の場合は明確に「空白を入れるな」と規定されている。

今回のブログ記事も Python の書き方を学んだときに確かにそっちの方が一貫性あるな、と感じたので他の言語でも採用することにした。

🦋 ES6 と TypeScript の普及で使う機会が増えた

import 文で分割代入(Destructuring assignment)構文が多用されるので、単一行のオブジェクトリテラルを使うケースがかなり増えた。この機会に一貫させてスッキリさせようと思う。

import {DateUtil} from 'luxon';
import {Get, Controller, UseInterceptors} from '@nestjs/common';

🐹 Prettier でコードフォーマットする

ESLintTSLint に単一行オブジェクトリテラルに対して警告を出す機能はないが、Prettier にはコードフォーマットさせるための bracketSpacing オプションが存在する。

.prettierrc

{
  "singleQuote": true,
  "trailingComma": "all",
  "bracketSpacing": false
}
$ yarn global add prettier
$ prettier --write '**/*.ts'

DockerのHEALTHCHECKをwgetでやる

Docker には HEALTHCHECK というコンテナが正常稼働しているか確認する機能がある。
何をもって『正常』と判断するかはコンテナを稼働する人が指定する。Nginx みたいな Web サービスのコンテナの場合は公式サンプルにもある通りcurl を死活監視のためのコマンドとして利用している。

curl -f http://localhost/ || exit 1

自コンテナの Web サーバからステータスコード 200 が返れば、シェルの終了コードも 0 になるので正常と判定されるし、ステータスコード 404 とかが返れば終了コードは 1 以上となるので(curl の場合は 22 だった)exit 1 が実行されて終了コード 1 となり異常と判定される仕組み。
ヘルスチェック結果は docker ps 時に healthy なのか unhealty なのか表示される。
AWS の ECS でもこれを利用して『異常』の場合は既存のインスタンスを自動停止して新たなインスタンスに差し替えてくれたりする。

⛰ Alpine Linux の場合

wget は最初から入ってるけど curl は入ってない。別にいれればいいだけの話なんだけど、コンテナサイズを最小化することでしのぎを削ってる人達(?)からすれば耐え難いことかもしれない。大丈夫 wget でも同じことができる。

🚝 Nginx を使ったテスト

お試し用の Dockerfile はこちら。

gist.github.com

nginx:mainline-alpine は Nginx 公式の Alpine Linux でつくられた最新安定版のコンテナ。
ビルドして起動すると curl を指定しているため、コマンドが存在せず起動後に unhealthy となる。

$ docker build -t nginx-health-check .
$ docker run -it --rm nginx-health-check

起動直後

$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                            PORTS               NAMES
7661b8c061fe        nginx-health-check   "nginx -g 'daemon of…"   4 seconds ago       Up 2 seconds (health: starting)   80/tcp              eager_lichterman

起動から 8 秒後

$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                     PORTS               NAMES
7661b8c061fe        nginx-health-check   "nginx -g 'daemon of…"   10 seconds ago      Up 8 seconds (unhealthy)   80/tcp              eager_lichterman

🚀 wget に書き換える

wget -q -O - http://localhost/ || exit 1 にする。-q は標準出力を出さない指定。-O - はファイル出力先を - 指定することでなしにする。つまり標準出力にするという意味。

FROM nginx:mainline-alpine

HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=1 \
  CMD wget -q -O - http://localhost/ || exit 1

CMD ["nginx", "-g", "daemon off;"]

コンテナを改めてビルド

$ docker build -t nginx-health-check .
$ docker run -it --rm nginx-health-check

今度は healthy となる。

$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS                   PORTS               NAMES
feba790fbc78        nginx-health-check   "nginx -g 'daemon of…"   6 seconds ago       Up 5 seconds (healthy)   80/tcp              distracted_chandrasekhar

TypeScriptをはじめるとき知っておくと捗る

⚙️ tsconfig.json

tsc コマンドで変換するときのための設定ファイル。

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "allowSyntheticDefaultImports": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6",
    "sourceMap": true,
    "allowJs": true,
    "outDir": "./dist",
    "baseUrl": "./src"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.test.ts"
  ]
}

一部しか理解できてないが、WebStorm や VS Codeデバッグ実行するなら "sourceMap": true が必須。"outDir": "./dist" で js ファイルの出力先を指定している。
exclude では node_modules 配下やテストファイルを変換の対象外と指定している。

🌲 ディレクトリ構成

リポジトリを作ったときに以下のようなディレクトリ構成にするのが一般的みたい。
src 配下に ts ファイルを置き、tsc コマンドで dist 配下に js ファイルと SourceMap を出力させる。実行するときは node dist/main.js すれば良い。

├── dist
│   ├── main.js
│   └── main.js.map
├── node_modules
├── src
│   └── main.ts
├── test
│   └── main.test.ts
├── package.json
├── tsconfig.json
└── yarn.lock

dist.gitignore に追加しておきコミットしない。
サーバサイドの Node.js + TypeScript で使うときはデプロイ時にトランスパイルする手順を踏む。これも pakcage.json に書いておけばいい。

"scripts": {
  "prestart:dev": "rm -rf dist && tsc",
  "start:dev": "node dist/main.js"
}

yarn start:dev するだけで prestart を終えた後に起動してくれる。npm-scripts を参照。

型定義ファイル

npm パッケージとして @types で提供されている。
TypeScript2.0 以前は tsd とか typing という型定義ファイルの管理ツールが使われていたそうだが、現在は必要な型情報がほしければ npm install @types/node みたいな感じで個別にパッケージをインストールするだけでよくなった。
TypeScript ではデフォルトで node_modules/@types 配下の型を参照してくれる。
有名な npm パッケージで公開されているライブラリのほとんどは型定義ファイルが用意されている状況。TypeSearch というページで検索できる。

🏹 ts-node

TypeScript は拡張子 ts ファイルを tsc コマンドで js ファイルに変換して実行するものだけど、ts-node を使えばそのまま実行できる。devDependencies に加えておけば良い。

$ yarn add -D typescript ts-node

実行するときは

$ yarn run ts-node src/main.ts

あらかじめ package.json に記述しておけば yarn start するだけでよくなる。

"scripts": {
  "start": "ts-node src/main.ts"
}

でも ts-node だとデバッグ実行できないみたいなので、自分はすぐに使わなくなった。console.log デバッグが好きな人向け。
Node options:--inspect --require ts-node/register を付加すれば IDE でもデバッグ実行できる。 → ts-nodeでデバッグ実行

🎙 トランスパイル ≒ コンパイル

ts ファイルを js ファイルに変換することをトランスパイルと言う。これも広義のコンパイルになるけど、コンパイルという用語は高水準な言語を低水準な言語に変換することを言うそうだ。
TypeScript や CoffeeScriptJavaScript に変換することのように、高水準な言語を高水準な言語に変換するのはトランスパイル。
でも厳密にトランスパイルという用語を使って説明してるサイトはあまりなく、単にコンパイルと言われてることの方が多い。