echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【VSCode】VS Code Meetup #19 - フロントエンド開発Nightで登壇しました(トラブル編)

はじめに

前回、「『VS Code Meetup #19 - フロントエンド開発Night』というイベントで『Remote-Containersでnext.js環境を作った話』という内容で登壇」した話を書きました。

前回は登壇内容の補足(Remote-Containersについて&どのようなツールを入れたか)がメインでしたが、今回はトラブル編ということで「環境を構築する際に困ったこと」について書きたいと思います。

内容

  • ts-jestで「Cannot use import statement outside a module」エラーが出る
  • emotionでエラーが出る

ts-jestで「Cannot use import statement outside a module」エラーが出る

現象

TypeScript→JavaScript変換時たまに発生する、ES Module非対応のソースでimport xxx from yyyを行うと発生するエラーです。

対策方法として割とよく知られているのは以下の方法だと思いますが、どれもうまくいかず、困っていました。

  • 拡張子を.mts(.mjs)にする
    • 関連するnode_modulesパッケージもすべて拡張子変更する必要があり、さすがに無理。
    • 自作のものならまだ何とかなるけど...
  • package.json"type":"module"定義を付与する
    • これも上と同様node_modulesパッケージもすべて拡張子変更する必要があり、無理がある。
    • 一応プロジェクト直下のpackage.jsonには定義したが、現象変わらず
  • babelを使う
    • これが一番良い方法かも
    • ただ、色々情報調べて試したが、自分の環境ではうまくいかなかった
解決方法

...と思っていたんですが、ts-jestっていつの間にかES Module対応されていたんですね。(公式Doc読めよ...)

ESM Support | ts-jest

という訳で、下記を行えばOKです。

  • 設定ファイルに以下を追加する
    • ts-jestに対して"useESM": "true"の設定を追加する
    • extensionsToTreatAsEsmに*.tsを追加する

※公式ドキュメントでは「tsconfigのmoduleもES Moduleの値(es2020など)かどうかを確認」と書いてあるが、これはpresetがCommonJSに変換する形式なら不要?

Presets | ts-jest

{
    ...(jestのほかの設定), 
    
    "preset": "ts-jest",
     "globals": {
      "ts-jest": {
        "tsConfig": "tsconfig.json",
        "useESM": true  // これを追加
      }
    },
    // これも追加
    "extensionsToTreatAsEsm": [
      ".ts",
      ".tsx"
    ]
  }

emotionで「インターフェイス 'JSX.IntrinsicElements' が存在しないため、暗黙的に JSX 要素の型は 'any' になります。」というエラーが出る

emotionの公式サイトに従って、下記のようにコードを書きますが、なぜかcssの箇所で「インターフェイス 'JSX.IntrinsicElements' が存在しないため、暗黙的に JSX 要素の型は 'any' になります。」というエラーが発生する現象です。(@emotion/react@emotion/styledはインストール済み)

import { css } from '@emotion/react'  
  
const myCss = css({
  margin: '10px;'
})  
  
export const ComponentDruaga: VFC<ComponentDruagaProps> = ({treasures}) => {
    const {state, changeState}: UseMyStateType = useMyState();

    return (  
       // なぜかここで上記エラーが発生する
      <div css={myCss}>
        <div>{`stateは${state}です。`}</div>
        <button onClick={changeState}>stateを変える</button>
      </div>
    )
}

公式サイトのIntroductionに特に記載があるわけでもないので、最初は分かりませんでした...

解決方法

解決方法ですが、公式サイトのここにあるように@emotion/babel-pluginを導入すれば解決できました。
※公式サイトでもこの@emotion/babel-pluginは強く推奨とあるのですが「version8以上では必須ではない」と書いてあったため、インストールしてませんでした。

@emotion/babel-pluginをインストール後、.babelrcファイルに書きを定義すればOKです。

{
    "presets": [
      [
        "next/babel",
        {
          "preset-react": {
            "runtime": "automatic",
            "importSource": "@emotion/react"
          }
        }
      ]
    ],
    "plugins": ["@emotion/babel-plugin"]
}

ちなみに、下記サイトにめちゃくちゃ分かりやすいemotion+TypeScript環境の作成のやり方が書いてあるので、おススメです。(自分もここを参照しました)

【Next.js & TypeScript】Emotionの導入が大変だったので手順をまとめておく

まとめ

以上、「環境を構築する際に困ったこと」でした。
色々有益な情報が各種ブログなどにあったので、助かりました。

あと教訓として、やはり「公式サイトはまず最初に見ろ!」ですね。はい。(自戒も込めて)

それでは、今回はこの辺で。

【VSCode】VS Code Meetup #19 - フロントエンド開発Nightで登壇しました&内容補足

はじめに

お久しぶりです。

もう1か月以上前になってしまったのですが、2022/4/11(月)に行われた「VS Code Meetup #19 - フロントエンド開発Night」というイベントで、「Remote-Containersでnext.js環境を作った話」という内容で登壇しました。

VS Code Meetup #19 - フロントエンド開発Night - connpass

内容はタイトル通り、VS Code拡張機能であるRemote-Containersを使ってNext.jsの開発環境のベースを構築した話なのですが、質問を受けた&説明しきれなかった部分について、ここで補足しようと思います。

なお登壇資料はこちらです。

www.slideshare.net

Remote-Containerって何?

Remote-Containerとは、「あるコンテナ環境内でVS Codeを開き、開発やテストを可能にする」拡張機能です。
コンテナの中に入る場合、docker exec -it などを使うと思いますが、そういう操作を一切せずに、VS Codeからわずかな操作だけで簡単にコンテナ内部に入ったり、コンテナ内でVS Codeで開発やテストを実行することもできます。

また、コンテナ環境を簡単に作るための情報(ベースのimage, 各種設定など)も用意されているので、コンテナベースでの環境構築・開発を行う際に大変便利です。(ただし、最低限のDockerの知識は必要になります)

ちなみに登壇時に「離れた場所にあるコンテナに入る拡張機能なのか?」という質問を頂いたのですが、Remote Containerはあくまでもローカル(=VSCodeで開いている環境)をコンテナで開く&コンテナに入るための拡張機能です。(離れた場所にあるコンテナに入る場合は、Remote-SSHを用います)

devcontainer.jsonについて

登壇資料内「定義ファイル(devcontainer.json)からコンテナ環境を作成できる」と記載していますが、そのdevcontainer.json についての補足です。

devcontainer.json は、開いている環境をRemote-Containerからコンテナとして開くための設定で、コンテナ情報+αを記載します。
登壇資料では主に1から作成する方法を記載しましたが、docker-compose.ymlDockerfileがあれば、そこから生成することもできます。

コマンドパレットから「Remote-Containers: Add Development Conatiners...」や「Open in Container」などの「コンテナを開く」系のアイテムを選択すると以下のようなメニューが表示されるので、そこから「From (ファイル名)」を選択すればOKです。

ちなみに、Remote-Containerで動かす場合、下記の設定が重要になります。

項目 説明 備考
settings コンテナ生成時に、settings.jsonにある項目についてのデフォルトの設定を行う
extensions コンテナ内で使用する拡張機能を設定する 拡張機能のIDを配列で指定する(※1)
features コンテナ内で使用するツールなどの設定を行う devcontainer.jsonを対話形式で作成した際に追加したツール(登壇資料の9スライド目を参照) (※2)

※1:拡張機能の一覧で右クリックすると、IDをコピー出来ます。また直接devcontainer.jsonのextensionsに追加することもできます。
※2:自分で定義する場合、いくつかの条件に沿う必要がありそう。下記記事に詳しく記載されています。

参考:Dev container featuresについて調べてみる

何のツールをインストールしたのか

「Remote-Containersでnext.js環境を作った話」というタイトルなのに、結局はnpm(yarn) installでの環境構築がメインになってしまったわけですが、参考までにどのツールを入れたかを記載しておきます。(詳細説明は割愛します)

  • create-next-app, typescript
    • このあたりは必須かなと思います。
  • ts-jest, @testing-library/react, @testing-library/react-hooks
    • テスト用のツール群です。(あと、storybookも入れておけばよかったかも)
  • emotion
    • CSS in JSのライブラリ
  • recoil
    • Reactでステート管理を柔軟に行うためのツール。(正式リリース版ではないみたいだが、個人的に使いやすい)
  • SWR
    • HTTP REF 5861で提案された「stale-while-revalidate(再検証中は古いものを)」に基づき、APIのデータを扱うライブラリ

emotion, recoil, swrは単に私が仕事で使ってて、使いやすかったからという理由で入れただけですが...

まとめ

以上、「VS Code Meetup #19 - フロントエンド開発Night」でも私の発表の補足でした。

イベントでの発表だとどうしても時間の関係で細かいところまで説明しきれない部分があるので、こんな感じでブログで補足せざるを得ないですね...本当は登壇前に補足ブログを公開した方がいいのかもしれないですね。(次からできるだけそうします)

本当は登壇から近い日にちに公開できればよかったんですが、個人的に登壇後まもなく入院、手術、その他諸々やらがあり、全く手が付けられませんでした...

ただ、これから徐々にペースを上げていこうと思います。

それでは、今回はこの辺で。

【AWS】IaCツールの比較

はじめに

お久しぶりです。

今までGitやJavaScriptについて書きましたが、今回は、Infrastructure as Code(以下IaCと記載)のツールについての記事です。

IaCツールに関しては色々あると思いますが、今回触れるのは下記の3つです。

  • AWS CDK
  • Terraform
  • Serverless Framework

上記について、自分なりに感じた特徴やメリット・デメリットなどを記載しようと思います。

前提

  • 今回はゼロベース(新規に一から導入する)観点で書いています。
    • 導入&実際に運用後はまた違ってくると思うので
  • 自分の経験の関係上、ターゲットとなるクラウドAWSとなります。

TL;DR

特徴 言語 メリット デメリット 有用なケース
AWS CDK ・プログラミング形式で書ける
AWS公式
各種プログラミング言語 プログラミング言語である プログラミング言語である プログラミングをわかる人がIaCも担当する場合
Terraform ・HCLという独自言語で記載
・公開モジュールを利用できる
HCL(HashiCorp Configuration Language) ・リソースの参照がしやすい
・公開モジュールを利用できる
パラメータ名が一部CloudFormation(以下Cfnと記載)と異なる クラウドインフラ全般をカバーしたい場合
Serverless Framework プラグインで機能拡張ができる
YAML/JSONで記載できる
YAML/JSON ・Lambda関連が非常に便利
プラグインで機能拡張できる
Lambda関連以外の部分の記載 Lambdaを多く使用する場合

AWS CDK

特徴

プログラミング言語で書ける
AWS CDKはプログラミング言語でコーディングして記載するのが特徴です。(メジャーなのはTypeScriptかな?)
そのため、何かしらの言語を知っていれば、導入の敷居はあまり高くないと思います。

AWS公式ツールである
AWS CDKはAWS公式が出しているツールなので、その点で(謎の)安心感があるかもしれません。(だから何?と言えばそれまでなのですが)

メリット

プログラミング言語で記載
先述の通り、AWS CDKはプログラミング言語で記載するので、いずれかの言語の知識があれば、導入はそれほど難しくありません。
そのため、アプリエンジニアがそのままAWS CDKでIaCを導入する...といったこともできます。

また、プログラミング言語に慣れている人からすれば、YAMLJSONより構造的で分かりやすくなるかな?という点も個人的にはメリットだと思います。

デメリット

プログラミング言語で記載
メリットと同じ内容ですが、裏を返せば、プログラミング何もわからん...という場合は、導入の敷居が高いかもしれません。

有用なケース

プログラミングをわかる人がIaCも担当する場合
ある程度対応するプログラミング言語がわかるメンバー(アプリエンジニアなど)が複数いる場合、AWS CDKは下記観点で有用だと思います。

  • プログラミング言語で書ける(=導入の敷居が高くない)
  • 複数のメンバーがメンテ対応できる(=属人化を防ぐ)

Terraform

特徴

HCLという独自言語で記載
TerraformはHCL(HashiCorp Configuration Language)という独自記法で記載します。(下記コードを参照)
といっても、そんなに難しいものではないので、そこまで導入に時間はかからないのではと思います。

# S3バケットを定義
resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"

  tags = {
    Name        = "My bucket"
    Environment = "Dev"
  }
}

# S3バケットポリシーを定義
resource "aws_s3_bucket_policy" "allow_access_from_another_account" {
  bucket = aws_s3_bucket.b.id
  policy = data.aws_iam_policy_document.allow_access_from_another_account.json
}

公開モジュールを利用できる
Terraformのモジュール(詳しくは前回の記事を参照)は、下記リンク先(公式サイト内)で公開されているものも多く、これらを使うことで、わざわざ自作せずとも、そのまま公開モジュールを利用できます。
そうすることで、定義作成の手間を軽減することができます。

https://registry.terraform.io/browse/modules?provider=aws

メリット

リソースの参照がしやすい
Terraformは、他のリソースの値の参照がしやすい、と個人的に感じました。
例えば先述のソースでbucket = aws_s3_bucket.b.idという記載がありますが、これだけでresource "aws_s3_bucket" "b"バケット名を参照できるので、例えばRefやFn::GetAttなどを利用しなくていいのは便利です。

また依存関係も、基本的には自動で解決してくれるので、自分で依存関係(=depends_onなど)を定義しなくてもいいのは良いなと思います。(一部、入れないといけないケースはありますが)

その他、外部リソース(アカウントIDやAWS上にあるリソースのARN等)の参照もHCLで記載できるので、わざわざローカルで定義しなくていいのは便利だと感じました。

公開モジュールを利用できる
「特徴」でも書いた通り、公開モジュールを利用することにより、わざわざモジュールやoutputの定義を自作する必要がなくなります。
そのため、公開モジュールを利用すれば、定義の手間を削減できます。

デメリット

パラメータ名が一部Cfnと異なる
一部リソースの一部パラメータ名がCfn構文のそれと異なる場合があるので、その場合はそのパラメータがどれに対応するかを調べる必要があります。(基本的には似通った名前だったり説明を読めばなんとなくわかりますが、たまに分かりにくいものもある)

有用なケース

幅広いリソースをデプロイする場合
Terraformはサポートしている範囲が広いので、数多くのリソースをデプロイする場合に向いていると感じます。(例えば、Fargateでフロント環境もして、RDBやDynamoDBなどのバックエンド環境も同時に作って...など)

別リソース値の参照や依存関係の解決がやりやすい点がその理由です。

またHCLで記載するので、特定のプログラミング言語に依存しない(=プログラミングに詳しくなくても対応できる)という点もメリットかなと思います。
(もちろん、プログラミングに詳しい場合でもTerraformを使用するメリットは大いにあります。)

個人的にはオールラウンダーなイメージで、ちょっと乱暴な言い方かもしれませんが「迷ったらTerraformを選ぶ」みたいな選択もアリなのかな?...とも思っています。

Serverless Framework

特徴

プラグインで機能拡張ができる
下記ページでプラグインが公開されており、標準で対応していないリソース・機能についても、プラグインで対応できる場合があります。

https://www.serverless.com/plugins

YAML/JSONで記載できる
Serverless Frameworkは、基本的にYAMLまたはJSON形式で記載します。
そのため、専用の言語や記法を覚える、ということはないので、その点で導入はしやすいと思います。

またnpmモジュールなので、インストールもnpm(yarn) install 1つで済むのは便利です。

メリット

とにかくLambda周りの定義が簡単
個人的に、Serverless FrameworkはとにかくLambda周りの定義が本当に簡単&充実しているのが最大のメリットだと思います。
例えば、Web APIでよくあるAPI Gateway+Lambdaの構成は、下記7行(実質6行)だけでできてしまいます。(最低限の設定であれば)

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

またそれ以外にも、S3, DynamoDBなどのトリガ設定も充実しています。

プラグインで機能拡張できる
「特徴」にも書いた通り、プラグインで機能拡張をすることにより、標準ではサポートしていないリソースにも対応できるケースがあります。
またプラグインでは単体テストなど、リソース定義以外で役立つものもあり、デプロイ以外にも開発全般で有用なものも多いです。

デメリット

Lambda関連以外の部分の記載
逆に、Lambda関連以外のリソースについては、AWS CDKやTerraformに比べるとサポートしていないものもまだまだあります。
その場合は、resourcesセクションに純粋にCfn構文を記載することになるので、そこが煩わしい、と感じることがあると思います。

有用なケース

Lambdaを数多く使用する場合
「メリット」に書いた通り、Serverless FrameworkはとにかくLambda周りの定義が便利&シンプルなので、(Web APIなど)Lambdaをたくさん使うような環境の場合、非常に有用だと思います。

またLambda以外の部分ですが、プラグインで対応したり、場合によっては他のIaCツールと併用する...というのもありだと思います。(管理は少し煩雑化してしまうかもしれませんが...)

まとめ

以上、IaCツール3つについて、ゼロベースで導入を考えた際の個人的な見解について書きました。

ただ、いろいろあるでしょうが、あくまで上記は考え方の一つであり、最終的にはプロジェクトの特性とか、メンバーの好みで選択するのも全く問題ないと思っています。

なので、あくまで今回の内容はゼロベースで導入を考えた際の参考程度にして頂ければと思います。

それでは、今回はこの辺で。

【Terraform】基本的な構文の説明

はじめに

お久しぶりです。

ちょっと前に「やっぱりクラウドバックエンドの仕事したいなあ...」と書いていたら、偶然にもIaC(Infrastructure As Code)のタスクを担当することができました。(またすぐフロントに戻るんでしょうけど)

で、今回は(Serverless FramewrokでもAWS CDKでもなく)Terraformをやってるのですが、これはこれで面白いですね。
本当にそれぞれメリットがあり、全部使いこなせると便利だなあと思いました。

で、今回は「Terraformユーザーのバイブル」とも言われている「実践Terraform」を読んでていまいち分からなかった箇所を含め、基本的な文法の解説について書きたいと思います。

TL;DR

下記について解説

  • resource
  • variable
  • local
  • output
  • data
  • module
  • dynamic

書かないこと

  • terraformのインストール&初期設定
  • 基本的な構成(*.tfファイルに記載する、providerの設定...など)
  • 各種ファイルの説明(terraform.tfvars, terraform.tfstateなど)

前提

  • 対象providerはAWSとします。(他は知らないから)
  • variable, local, outputなどは実際は別ファイルに記載するのが推奨みたいですが、今回は同じ箇所に記載します。

resource

対象providerのリソースを作成する。

resource <固有リソース名> <識別子> 形式で記載。
<固有リソース名>は下記URL参照

https://registry.terraform.io/providers/hashicorp/aws/latest/docs

またリソースの各種プロパティは、<固有リソース名>.<識別子>.<プロパティ名> で取得可能。

# 例:S3バケットを作成する。
resource "aws_s3_bucket" "hoge" {
  bucket = "my-tf-bucket-hoge"
}  
  
# 上記S3バケットのポリシーを設定  
resource "aws_s3_bucket_policy" "hoge" {
  # 上記S3バケットのid(=バケット名)を取得する
  bucket = aws_s3_bucket.hoge.id 
  
  #policyの設定は省略 
  policy = ... 
}

variable

各リソースの各種プロパティに対して、変数を割り当てる。
変数には任意の値を設定できるので、各種プロパティに任意の値を設定できる。

設定した変数の値はvar.<変数名> 形式で取得可能。

主な用途は下記(?)

  • アプリ名・ドメイン設定など、プロジェクトで共通で使用する値の定義
    • terraform.tfvars(※)などに値を設定する
  • モジュール(後述)に設定する値の定義
    • 関数の「引数」みたいな使い方をする

※外部ファイル(*.tfvars)の中でも特殊なもので、なにも設定しなくてもこのファイルの内容から変数の値を取得してくれる。(他の外部ファイルはコマンド実行時に指定が必要)

# 例1:プロジェクトで共通で使用する値の定義  
# どこか(terraform.tfvarsと同じ階層のファイル内)にvariableを定義する  
variable "app_name" {
  type = string
}  
  
# terraform.tfvarsなどで設定する。  
app_name = hogehoge  
  
# そうすると、リソース定義などの際にそれを利用できる。  
# 文字列内で利用する場合は${var.name}とする  
resource "aws_s3_bucket" "hoge" {
  bucket = "${var.app_name}-bucket-hoge"  
  tags = {
    APP_NAME = var.app_name
  }
} 
# 例2:モジュールの引数として使用する値の定義  
# モジュール用のvariableを定義する  
variable "bucket_prefix" {
  type = string
}  
  
# モジュール側で、variableの値を参照する。
resource "aws_s3_bucket" "this" {
  bucket = "${var.bucket_prefix}-bucket-fuga"  
}  
  
# そうすると、モジュールの呼び出し側でそれを利用できる。  
# 結果として、「makky12-bucket-fuga」という名前のバケットができる  
module "aws_s3_bucket_piyo" {  
   # 上記「resource "aws_s3_bucket" "this"」を参照しているものとする
  source = "./module/s3" 
  bucket_prefix= "makky12"  
} 

local

ローカル変数。variableと似ているが、下記の点が異なる。

  • 定義した(≒同じフォルダ内の)resourceやmoduleにのみ作用する。
    • サブフォルダなどにまたがって使用することはできない。
  • 任意の値を後から設定することはできない。(宣言時の値で固定)
    • プログラム言語の定数みたいなもの。
    • ただしvariableと組み合わせることで、任意の値を初期値に設定することはできる。
      • もちろん、その値はもう変更できない

具体的な使い方は、下記サイトが参考になります。
[terraform] VariableとLocal Valueの使い所 - Qiita

# モジュールで使用する場合。
variable bucket_suffix {
    type = string
}
  
locals {  
    # localsは定義した値で固定。  
    # ただし値にvariableを使用すれば、任意の値を設定可能。  
    # (もちろん、変更は不可)
    local_prefix = "makky12"
    local_suffix = var.bucket_suffix 
}
  
resource "aws_s3_bucket" "this" {
  # localの値を参照する。
  bucket = "${local.bucket_prefix}-bucket-piyo-${local.bucket_suffix}"  
}  
  
# そうすると、リソースを呼び出す側でそれを利用できる。  
# 結果として、「makky12-bucket-fuga」という名前のバケットができる  
module "aws_s3_bucket_piyo" {  
   # 上記「resource "aws_s3_bucket" "this"」を参照しているものとする
  source = "./module/s3" 
  bucket_prefix= "makky12"  
} 

output

以下の用途に用いられる値。

  • terraform apply 時に、実際の値を出力する。
    • ただしsensitive = trueにすると、出力されない。
  • モジュールにおける、モジュールの固有のプロパティを設定できる
    • プロパティは、モジュール呼び出し側から参照できる。

取得する際は<該当モジュール名>.<output名> 形式で取得可能。

# 例1:terraform applyのoutputとして利用する  
    
# S3バケットを定義する  
resource "aws_s3_bucket" "hoge" {
  bucket = "hoge-fuga-bucket"  
}   
  
# こうすると、terraform apply時に上記S3バケットのARNが出力される
output "s3_arn" {
  value = aws_s3_bucket.hoge.arn
}   
  
# sensitive=trueの場合、値は出力されない  
output "s3_domain_name" {
  value = aws_s3_bucket.hoge.domain_name
  sensitive   = true
} 
# 例2:モジュールの引数として使用する値の定義  
# 共通モジュール側に、先程のようにS3バケットとoutputを定義する。
resource "aws_s3_bucket" "hoge" {
  bucket = "hoge-fuga-bucket"  
}   
  
output "s3_id" {
  value = aws_s3_bucket.hoge.id
}   
  
# 上記共通モジュールを呼び出す  
module "aws_s3_bucket_piyo" {  
  source = "./module/s3" 
}   
 
# 下記のようにすることで、outputの値(=S3バケットのID, =バケット名)を取得できる 
resource "aws_s3_bucket_policy" "hoge" {
  bucket = module.aws_s3_bucket_piyo.s3_id
  policy = ...
}

data

terraform外部の値を取得することができる。 例えば以下のような使い方ができる。

  • AWSアカウントから、アカウント情報(アカウントID、リージョン情報など)や既存リソースのARNを取得する
  • ファイルからデータを読み込む(externalと併用)

定義はdata <固有data名> <識別子> 形式で取得可能。(取得は下記ソース参照)

externalについては、下記サイトを参照。
- https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source#processing-json-in-shell-scripts - terraform の external data source を使って外部コマンドの実行結果を variable として使用する · GitHub

# 例1:AWSから各種情報を取得する  
    
# ACMから、発行済ドメインの証明書ARNを取得する  
data "aws_acm_certificate" "hoge" {
  domain   = "hogehoge.fugafuga.com"
  statuses = ["ISSUED"]
}
  
# terraformに紐づいているAWSアカウント情報を取得する。  
data "aws_caller_identity" "fuga" {}
  
# CloudFrontで、上記dataの情報を参照する
resource "aws_cloudfront_distribution" "hoge" {
  (一部省略)  
  tags = {
    ACCOUNT_ID = data.aws_caller_identity.fuga.account_id
  }

  viewer_certificate {
    acm_certificate_arn = data.aws_acm_certificate.hoge.arn
  }
}   
  
# sensitive=trueの場合、値は出力されない  
output "s3_domain_name" {
  value = aws_s3_bucket.hoge.domain_name
  sensitive   = true
} 
# 例2:ファイルの内容を読み込む  
# catコマンドでjsonファイルを読み込む。
  
data "external" "hoge" {
  program = ["cat", "test.json"]
}   
  
# 上記jsonファイルのキーの値を参照する。
resource "aws_s3_bucket" "hoge" {
  bucket = value = "${data.external.hoge.result["piyo"]}"
} 

module

リソース定義などの処理を共通モジュール化できる。
共通モジュール化することで、毎回同じ定義を書かずとも、最低限のvariableを共通モジュールに渡すだけで該当のリソースを何個も作成できる。

1プロジェクト内でたくさん作成するリソースの定義をモジュール化しておくと便利。(Lambdaとか)

またモジュールは自作する他に、Terraform Registryで公開されているものを使用することができる。
これを利用した場合、特に何もしなくても各モジュールが用意しているoutputを参照できる。

# 例1:自作モジュールを利用する場合  
  
# (共通)モジュール側は、通常のリソース定義と同様の書き方でOK。
variable "filename" {
  type = string
}  
  
variable "function_name" {
  type = string
}  
  
variable "handler" {
  type = string
  default = "index.handler"
} 

variable "env" {
  type = string
  default = "dev"
}  
  
resource "aws_lambda_function" "this" {
  filename      = var.filename
  function_name = var.function_name
  handler       = var.handler
  role = ... #省略
  
  runtime = "nodejs14.x"
  
  environment {
    variables = {
      ENV= var.env
    }
  }
}  
  
# 共通モジュールを呼び出す側は「module <任意のモジュール識別子名>」で  
# 共通モジュールを呼び出す。  
# sourceで共通モジュールのパス、あとはvariablesを指定する。  
# 結果として、function_hoge、function_fuga、function_piyoの
# 3つのLambda関数ができる    
module "hoge" {
  source = "./modules/lambda"
  filename = "./hoge_function.zip"
  function_name = function_hoge
}  
  
module "fuga" {
  source = "./modules/lambda"
  filename = "./fuga_function.zip"
  function_name = function_fuga
  env = "test"
}  
  
module "piyo" {
  source = "./modules/lambda"
  filename = "./piyo_function.zip"
  function_name = function_piyo
  env = "stg"
  handler = "index.main"
}
# 例2:Terraform Registryの公開モジュールを利用する場合  
  
# sourceやversionは、各モジュールページ右上の「Provision Instructions」に
# 記載してあるので、それをコピペする。  
# variable, outputも各モジュールページやリンク先のGitHubに
# 記載してあるので、それを参照する。  
# 今回は「AWS lambda」の共通モジュールを使用する。  
# https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest
module "lambda" {
  source  = "terraform-aws-modules/lambda/aws"
  version = "2.34.1"
  function_name = "function_hogehoge"
  description   = "hogehoge lambda function"
  handler       = "index.handler"
  runtime       = "nodejs14.x"

  source_path = "../src/hogehoge"
}
 
# 各公開モジュールで定義されているoutputは、そのまま  
# 使用することができる。 
resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket = "bucket_hogehohe"

  lambda_function {
    # ここで「lambd_function_arn(=Lambda関数のARN)」を参照
    lambda_function_arn = module.lambda.lambd_function_arn
    events              = ["s3:ObjectCreated:*"]
  }
} 

dynamic

リソース内で複数個設定可能なmap形式の同一プロパティについて、配列で指定することで配列の値を繰り返し適用できる。(string, numberなどプリミティブなプロパティにはfor_eachなどでループできる)

プログラム言語のforループ文みたいな使い方ができる。

# 例2:ファイルの内容を読み込む  
# catコマンドでjsonファイルを読み込む。
resource "aws_waf_web_acl" "hoge" {
  name        = "hogehoge"
  metric_name = "hogehoge"

  default_action {
    type = "BLOCK"
  }
  
  # 実際にrulesに設定する内容がvar.rulesに配列で渡されるとする
  dynamic "rules" {
    for_each = var.rules
    content {
      priority = rule.value["priority"]
      rule_id = rule.value["rule_id"]  
       
      # ネスト構造の場合はこうする。
      # https://www.terraform.io/language/expressions/dynamic-blocks
      dynamic "action" {
        for_each = rule.value.action
        content {
          type = action.value.type
        }
      }
    }
  }
}

まとめ

掛け足でしたが、ざっとterraformの基本構文についてまとめてました。(自分の備忘録も兼ねて)

今までTerraformは触ってませんでしたが、使ってみるとこれはこれで便利な部分も多く、面白いなあと感じました。

IaCツールは色々特徴や強み・弱みがあるので、適材適所使い分けれらるようになりたいなあ、と思います。

それでは、今回はこの辺で。

【JavaScript】数値に関する謎(?)挙動について

はじめに

前回まで、Git管理やバージョニングについて記事にしましたが、今回は一転してみんな大好き?JavaScriptの話です。

最近ちょっとSNSで話題になってた、JavaScriptの数値に関する謎?挙動についてです。

アジェンダ

  • parseInt()の挙動
  • 「+」「-」に関する挙動

parseInt()の挙動

parseInt(引数を数値に変換する関数)の挙動について、ある外国人のエンジニアの方が、こんな内容のツイートをしてました。(リンクは失念)

/*
 mysterious behavior of parseInt
*/
console.log(parseInt(0.5));  // 0
console.log(parseInt(0.05));  // 0
// (中略)
console.log(parseInt(0.000005));  // 0
console.log(parseInt(0.0000005));  // 5 ←What!?
   

上記に対して、Twitterでもいろいろツイートが飛び交ってましたね。まあ色々と。

上記の挙動について

まず、parseIntに数値を指定してるのがあまりよろしくないという点に注意です。(意味がない)

で、parseIntに数値(文字列以外)を指定した場合の挙動ですが、下記のようになります。(引数の数値をそのまま返却するわけではない点に注意)

  1. 引数をtoString()で文字列に変換する
  2. 上記文字列を数値にした値を返却する

参考:parseInt() - JavaScript | MDN

で、0.0000005をtoString()すると、下記の通り「5e-7」と指数表現された文字列が返ることが分かります。
f:id:Makky12:20220205192210p:plain

これを数値に変換するわけですが、文字列「5e-7」の「e」は数値(=10進数)に変換できないため、先頭の「5」のみ返された結果、結果が「5」となるわけです。

なお引数を文字列で指定すれば、0.0000005(やさらに小さい値)でも、正しい結果が返ります。
f:id:Makky12:20220205192828p:plain

ちなみにこの件については「1時間プログラミング」の紀平拓男さんもブログを書いてますので、そちらもぜひ。
JavaScript で parseInt / parseFloat を使わない方が良い理由

また、先述のMDNページでも下記のように「とても大きな数字やとても小さな数字を使用する際に予期しない結果を生み出すことがあります。」と記載してあります。

数値によっては e の文字を文字列表現の中で使用しますので (例えば 6.022E23 は 6.022 × 1023 を表します)、parseInt を使用して数値を切り捨てると、とても大きな数字やとても小さな数字を使用する際に予期しない結果を生み出すことがあります。parseInt を Math.floor() の代用として使うべきではありません。

基数変換でも

なお上記の文字列/数値の挙動の違いですが、8進数の基数変換の際にも起こります。
下図の通り、parseIntで8進数(っぽい値)の基数変換を行った場合、文字列or数値、基数(第二引数、以下「radix」と記載)の有無で値が変わります。

f:id:Makky12:20220206180908p:plain

まずややこしいのが、JavaScriptの8進数の扱いが文字列or数値で異なる点で、先述のMDNページの説明にもある通り、下記の挙動となります。

  • 文字列:先頭が0の値を8進数として扱わない
    • 先頭の0を除いた値を10進数文字列として扱う
  • 数値:先頭が0の値を8進数として扱う

上記により先頭2つのparseIntは、文字列「021」を文字列「21」として扱います。
そして、parseInt("21")はそのまま21、parseInt("21", 8);は「8進数の21=10進数の17」を返します。

また後ろ2つはまず数値021を10進数に変換→その値を文字列にする...が行われ、結果文字列「17」が返ります。
そして、parseInt("17")はそのまま17、parseInt("17", 8);は「8進数の17=10進数の15」を返します。

こんな感じで、parseIntを使った8進数の基数変換は結構ややこしい挙動をするので、扱いには要注意です。

16進数の場合は...

先述のMDNページの記載にもある通り、先頭が「0x(0X)」の文字列は8進数と違い、ちゃんと16進数として扱ってくれます。
なので下図の通り、parseInt("0x21")およびparseInt("0x21", 16);はどちらも33を返します。

ただ数値の場合は注意が必要で、まず0x21を10進数に変換し(=33)、それをradixで判定するので、3つめはそのまま33、最後のは「16進数の33(=0x33)=10進数の51」となり、51が返ります。

個人的には、parseIntについてはこんな感じかなあと思います。

  • 極力parseIntは使うべきではない。
    • 紀平さんのブログにもある通り、Numberを使うべき
    • 8進数の変換をする必要がある場合、ちゃんと挙動を理解して使うか、あるいは専用の関数を用意する
  • 第一引数は必ず文字列にする

f:id:Makky12:20220206182741p:plain

「+」「-」に関する挙動

また結構前ですが「+」や「-」の挙動について、別の外国人のエンジニアの方が、こんな内容のツイートをしてました。(これもリンクは失念)

/*
 mysterious behaviour of numeric-string operands
*/
console.log("2" + 2) // string "22"
console.log("22" + 2) // string "222"
console.log("222" - 2) // number 220 ←What!?
   

上記の挙動について

上記ツイートした外国人の人が言いたかったのは、

  • 「+」だと文字列なんだけど...
  • 「-」したとたん数値になってる!?

ってことなのかな?

まずJavaScriptの「+」には、

  • 文字列の場合:文字列の連結
  • 数値の場合:数値の加算

の2つの機能があります。

で、最初の2つは、左辺が文字列なので「文字列の連結」の方が実施されます。(左辺と右辺のいずれかが文字列なら、文字列連結が優先されるようです) f:id:Makky12:20220205195826p:plain

しかし「-」は「+」と違い、文字列を扱う機能はなく、単に「数値の減算」のみ行います。

最後の計算は左辺の文字列「222」が数値に変換可能な文字列なので「222 - 2」が実施され、結果220(数値)が返されます。(なお、数値に変換不可能な文字列だった場合、NaNになります)

まとめ

以上、最近SNSで話題になった、JavaScriptの謎?挙動に関する記事でした。

たしかにぱっと見「なんじゃこりゃ!?」というような挙動がJavaScriptには結構ありますが、そこでただディスるだけではなく、その仕組みを追っていくと「なるほど」とか「そういうことか」と勉強になる点も多々あります。

実際、parseInt()の挙動についてはかなり勉強になりました。(ただ、parseInt(021, 8) が15になる挙動は、理解するのに結構時間がかかりましたが...)

それでは、今回はこの辺で。

【Git】ログ&バージョニングを管理する

はじめに

前々回前回とGit運用に関する記事を書きました。

今回はその最終回として「ログ&バージョニングを管理する」について書きたいと思います。

開催した&今回記載する内容

  • コミットメッセージを統一する ←前々回の記事
  • コミットメッセージをチェックする ←前回の記事
  • ログ&バージョニングを管理する ←今回はこれ

ログ&バージョニングを管理する目的

ソース変更とバージョンを一致させる
バージョン番号(「x.y.z」)には正しく意味があり、大まかには下記となります。

  • x:大きな影響を及ぼす変化(見た目が大幅に変わる、前と互換性のない変更 etc.)
  • y:機能や情報の追加など
  • z:既存機能に影響のない、細かい修正(バグや誤字脱字の修正など)

ソースに対する変更を行う際に、バージョン番号も変更内容に応じた正しい番号にアップする必要があります。

たとえば、バグ修正しかしていないのにメジャーバージョンが上がったりとか、そういうことがないようにする必要があります。

バージョンごとの変更内容を明確にする
バージョンアップは何かしらの変更で行われるので、各バージョンごとに「このバージョンではどんな変更があったか」を明確にする必要があります。
それを行うことでソフトの利用者/開発者に情報を提供することができます。(機能の追加、バグ修正、脆弱性対応状況など)

ツールの紹介

といっても、これらを人力で管理するのは大変なので、これらをサポートしてくれるツールを紹介します。

semantic-release(https://github.com/semantic-release/semantic-release)

  • 上記のログ&バージョニングを行ってくれるツール
  • コミットメッセージから判断し、バージョニングやログを記録してくれる
  • リモートブランチで動作する

standard-version(https://github.com/conventional-changelog/standard-version)

  • こちらも上記のログ&バージョニングを行ってくれるツール
  • 基本的な部分はsemantic-releaseと同じ
  • こちらはローカルブランチで動作する

以後、standard-versionを例に説明を行っていきます。(semantic-releaseをしっかり使ったことないので)

参考サイト

基本的な動き

インストール&設定
公式サイトの通りに行えばOKです。

# インストール
> npm i --save-dev standard-version  # npm
> yarn add --dev standard-version  # yarn
// package.jsonに下記を追加
{  
  "version": "1.0.0",
  "scripts": {
    "release": "standard-version"
  }
}

最初のリリース
とりあえず、下記メッセージでcommitを行います
feat(xxx): 検索機能を追加

で、下記コマンドでstandard-versionを実行します。

> yarn release --first-release

すると、以下のことが行われます。

  • CHANGELOG.mdの生成&バージョン番号・commitメッセージの記載
  • 上記変更のcommit
  • Gitタグ「v1.0.0」の生成

2回目以降のリリース
さらに下記commit&standard-version実行をします。

> git commit -m "feat(xxx): 部分一致検索を追加"
> yarn release  
> git commit -m "fix(xxx): スペースが入るとエラーになるバグを修正"
> yarn release

すると、下記の変更がなされると思います。

  • package.jsonのバージョンが「1.1.1」になる
  • CHANGELOG.mdにバージョン番号・commitメッセージが追記
  • 上記変更のcommit
  • Gitタグ「v1.1.0」及び「1.1.1」の生成

まとめると、standard-versionは以下のことを行ってくれます。

  • packae.jsonのverison管理(=commitメッセージの内容に応じたバージョンアップ)
    • 初回リリース(--first-release)時を除く
  • CHANGELOG.mdの作成&記載
    • バージョン番号&それに対応する変更内容の記載
  • 上記変更(package.jsonCHANGELOG.md)のcommit
  • バージョン番号がついたgitタグの作成

バージョンアップのルール

バージョンアップの基本的ルールは「ログ&バージョニングを管理する目的」に書きましたが、前回&前々回で紹介したConventional Commitsのページにも記載されています。

standard-versionではConventional Commitsのルールをベースに、下記のルールでバージョンアップが行われるようです。

type(変更種別) バージョンアップの種別
fix(バグ修正) パッチバージョンアップ v1.0.0→v1.0.1
feat(機能追加) マイナーバージョンアップ v1.0.0→v1.1.0
breaking change(破壊的変更) メジャーバージョンアップ v1.0.0→v2.0.0
上記以外 パッチバージョンアップ v1.0.0→v1.0.1

設定のカスタマイズ

standard-versionのカスタマイズですが、プロジェクトルートに.versionrcファイルを作成し(.versionrc.json, .versionrc.jsでもOK)、そこに設定を定義することで行えます。

設定項目はたくさんありますが、とりあえず最低限だけ紹介します。(あとは公式サイトや--helpオプションの結果を参照)

{
  "types": [  
    // type: 変更種類(feat, fixなど)  
    // section: CHANGELOG.mdのcommitメッセージ前に  
    // 記載する内容(hidden=falseの場合のみ)  
    // hidden: trueの場合、そのtypeのcommitメッセージはCHANGELOG.mdに記載しない。  
    // (ただしバージョン番号はhiddenの設定に関わらず記載される) 
    {"type": "feat", "section": "Features"},
    {"type": "fix", "section": "Bug Fixes"},
    {"type": "chore", "hidden": true},
    {"type": "docs", "hidden": true},
    {"type": "style", "hidden": true},
    {"type": "refactor", "hidden": true},
    {"type": "perf", "hidden": true},
    {"type": "test", "hidden": true}
  ],
  "tag-prefix": "xxx-v"
  "releaseCommitMessageFormat": "chore(release): {{currentTag}}"
}

それぞれ、下記の内容です

項目 説明 備考
types 変更種類ごとのCHANGELOG.mdへの記載の設定 詳細は「typesの設定」を参照
tag-prefix gitタグのバージョン番号の前につくプレフィックス 上記の例だと、例えばxxx-v1.1.0みたいなタグになる
releaseCommitMessageFormat project.jsonCHANGELOG.mdの更新commitに対して付けられるコミットメッセージ。 {{currentTag}}は、現在のgitタグ(=バージョン番号)。
詳細は下記URL参照。

https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.1.0/README.md

typesの設定

項目 説明 備考
type 変更種類(feat, fixなど)
section CHANGELOG.mdのcommitメッセージ前に記載する内容 hidden=falseの場合のみ有効
hidden CHANGELOG.mdにcommitメッセージを記載するかどうか。(trueだと記載されない) あくまで書かれないのはcommitメッセージのみで、バージョン番号はhiddenの設定に関わらず記載される

コマンドオプション

satndard-version実行時に使用できるオプションになります。(これも主要そうなもののみ記載)
--first-releaseは説明済なので省略

オプション 説明 備考
--prerelease [name] プレリリース版としてバージョニングを行う v1.0.0-[name]のようなバージョンが付与される。
nameは省略可能。省略すると0, 1, 2...とインクリメントする
-t <tag-prefix> gitタグのバージョン番号の前につくプレフィックスを指定する .versionrcのtag-prefixと同じ
--release-as <versionNo> バージョン番号を直接指定する
--no-verify project.jsonCHANGELOG.mdの更新commit時に、Git Hooksを無効にする 前々回紹介したような、「(git-czなどの)ツールでのcommit以外はエラーにする」ような処理をpre-commitなどでかけている場合に、それを無効にできる
--dry-run 実際にログ記載&バージョン変更は行わず「もし行ったらこうなる」という結果のみ表示する 事前の動作確認用

まとめ

以上、ログ&バージョニングを管理する方法でした。

前々回・前回含めてGitのコミットメッセージ管理やそれに伴うログ・バージョン管理を書きましたが、チームの運用管理面でも、こういう点をやっておくとプロダクト管理が非常にやりやすくなると思います。(特に時間が経ってから見返すとき)

また、こういう点は手作業で管理すると大変なので、各種ツールを使って自動化して、なるべくメンバーが本来行うべき作業に工数をたくさん割けるようにしたいものです。

それでは、今回はこの辺で。

【Git】コミットメッセージを自動でチェックする

はじめに

前回、Git運用記事の第一弾として「コミットメッセージを統一する」という記事を書きました。

今回はその第二弾として「コミットメッセージをチェックする」について書きたいと思います。

記載する内容

  • コミットメッセージを統一する ←前回の記事
  • コミットメッセージをチェックする ←今回はこれ
  • ログ&バージョニングを管理する

目的

コミットメッセージをチェックする目的ですが、もちろん「コミットメッセージが(Semantic CommitsやConventional Commitsの)規約に沿ったフォーマットであるか」をチェックするためです。

詳細は次回書きますが、ログ&バージョニングでは、コミットメッセージが規約に沿っていることが前提なので、規約に沿ってないと正しく動作しません。

また、前回紹介した「cz-cli」や「git-cz」経由でgit commitすれば問題ないですが、ついつい「git commit」や(VS Codeなど)GUIのGit機能経由でcommitを行ってしまうケースも起こりえます。

そういった際にもちゃんとコミットメッセージが規約に沿っていることを保証するために、コミットメッセージをチェックする必要があります。

紹介するツール

husky

huskyは、各種Git Hooksの処理を簡単に作成できるツールです。

GIt Hooksについては下記リンク先を参照。なおコミットメッセージのチェックの場合「commit-msg」だけでOKです。
Git - Git フック

husky自体は直接コミットメッセージのチェックは行いませんが、「git commitした際に、自動でコミットメッセージのチェックを行う」処理を簡単に作成できます。(commitlintでも、huskyとの併用方法が紹介されています)

インストール&使用方法

下記手順で実施します。(下記手順を実施すると、カレントディレクトリに「.husky」というフォルダが作成される)

# インストール 
> npm install -D husky # npm
> yarn add -D husky # yarn  
  
# Git hooksの有効化(これやらないとGit hooksを扱えないので注意)  
> npx husky install # npm
> yarn husky install # yarn

なお必須ではないですが、package.jsonに下記scriptを定義しておくと、リポジトリclone&npm(yarn) install時に自動でGit Hooksの有効化処理をしてくれるため便利です。(公式でも推奨されています)

"scripts": {
  "postinstall": "husky install"
}
フックイベントの作成

下記コマンドをすると、Git Hooks定義ファイルが.hooks内に作成されます。(今回はcommit-msgフック用の定義ファイルを作成)

# 「npx(yarn) husky add .husky/(Git Hook) 実行コマンド」の形式。
# $1に実際のコミットメッセージが入る。  
> npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1' #npm
> yarn husky add .husky/commit-msg 'yarn commitlint --edit $1' #yarn

ここまでできれば一旦huskyの設定はOKなので、次はcommitlintです。

なお今回は紹介しませんでしたが、よくhuskyと一緒に用いられるツールとして「list-staged」があるので、リンクを貼っておきます。
(commit時にeslintやprettierなどの処理を実行する際に使われます)

https://github.com/okonet/lint-staged

commitlint

commitlintは、概ね名前の通り「commitメッセージのlintツール」で、commitメッセージが規約に沿っているかをチェックしてくれます。

huskyと組み合わせて、以下の処理を書くことで、git commit時にcommitメッセージのチェックを自動で行ってくれます。

  • huskyでcommit-msgをフック
  • commit-msgのフック処理でcommitlintを実行する
インストール&使用方法
> npm install -D @commitlint/config-conventional @commitlint/cli # npm
> yarn add -D @commitlint/config-conventional @commitlint/cli #yarn  
  
# windows以外だと、こういう書き方もできるんだね  
> npm install --save-dev @commitlint/{cli,config-conventional}  
  
# commitlint.config.jsファイルに設定を書き込む。
# なお公式ページにある通り、設定ファイルの名前はいろいろある
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

最後のコマンドはcommitlint.config.jsファイルに設定を書き込んている処理なので、自分でcommitlint.config.jsファイルを作成してもOK。

module.exports = { 
  extends: ['@commitlint/config-conventional'] 
};

また「@commitlint/config-conventional」はConventional Commitsを適用しますが、他にも種類があるので、興味がある人は調べてみてください。(今回は@commitlint/config-conventionalに限定して話を進めます)

なお設定の詳細は、下記ページを参照してください。
https://commitlint.js.org/#/reference-configuration?id=configuration

ルールの設定&カスタマイズ

commitlintで適用されるルールは、下記ページに記載されています。 https://commitlint.js.org/#/reference-rules?id=rules

上記ページを読めば、適用ルール自体は理解できると思います。

また独自のルールを追加することはできない(少なくとも自分は知らない)ですが、既存ルールの設定を変更することはできます。

その場合は、commitlint.config.jsの「rules」に、その内容を記載します。

const Configuration = {
  extends: ['@commitlint/config-conventional'],
  /*
   * ルール変更の例:type(変更種類)の最低文字を1文字にする(=入力必須にする)
  */
  rules: {
    'type-min-length': [2, 'always', 1],
  },
ルールのカスタマイズの方法

ルールのカスタマイズは、先述のように、下記形式で記載します。(関数形式でも書けます)

rule_name = [level, applicable, value]

右辺の配列の項目は、以下になります。

項目 説明 備考
level エラーレベルを0, 1, 2のいずれかで記載。
・0: ルールを無視する(適用しない)
・1: NG時に警告を出す(commitは継続)
・2: NG時にエラーにする(commitも中止)
applicable ルールの適用方法をalways, neverのいずれかで指定。
・always: 値が条件を満たした場合、OKとする。
・never: 値が条件を満たした場合、NGとする。
条件は公式ページのconditionを参照
value value(最小値、最大値、数値の範囲など)を受け付けるルールの場合に、その値を設定する。 valueの有無は公式ページのvalueを参照

applicableの例
例えば「type_empty」は「type(変更種類)が空文字であること」をチェックするルールですが、applicableの設定により、下記のような挙動になります。(=真偽が反転する)

  • always: typeが空文字の時、判定結果はOKになる
  • never: typeが空文字の時、判定結果はNGになる。(=未入力はNG)

というか「xxx_empty」系をalwaysで使うことはまずないと思います。(level=0かnever。デフォルトもそうなってます)

実際に導入する

といっても、導入&コマンドはhuskyのところで書いた通りですし、commitlintの設定は最低限上記に書いた「module.exports=extends: ['@commitlint/config-conventional'] 」があれば動きます。

ここまで出来たら、実際にcommitを実施してみましょう。

commitメッセージがSemantic CommitsやConventional Commitsの規約通りなら正しくcommit出来て、そうでないならばcommitlintが何かしらのエラーを表示すると思います。(例えば「なんか変更」というコミットメッセージ(=typeを指定しない)をcommitすると、エラーになるはずです)

正しく動作すれば、導入は完了です。あとは色々カスタマイズして、運用しやすいように変更しましょう。

まとめ

今回はコミットのチェックについて書きました。

commit時にコミットメッセージのチェックを自動でやってくれるのは、非常に便利だなと思います。(ルール浸透にも役立ちますし)

またhuskyは色々なGit Hookをトリガに処理を実行できるので、コミットメッセージのチェック以外にも色々使えそうですね。

次回は最終回として「ログ&バージョニングを管理する」について記載しようと思います。

それでは、今回はこの辺で。