echo("備忘録");

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

【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をトリガに処理を実行できるので、コミットメッセージのチェック以外にも色々使えそうですね。

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

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

【Git】コミットメッセージを統一する

はじめに

新年あけましておめでとうございます。今年もよろしくお願いします。

で、新年一発目の記事ですが、現在業務でアプリ開発以外にチーム運営にも携わっており、その中でGit運用のことを少しやっております。

今回から数回は、そのGit運用について書こうと思います。

今回&今後記載する内容

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

目的

コミットメッセージを統一する目的としては

  • コミットメッセージから変更内容をわかりやすくする
  • チーム内で記法を統一する。(メンバー間でばらばらにしない)
  • リポジトリ(≒アプリ)のバージョン管理をしやすくする
    • これは「ログ&バージョニングを管理する」で説明します

等があります。

たとえば、「index.jsを変更」というコミットメッセージがあった場合、下記の問題があります。

  • 具体的にindex.jsの何を変更したのかが分からない。
  • それは何のために実施したのかが分からない。(バグ修正、パフォーマンス改善など)

こういった点を極力無くし、意味のある分かりやすいコミットメッセージにしよう、というのがその狙いです。

コミットメッセージの統一

コミットメッセージの統一の指標として、「Semantic Commits」とか「Conventional Commits」と呼ばれるものがあります。

こういった指標を用い、意味のある(=semantic)かつ定型的(=conventional)なコミットメッセージを作成することで、チーム内でコミットメッセージを統一するのに役立ちます。

Semantic CommitsやConventional Commitsの概要

Semantic CommitsやConventional Commitsの概要ですが、コミットメッセージは下記の形式で書くことが必要とされます。

<type>[optional (scope)]: <description>
  
[optional body]
  
[optional footer(s)]

各項目の説明は、下記のとおりです。

項目 説明 備考
type(必須) 変更の種類 詳細は下表参考
scope(任意) 変更対象の範囲 アプリ名、ライブラリ名、モジュール名など
description(必須) 変更内容の概要 git commit -m で記載する内容
body(任意) 変更内容の説明 descriptionで説明しきれない場合。
footer(任意) 補足説明 breaking change(破壊的変更)がある場合、その内容をここに書く

また「type」には、下記のようなものがあります。(あくまで推奨されているもので、これをベースにチーム内で検討するのが良い)

項目 説明 備考
feat 新規機能の追加
fix バグ修正
docs ドキュメントのみの変更 ソース自体には変更なし。readme.mdの修正のみとか
style スペース、インデント、末尾のセミコロンなどのみの修正
refactor ソースのリファクタリング
perf パフォーマンス改善 improveにする場合もある模様
test テストソースの変更
build ソースのビルド関連の処理の変更 webpackやbabelとか
ci ci/cd関連のソースや設定の変更 github ActionsやCircle CIなど
chore ライブラリ追加など、ソース本体には影響しない変更。 上記に該当しないすべてのケースに当てはめてもOK

たとえば「feat(components/xxx): add validation」だったら「xxxというコンポーネントにバリデーション機能を追加した」となります。

破壊的変更(breaking changes)

また、ソースに破壊的変更(=下位互換性を保証しない変更)が発生した場合、下記のいずれかを行う必要があります。

  • type(またはtype(scope))の後に「!」をつける。(例:feat(app)!: xxx)
  • footerに「BREAKING CHANGE:」 という記載をする。

上記が含まれたコミットは破壊的変更となり、下位互換性を保証しません。
またバージョン時には、メジャーバージョンが上がる形となります。(v1.0.0→v2.0.0)

サポートツール

上記のSemantic CommitsやConventional Commitsですが、手作業コミットで導入しようとしても、なかなか大変だと思います。(スペルミスなどもあるでしょうし)

そこで、これらをサポートするツールもあります。

cz-cli(https://github.com/commitizen/cz-cli)

commitizenが提供するサポートツール。
commitizenをglobal installすることで使えるようになります。

対話形式でSemantic CommitsやConventional Commitsのtype, scope, descriptionなどを設定することで、自動でそれらの形式のコミットメッセージを作成してcommitをしてくれます。

f:id:Makky12:20220115202026p:plain

カスタマイズは後述のgit-czよりちょっとややこしそうですが、その分git-czより突っ込んだカスタマイズが出来そうです。
参考:Customization - Commitizen

git-cz(https://github.com/streamich/git-cz)

cz-cli同様、対話形式でSemantic CommitsやConventional Commits形式のコミットメッセージを設定できるツール。
こちらはcommitizenなしでも使えます。(一緒にインストールしてもOK)

cz-cliよりカスタマイズできる範囲は限られますが、その分カスタマイズ自体はcz-cliよりやりやすいです。(チーム内での運用レベルなら、必要十分な設定は揃っている印象)

なおcz-cliエイリアスで「git-cz」を持っていますが、これはそれとはまったく別物になります。

まとめ

今回はGit運用として、コミットメッセージ統一、及びSemantic CommitsやConventional Commitsについて書きました。

これらの指標をベースにチーム内でコミットメッセージを統一すると、後で見返した際にも変更内容などが把握しやすくてよいと思います。

また「ログ&バージョニングを管理する」で書きますが、これらをやっておくとバージョニングもやりやすくなるのでおススメです。

次回は「コミットメッセージのチェック」について記載しようと思います。

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

【Vue.js】Storybookでコンポーネント開発を便利にする

はじめに

今回もVue.jsネタを。(本当はもっとAWSやServerless Frameworkなどバックエンド的なネタを書きたい。てか仕事でもそうしたい...)

最近、フロント側を触る機会があるのですが、コンポーネントベースでの開発(いわゆる「コンポーネント駆動開発(CDD)」)を行う事が多いです。(特にReactでは多いのかも)
ただその際、コンポーネントを実際にアプリに組み込まないと確認ができないので(あくまで「コンポーネント」なので)、どうしたものかなあ...と思ってました。

しかし「Storybook」というツールを使えば、コンポーネント単体での動作確認が可能になります。
今回はその「Storybook」についての記事です。

おことわり

storybookに関するブログはほとんどがReactなので、私はVue.jsを使おうと思います。(Reactは他の方の記事がいっぱいありますし)

Storybookについての詳細は、Storybook公式サイト(下記)を参照してください。
https://storybook.js.org/

インストール&初期設定

インストール
ルートフォルダで下記コマンドを指定すればOKです。

※Vue.js3のサポートは現在も実装中とのことなので、動かない機能があるかも。
参照: Install Storybook (「troubleshooting」を参照)

# storybookのインストール(言語(=Vue.js)を自動で判別する)
> npx sb init  
  
# 明示的にVue2であることを指定してインストールする
> npx sb init --type vue  
   
# 明示的にVue3であることを指定してインストールする  
> npx sb init --type vue

その後、下記コマンドを実行して正常に起動すればOK。

※私の環境(Windows10 + WSL2 Ubuntu)では、一度PCを再起動しないとダメでしたので、うまく起動しないという場合、一度PC再起動した方が良いかも。

# もちろんnpm run → yarnでもOK 
> npm run storybook  

初期設定
「.storybook/main.js」ファイルを編集して「stories」に開発フォルダを追加する。
こうしないと、開発フォルダ内のストーリーファイル(後述)を認識してくれない。

※「.storybook/main.js」ファイルは、Storybookインストール時に自動で作成される

module.exports = {
  "stories": [  
    // デフォルトはこの2つのみ
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)",  
      
    // 自分の開発環境をお好みで追加すると便利 
    "../makky12/src/components/**/*.stories.mdx",
    "../makky12/src/components/**/*.stories.@(js|jsx|ts|tsx)",
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ],
  "framework": "@storybook/vue"
}

ストーリーコードを書く

Storybookでの動作確認は、「ストーリー」(=テストパターンのようなもの)をストーリーファイルに定義することで行えます。(ストーリーについての詳細は、下記公式サイトを参照)

上記サイトにもある通り、ストーリーの基本の流れは以下の通りです。

  • 対象コンポーネントをimportする
  • 対象コンポーネントを使用したテンプレートを作成する
  • 上記テンプレートをbindする
  • 必要に応じて、argsに各種propsの値を設定する

百聞は一見に如かずということで、サンプルを作成しました。(Sample.jsが対象のコンポーネント、Sample.stories.jsがそのストーリー定義ファイルです)

※ストーリーファイル名が先述の「.storybook/main.js」ファイルの「stories」に記載のパターンとマッチしていないと、Storybookの画面に表示されないので注意です。

// Sample.js
<template>
  <div>
    <input type="text" :placeholder="placeHolder" :value="defaultValue" @change="onChange" />
  </div>
</template>
  
<script>
export default {
  name:"Sample",
  props: {
    placeHolder: {
      type: String,
      required: false,
      default: ""
    },
    defaultValue: {
      type: String,
      required: false,
      default: ""
    },
  },
  
  data: function () {
    return {}
  },
  
  methods: {
    onChange(e) {
      this.$emit('onChange', e.target.value);
    },
  },
}
</script>
  
<style>
</style>
// Sample.stories.vue
import Sample from './Sample.vue';
  
const Template = (args, { argTypes }) => ({
  props: Object.keys(argTypes),
  components: { Sample },
  template:
    '<Sample @onChange="onChange" v-bind="$props" />',
});
  
export const noProps = Template.bind({});
export const withplaceHolder = Template.bind({});
withplaceHolder.args = {
  placeHolder: 'I am placeholder.'
};
  
export const withBoth = Template.bind({});
withBoth.args = {
  placeHolder: 'I am placeholder.',
  defaultValue: 'I am default value.'
};
  
export default {
  title: 'Example/Sample',
  component: Sample,
};

上記ストーリーファイルを定義してStorybookを起動すると、下記画面が表示されます。

左側の「Example」内に「Stories」(=export defaultのtitle)が表示され、その下に定義した各ストーリーの変数名が表示されています。(単語単位で区切られるみたいですね)

※Sample以外は、デフォルトでインストールされるストーリーです。

各ストーリーの変数名をクリックすると、ちゃんとargsに応じたpropsの値が反映されていることが分かります。(With Bothは初期値を設定しているので、placeholderは表示されませんが)

f:id:Makky12:20211226190147p:plain
f:id:Makky12:20211226190526p:plain
f:id:Makky12:20211226190538p:plain

また、画面下部の「Controls」タブ内の「Props」に各ストーリーで渡したpropsが表示されており、ここの値を変更することでpropsの値を直接変更することができます。

f:id:Makky12:20211230203555p:plain

Actionsについて

またStorybookには「Actions」というものがあり、これを用いることで、emitしたイベントの確認ができます。

emitを確認する場合、特別なことは必要なく、先述のサンプルソースのようにテンプレートに通常のVue.jsのemitと同じように定義することで、イベントの発火を確認できます。

その場合、画面下部の「Actions」タブにemit関数に渡された値が表示されます。(テンプレートの「@emit変数名="XXX"」のXXXの箇所が、「Actions」タブのキーとして表示されます)

※詳細は、デフォルトでインストールされるストーリーの「Button」のソースを見るとわかります。
f:id:Makky12:20211226191452p:plain

ちなみに、こちらのページに記載のやり方でも、emitを確認できます。(こちらはReactなどでも行うやり方)
ただしこちらの場合でも、通常のVue.jsのemitと同じ定義は必要です。

Actions

こちらの方法の場合、下記が必要になります。(ただしsb initコマンドでインストールすれば、最初から下記が完了した状態になっています。)

  • 「@storybook/addon-essentials」のインストール
  • 「.storybook/main.js」ファイルのaddonsに「@storybook/addon-essentials」を追加する
    • 先述の「.storybook/main.js」ソースを参照

参照:https://storybook.js.org/docs/vue/essentials/introduction

// export defaultのargTypesで指定する方法(全ストーリーに適用)  
export default {
  title: 'Example/Sample',
  component: Sample,
  argTypes: {
    onChange: { action: 'changed' }
  },
};  
  
// actionを@storybook/addon-essentialsからインポートする方法  
// (ストーリー個別に適用可能)  
import { action } from '@storybook/addon-actions';  
  
export const withBoth = Template.bind({});
withBoth.args = {
  placeHolder: 'I am placeholder.',
  defaultValue: 'I am default value.',
  methods: {
    onChange: action('change'),
  },
};

まとめ

以上、急ぎ足でしたが、Storybookの使い方でした。

自分も最近Storybookのことを知った...というかあまりフロント側のテストや確認については知らなかったので、便利だなあと思いました。

てか、かつて関わったVue.jsの業務をやった時にこれを知っていればなあ...と思ったりします。

でもやっぱり、本当はもっとAWSやServerless Frameworkなどの技術に携わりたいなあ...

それでは、良いお年を!

【Vue.js】v-forループでduplicated key警告が出る場合の対処法

※ 2021/12/10 19:00 一部内容を訂正しました

はじめに

前回に続き、今回もVue.jsネタを。

配列などの値を<template>内で表示する場合、v-forなどのループを用いることは多いと思います。

そしてその際、配列のキーとなる値(≒key)が重複している値があると「duplicated key(=キーの重複)」警告が出ます。(エラーではない)

ただしこれがなぜか「扱う値に重複がないはずなのに出る」ケースがあります。

今回はそれについての原因と対応策です。

値が重複している

まず大前提として、配列内のkey値が重複している場合、それを<template>内でv-forループさせると「duplicated key」警告が出ます。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <ul>
      <!-- sampleFuncが実行された際、id=3が重複するので、duplicated keyが発生する -->
      <li v-for="item in items" :key="item.id">{{ item.value }}</li>
    </ul>
  </div>
</template>  
  
<script>
export default {
  data: function () {
    return {
      items: [
        {
          id: 1,
          value: 'hoge'
        },
        {
          id: 2,
          value: 'fuga'
        },
        {
          id: 3,
          value: 'piyo'
        },
      ],
    }
  },
  
  methods: {
    sampleFunc: function() {  
      // 重複した値を挿入する
      this.items.push(
        {
          id: 3,
          value: 'piyo'
        }
      );
    },
  }
}
</script>

これに関しては言うまでもなく「keyの値を重複させない」ようにすればよいです。
※よくあるのは「配列の初期化し忘れ」です。(といっても、意外と忘れがち...)

// こんな感じで、再設定前に一度初期化してあげる
items.splice(0, items2.length);

本題

で、次が本題。

下記のようなソースがあったとします。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <!-- itemsとitems2は配列もspanタグも別物...のはずなのに、これもduplicated keyが発生する -->
    <span v-for="item in items" :key="item.id">{{ item.value }}</span>
    <span v-for="item2 in items2" :key="item2.id">{{ item2.value }}</span>
  </div>
</template>  
  
<script>
export default {
  data: function () {
    return {
      items: [
        {
          id: 1,
          value: 'hoge'
        },
        {
          id: 2,
          value: 'fuga'
        },
        {
          id: 3,
          value: 'piyo'
        },
      ],
      items2: [
        {
          id: 3,
          value: 'foo'
        },
        {
          id: 4,
          value: 'bar'
        },
      ],
    }
  }
}
</script>

itemsとitems2は配列もspanタグも別物...のはずなのに、<template>内のコメントで書いたように、これもduplicated keyが発生してしまいます。

なぜかというと、Vue.jsのv-forループのkeyは、「ある要素の子要素内全体で重複があってはならない」からです。

つまり上記ソースの例では、<div id="app">の子要素である<span>2つ(と<input>)のすべてで、同じ値のkeyがあってはならないのです。(itemsとitems2両方にid=3があるため、duplicated keyが発生する)

ここは盲点&面倒くさい点だと思います。

対策方法

対策方法ですが、とりあえず以下の2つくらいでしょうか?

  • keyにindexを使う
  • かぶらない値を設定する

keyにindexを使う
v-forのindex(=配列のindex)を使えば、とりあえずduplicated keyは回避できます。
しかしindexは配列要素の追加や削除で変わるので(=indexに対応する値が変わる)、あまりお勧めできません。

【2021/12/10 19:00追記】
indexを使うと必ず0始まりになるので、むしろduplicated keyが頻発してしまいますね。失礼しました。

なおduplicated keyが起こらないケースでも「indexは配列要素の追加や削除で変わるので(=indexに対応する値が変わる)、あまりお勧めできない」という事は変わりません。

【参考ページ】

かぶらない値を設定する
「かぶらない値を設定する」ですが、まず実際にDBなどに保存している値レベルで対応するのは困難だと思います。(0, 1, 2...など、各テーブル単位で数値などを設定するのが多いと思うので)

そうなると...なんかイケてない感がありますが、こんな感じにkey名を無理やり一意にする...しかないのかなあ、という感じです。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <!-- JavaScriptは同じなので省略 -->
    <!-- keyを無理やり絶対に重複しない値にする -->
    <span v-for="item in items" :key="`items-${item.id}`">{{ item.value }}</span>
    <span v-for="item2 in items2" :key="`items2-${item2.id}`">{{ item2.value }}</span>
  </div>
</template>  

github.com

なんかいい方法ないかなあ、という感じです。(Vue.js3.xではどうなんだろう)

短めですが、今回はここまで。

【Vue.js】data変数の値の変更が画面に反映されない場合の対処

はじめに

Vue.jsでは、data()関数の戻り値に設定したオブジェクトのキー(以下「data変数」と記載)について、method内の各関数などで値を変更すると、それが画面に即座に反映されます。

<script>
export default {
  data() {
    return {
      hoge: 'aaa'
    }
  },
  
  methods: {
    example(val) {
      // valの値が即座に画面に反映される。
      this.hoge = val;
    }
  }
}
</script>

しかし、一部のケースで値の変更が画面に反映されないケースがあります。
そのケースの説明と対処方法です。

参考サイト(どちらもVue.js公式サイト)

オブジェクトと配列

上の題にもある通り、オブジェクトと配列は、変更が画面に反映されないケースがあります。

オブジェクト

オブジェクトについて、下記のように「data変数で定義したオブジェクトにキーを追加(または削除)した」場合、それは画面に反映されません。(ただし、data変数で定義済のキーの値の変更は、即座に反映されます)

なお、キーの追加や削除をした場合もあくまで「即座に画面に反映されない」だけで、追加や削除自体は有効になっていますので、そのあとに何かしら画面の更新が行われた場合は、ちゃんとそれらは画面上の値にも反映されます(これは配列も同様です)。

<script>
export default {
  data() {
    return {
      hoge: {
        foo: 'bar',
        foofoo: 'barbar'
      }
    }
  },
  
  methods: {
    example(val) {
      // キーを新たに追加しても、それは画面に反映されない。
      this.hoge.fuga = val;  
      
      // キーの削除も同様  
      delete this.hoge.foofoo;  
        
      // data変数で定義済みのキーの値の変更はOK。  
      // この時点で、上記のキーの追加/削除が画面に反映される。  
      // 画面にはthis.hoge = {foo: val, fuga: val} の状態で表示される
      this.hoge.foo = val;  
    }
  }
}
</script>

Vue.jsでキーの追加(または削除)を即座に画面に反映する場合、「Vue.set」または「vm.$set(this.$set)」関数を使う必要があります。(vm.$setはVue.setのエイリアス)

<script>
  methods: {
    example(val) {
      // これならOK。  
      // ちなみに引数は順に「data変数名」「追加するキー名」「該当キーの値」
      this.$set(this.hoge, 'fuga', val);  
    }
  }
}
</script>

また、複数のキー&値を割り当てたい場合、下記のようにObject.assign()の戻り値をdata変数に代入すればOKです。(Object.assign()単体ではダメ)

<script>
  methods: {
    example() {  
      // こんな感じ。  
      // Object.assign単体ではなく、戻り値を代入する。
      this.hoge = Object.assign({}, this.hoge, { fuga: fuga, barbar: 'barbar', piyopiyo: 'piyopiyo' });  
      
      // 下記はNG。(画面に反映されない)  
      // Object.assign(this.hoge, { fuga: fuga, barbar: 'barbar', piyopiyo: 'piyopiyo' });
    }
  }
}
</script>

配列

配列については、下記の変更は検知できません。

  • 配列の(特定インデックスの)値の変更
  • 配列の長さ(length)のみの変更(下記参照)
    • push/popなどで要素数(=値)の追加/削除などを行った場合は、反映されます。
<script>
export default {
  data() {
    return {
      hoge: ['foo', 'bar', 'fuga'];
    }
  },
  
  methods: {
    example(val) {  
      // 以下は、反映されない。
      // 配列の(特定インデックスの)値の変更
      this.hoge[1] = val;  
      
      // 配列の要素数(length)のみの変更
      this.hoge.length = 6;  
  
      // 配列操作関数(Array.prototype.XXX)を使った配列の要素数の  
      // 追加、変更などはOK  
      this.hoge.pop();  
    }
  }
}
</script>

配列の変更を即座に反映するには(すでに若干ネタバレしてますが)、下記のような配列操作関数(Array.prototype.XXX)を使います。

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

また、先述の「Vue.set」または「vm.$set(this.$set)」関数でもできます。

<script>
export default {
  data() {
    return {
      hoge: ['foo', 'bar', 'fuga'];
    }
  },
  
  methods: {
    example(val) {  
      // 「Vue.set」または「vm.$set(this.$set)」関数を使う場合  
      // 引数は順に「data変数名」「値を変更するindex」「該当indexの値」
      this.$set(this.hoge, 1, val);
  
      // 配列操作関数を使う場合  
      // なお公式ページ、および各種ブログなどでは  
      // splice関数が推奨されている。  
      // splice関数については後述
      this.hoge.splice(1, 1, val);  
      
      // 配列の要素数(length)のみを変える場合
      this.hoge.splice(this.hoge.length, 0, "", "", "");
    }
  }
}
</script>

splice関数について

公式ページ、および各種ブログなどで推奨されているsplice関数ですが、下記の関数になります。(ちなみに僕は、この現象を調査するまで、splice関数の存在自体知りませんでした)

splice(start, deleteCount, item1, item2, ...itemN)

引数 説明 備考
start 操作(追加/削除等)を行う基準となる位置(=index)
deleteCount 削除する要素数 省略可
item1, item2, ...itemN 追加する要素(複数指定可能) 省略可

【参考】 Array.prototype.splice() - JavaScript | MDN

例として、下記ソースは以下のように動作します。

  • startが1なので、index=1の場所(「fuga」の場所)を基準位置とする。
  • deleteCountが2なので、index=1の場所から要素を2つ削除する。(「fuga」「piyo」が削除される)
  • itemに'foo', 'bar', 'foofoo'の3つが指定されているので、index=1の位置に順に'foo', 'bar', 'foofoo'の3つを挿入する。
  • 最終的に、arrayは['hoge', 'foo', 'bar', 'foofoo', 'hogehoge']となります。
example(val) {  
  const array = ['hoge', 'fuga', 'piyo', 'hogehoge'];  
  array.splice(1, 2, 'foo', 'bar', 'foofoo');
}

なお、deleteCountは省略可ですが、値の追加のみを行う場合に省略すると分かりにくいので、明示的に0を指定した方が良いと思います。(特に数値を追加する場合)

またsplice関数を下記のように使うことで、startに指定したindexの値の変更を行うこともできます。(実際はdelete→insertですが)

example(val) {
  // index=1の値(=fuga)を「foofoo」に変更(delete→insert)する  
  const array = ['hoge', 'fuga', 'piyo', 'hogehoge'];  
  array.splice(1, 1, 'foofoo');
}

【JAWS-UG】JAWS PANKRATION 2021で登壇しました。

はじめに

先日の2021/11/20(土) ~ 2021/11/21(日)に、AWS User Group Japan(通称JAWS-UG)主催のイベント「JAWS PANKRATION 2021」が開催されました。

jawspankration2021.jaws-ug.jp

そしてこのイベントで「API GatewayAWS AppSync の使い分け」というタイトルで、15分程度の登壇を行いました。(ちなみにこれ、AWS Dev Day Online JapanのCFPでお祈りされたネタだったりします)

今回は、その感想や補足について書きたいと思います。

※登壇資料はこちら

www.slideshare.net

JAWS PANKRATION 2021とは?

JAWS-UG開催のAWSイベントで、AWSに関する様々なセッションを24時間にわたりAWSユーザー、AWS HERO、AWSの中の人などが行いました。

前回のJAWS SONIC 2020と違うのは、日本国内にとどまらず、全世界の人が対象ということです。(実際、外国人の登壇者も多数いました)

そのため、ソースネクスト社の「POCKETALK」を使い、登壇動画中に同時翻訳の字幕がリアルタイムで流れるのが特徴です。

※なお、これを実現しているAWSのアーキについては、JAWS PANKRATION 2021運営者&AWS Serverless Heroの松井 英俊さんのTwitterの固定ツイートに載ってますので、そちらをご参照ください。てかすごい...

Hidetoshi Matsui | AWS Serverless Hero

感想&裏話

24時間イベント
いや本当に運営の皆様、お疲れさまでした&ありがとうございました、です。

てかあれだけの規模&時間のイベントで、大きなトラブルが一切なかったのが本当にすごい。

世界中の方のセッションが聴けた
日本人だけではなく、外国人のエンジニアの方のお話が聴けたのが良かったです。

本当にいろいろな国の方がAWSについて勉強しているんだなあと思いましたし、いろいろ勉強になりました。

ただ色々あったのか、セッションをすっぽかしてしまった方も何人かいました...

POCKETALKによる同時翻訳
やっぱり今回はこれでしょうね。字幕による同時翻訳です。

ちなみに、実際はPOCKETALKを使い、こんな感じでやってました。

  • ボタンを押す
  • 日本語をしゃべる→サーバーに送信される
  • ボタンを離す
  • POCKETALKが翻訳する→サーバーに送信される

f:id:Makky12:20211127202444p:plain

こんな感じで「ボタンを押し忘れていないか」とか「翻訳が送信されているか」を専用の画面で確認しながらやっていたので、動画を見ている方からすると「なんか間が長いなあ」とか「話し方がぎこちないなあ」と感じたかもしれません。

また、個人リハでPOCKETALKの翻訳具合や時間を事前に確認していたのですが、僕の場合はちょっと遅めに話さないとうまく翻訳できないことがあり、話すスピードが遅かったため、なおさらぎこちなく感じたかもしれません。

あと、どうしてもうまく翻訳できない言葉(AWSの固有名詞など)がありましたが、これは仕方ないですね。
特に「AppSync」はどうしてもダメでした。Lambdaはうまく訳せたのですが...(「無駄」と訳すケースもたびたびありましたが)

登壇を終えて

いや、登壇して本当に良かったと思いました。
JAWS-UGさんのイベントだと、3月のJAWS DAYS以来になるのかな?

というか、こういうイベントに参加&登壇することで、否が応でも勉強する機会になりますし、そうすることでインプット&アウトプットを行うことになり、自分のスキルアップにもつながります。

またそういう機会をもらうことで、いい刺激になりますし、自分のモチベーションも「あ、もっとスキルアップしなきゃ」という感じになります。

特にここ1か月間は仕事でバタバタしていたり、やたら疲労がたまってたり、ちょっとトラブルなどがあったりして、スキルアップの方がおろそかになっていたのですが、今回JAWS PANKRATION 2021での登壇機会を得たことで、気分を新たに「またやらなきゃ」という気持ちになりましたので、本当に良かったと思います。

登壇の機会をくださった関係者の方々、そして視聴してくださった方々、本当にありがとうございました。

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