Wiz テックブログ

Wizは、最新のIoTやICTサービスをお客様に届ける「ITの総合商社」です。

gitでpushする前にコミットを整理する

こんにちは。バックエンドエンジニアの河内です。

今回は、コミットを整理する方法のうち、個人的に使用頻度が高いものについて書きます。

前提方針

コミットログを整えるために編集を加えるのは、ローカルリポジトリのコミットだけとします。 逆に言うと、リモートリポジトリにpushされたコミットについては編集を加えないということです。

たとえば、以下リモートリポジトリで(C)が最新の場合、 ローカルリポジトリで編集してよいのは(d)と(e)です。

[リモートリポジトリ]

※ 行頭の(A)などの識別子は説明のため加えており、実際には表示されません

(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

[自分のローカルリポジトリ

(e) 58e6b3a コミットe ← 編集してもよい
(d) 3c36383 コミットd ← 編集してもよい
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

各行頭の識別子に関し、ローカルのコミットログについてはアルファベット小文字で表現しました。 後述の事例でも区別のため、そのルールで表記します。

よくある事例とその対処

最新のコミットに対して

(1) 最新のコミットのメッセージを間違えた

以下のコミットログを見てみると…「官僚」ではなく「完了」のような気がします。

(d)はローカルリポジトリのコミットなのでまだ間に合います。

git log --oneline

(d) ee17560 事例1用管理画面実装官僚
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

[対処]

git commit --amend

エディタが開くので、メッセージを「官僚」→「完了」と編集し、保存します。

事例1管理画面実装官僚

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#

:

修正できました。

ハッシュ値は変わります。既存コミットは上書き保存され、別物になりました。

git log --oneline

(d) 03a9339 事例1用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

前提方針でリモートリポジトリにpushされたコミットに編集を加えない、としたのは、このようにコミットが上書き保存される操作のためです。

上書きしたコミットを再pushした場合、上書きされる前のコミットをすでにローカルリポジトリに取り込んでいた別の作業者がいたら不整合が発生するので、なんらかのすり合わせ作業が必要となってしまいます。

次の事例に進みます。

(2) 現在の変更点を最新のコミットに含めたかった

git log --oneline

(d) ce9ed66 事例2用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

(d)のコミットのあとindex.phpを編集したが、この内容は(d)に含めたかった…とします。

[対処]

git add index.php
git commit --amend

まず、index.phpをインデックスに登録します。

git add index.php

そして、git commit --amendです。

すると、addした内容が(d)のコミットに含まれる形でコミットログの編集を求められます。 メッセージはこのままでよいので編集せずに保存。

事例2用管理画面実装完了

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#

:

コミットを増やさず保存できました。

git log --oneline

(d) 5d75642 事例2用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

ちなみに、(1)と(2)が同時に起こった場合、(2)でメッセージ編集をすればよいわけなので1回のgit commit --amendでいけますね。

最新のコミットより前のコミットに対して

(3) 最新のコミットの1つ前のコミットのメッセージを間違えた

(1)、(2)は最新のコミットに対しての話でしたが、それより前のコミットとなると別のアプローチをとる必要があります。

今回は、(d)のコミットメッセージを間違えているようです。

git log --oneline

(e) 0e647f2 リファクタ
(d) 7cfd024 事例3用管理画面実装官僚
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

[対処]

git rebase -i 32096c2 ※ 「r, reword」利用

git rebaseを使います。

git rebaseには他のブランチを巻き込む編集も存在しますが、今回は現行のブランチだけを対象とし過去のコミットに対してまとめて編集を加える、-i (--interactive)オプションを使います。

引数には、編集対象の1つ前のコミットを指定します。 今回は(d)の直前の(C)なので、ハッシュ値「32096c2」を指定しました*1

すると、以下のように表示されます。git logの順と違い、上から古い順である(d)、(e)の順で表示されます。

よく見ると、コメント部の「Commands:」にできることが書いています。 今回したいことは、コミットメッセージの編集なので、これには「r, reword」を使います。

(d) pick 7cfd024 事例3用管理画面実装官僚
(e) pick 0e647f2 リファクタ

# Rebase 32096c2..0e647f2 onto 32096c2 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

該当コミットの「pick」を「r」もしくは「reword」に置き換えます*2。 今回は短い「r」を使用します。置き換えたら保存します。

(d) r 7cfd024 事例3用管理画面実装官僚
(e) pick 0e647f2 リファクタ

:

すると、そのコミットログの編集を求められます。(1)の時と同様、メッセージ編集〜保存すると反映されます。

事例3用管理画面実装官僚

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#

:

(d)のハッシュ値のほかに、内容としては何も変更のなかったはずの(e)のハッシュ値も変更されています。 対象コミットの後続のコミットすべてのコミットについて、このように変更がなされます。

逆に、「(d)以降」を指定するためにgit rebase -iのオプションに(C)のハッシュ値を渡しましたが、(C)に影響はありません。

git log --oneline

(e) 0dad9a6 リファクタ
(d) 79377c3 事例3用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

(4) 現在の変更点を最新のコミットの1つ前のコミットに含めたかった

(2)と同様、index.phpを編集しましたが、この編集内容は(d)に含めたかったです。メッセージは正しそうです。

git log --oneline

(e) 2092de6 リファクタ
(d) b3fed10 事例4用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

[対処]

git add index.php
git commit
git rebase -i 32096c2 ※ 「f, fixup」利用

同じくgit rebase -iですが、「f, fixup」を使います。 まず、index.phpの編集内容をコミットします。

git add index.php
git commit

このあと(d)に取り込まれるので、コミットメッセージはなんでもよいです。

事例4用管理画面実装完了の作業漏れ

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#

:

(f)ができました。(f)を(d)に取り込みます。

git log --oneline

(f) adfec57 事例4用管理画面実装完了の作業漏れ
(e) 2092de6 リファクタ
(d) b3fed10 事例4用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

git rebase -i 32096c2と打つと編集モードになります。

(d) pick b3fed10 事例4用管理画面実装完了
(e) pick 2092de6 リファクタ
(f) pick adfec57 事例4用管理画面実装完了の作業漏れ

# Rebase 32096c2..adfec57 onto 32096c2 (3 commands)
#
# Commands:

:

含めたいコミット(f)を、含め先のコミット(d)の直下に移動します。そして、(f)の「pick」を「f」に書き換えて保存します。

(d) pick b3fed10 事例4用管理画面実装完了
(f) f adfec57 事例4用管理画面実装完了の作業漏れ
(e) pick 2092de6 リファクタ

# Rebase 32096c2..adfec57 onto 32096c2 (3 commands)
#
# Commands:

:

すると、(f)は(d)に吸収されます。ハッシュ値は、(d)以降のコミットについて変更されています。

git log --oneline

(e) 605cb37 リファクタ
(d) 533f9bb 事例4用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

――上記のフローを理解した上で、少しだけ早い方法を使うこともできます。

[少しだけ早い対処]

git add index.php
git commit --fixup=b3fed10
git rebase -i 32096c2 --autosquash

コミットの時に、含め先のコミット(d)のハッシュ値を--fixupで指定します。 すると、プレフィックスのついたコミットメッセージを持つコミットが生成されます。

git log --oneline

(f) b230d6b fixup! 事例4用管理画面実装完了の作業漏れ
(e) 2092de6 リファクタ
(d) b3fed10 事例4用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

そして、git rebase -i 32096c2のオプションに--autosquashをつけると、コミットの移動と「pick」の置き換えが自動でおこなわれた状態で編集モードに入ります。「pick」の置き換えは「f」ではなく正式な「fixup」となるようですね。

(d) pick b3fed10 事例4用管理画面実装完了
(f) fixup b230d6b fixup! 事例4用管理画面実装完了の作業漏れ
(e) pick 2092de6 リファクタ

# Rebase 32096c2..b230d6b onto 32096c2 (3 commands)
#
# Commands:

:

保存すれば、目的達成です。

(5) 現在の変更点を最新のコミットの1つ前のコミットに含め、メッセージも変更したい。

では、(2)で同時に(1)、(2)の操作をおこなうことを考えたように、(3)と(4)を同時におこないたい場合どうしたらよいでしょうか。もちろん、(3)と(4)を別々におこなうこともできますが、同時にもおこなえます。

[対処]

git add index.php
git commit
git rebase -i 32096c2 ※ 「s, squash」利用

git rebaseでの編集まで(4)と同じ流れです。

コミット。

git add index.php
git commit

メッセージ編集〜保存。

事例5用管理画面実装官僚の作業漏れ

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#

:

(f)ができました。

git log --oneline

(f) 547cd2b 事例5用管理画面実装官僚の作業漏れ
(e) 9a636f7 リファクタ
(d) 16b489d 事例5用管理画面実装官僚
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

(f)を(d)に取り込みます。git rebase -i 32096c2ですね。

含めたいコミット(f)を、含め先のコミット(d)の直下に移動するところまでは(4)と同じです。 ここで、(f)の「pick」を「s」に書き換えて保存するだけです。

(d) pick 事例5用管理画面実装官僚
(f) s 547cd2b 事例5用管理画面実装官僚の作業漏れ
(e) pick 9a636f7 リファクタ

# Rebase 32096c2..16b489d onto 32096c2 (3 commands)
#
# Commands:

:

すると、2つのメッセージが上下に配置された状態で編集モードに入ります。

# This is a combination of 2 commits.
# This is the 1st commit message:

事例5用管理画面実装官僚

# This is the commit message #2:

事例5用管理画面実装官僚の作業漏れ

# Please enter the commit message for your changes. Lines starting

:

上記のまま保存すると、以下のようなコミットメッセージになってしまいます。

事例5用管理画面実装官僚

事例5用管理画面実装官僚の作業漏れ

以下のように編集して保存します。

# This is a combination of 2 commits.
# This is the 1st commit message:

事例5用管理画面実装完了

# Please enter the commit message for your changes. Lines starting

:

すると、(f)は(d)に吸収されメッセージも編集されました。

git log --oneline

(e) 2536867 リファクタ
(d) 35bcb56 事例5用管理画面実装完了
(C) 32096c2 コミットC
(B) ae4f281 コミットB
(A) 6dcd4ce コミットA

まとめ

小分けのコミットを心がけると整理も容易になります。また、逆に整理を意識するとコミットを小分けするよう意識が向きます。

コミットログは今ままで自分が何をしてきたかの歴史となるので、きれいに記せていければよいのかな、と思います。

最後に

Wizではエンジニアを募集しております。 興味のある方、ぜひご覧下さい。

【フロントエンドエンジニア】

場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】

勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

*1:ハッシュ値に限らず、コミットを指し示せればよいのでHEAD^2といった指定でも可。

*2:ここのメッセージを編集しても反映されません。たまに、ついやってしまいます。

輪読会を開催した話とその感想

はじめまして、フロントエンドエンジニアの髙橋です。

 

バーチャルオフィス導入の影響もあり、Wiz社内でのオンラインのLT会もくもく会などイベントが増えてきております。

その一環でWizでは今期から毎週輪読会を行っており、その内容や感想などをご紹介したいと思います!

 

輪読会を開催した目的 

輪読会にはさまざまな効果がありますが、今回の目的は2つです!

 

- インプット / アウトプットの場を設ける

- 技術的なディスカッションをして、みんなで理解を深める

 

増えてきたとはいえ、社内でのこういったイベントはまだまだ少ないと感じています。(勉強会等に参加されている方は多いですが)

内容を理解し、話し合うことでただ「本を読む」以上の効果を期待して開催しました!

 

輪読会の内容

そもそも輪読会とは??

輪読会とは、複数人が同じ本を回し読み、お互いの意見などを話し合う会です!

Wizでは、slackでteam-rindokuというチャンネルを作り、(そのままですね…)

そこでアナウンスやアンケートをとったりしています。

f:id:sotq17:20210218200403p:plain


実際の内容(初回)

■対象書籍
リーダブルコード

■読書予定範囲
2章ずつ (おおよそ1時間)

■進め方
 - 1~2章順番に音読+軽くディスカッション
 - 読んでいて疑問点があれば、それについても話し合う
 - 準備は特に必要なし
 - 本は各自でご用意を

 

書籍は以下の理由でお馴染みのリーダブルコードにしました!

- 読んだことのある人も読み直すことで新しい気づきがあるため

- 読んだことのない人には自信を持って勧められる本のため

- 読んだ後のディスカッションがしやすいため

 

開催に際して、気をつけていること

- なるべく参加ハードルは低くする

自由参加ということもあり、参加ハードルが高ければ集まりづらく、続かない可能性があると思っています。

今回のリーダブルコード輪読会は準備不要にするなど、なるべく気軽に参加してもらえるようにしました!

 

- 最低開催人数を決めておく

開催日時によって参加できる人数にばらつきがあるので、最低このくらい集まったら開催する、といったボーダーを決めています。

そうすることで一定のペースで開催し続けることができたのではないかと思います。

 

感想

良かった点

■より深い理解になった

読書って途中から集中力が切れて、内容が頭に入らない部分が多いと思うのです。(私だけですかね…?)

人に聞かれるのでしっかり読まねば!という意識が働き、集中力を保ったまま読むことができました。一人の時よりも内容がしっかり残っている気がします。

また、難しい箇所が出ても、すぐに質問できるので疑問点が残りづらく、それも輪読会のメリットの一つだと感じています。

 

■ディスカッションでさらに理解が深まる

ディスカッションをすることで、自分とは違う感想を聞くことができたり、新しい発見をしたりと、考えの幅が広がるのではないかと思います!

「コード書いている時、リーダブルコード思い出しました!」

という声もあり、多少なりとも業務に還元できたのでは?と感じています。

 

また、ディスカッション時に脱線して話をしたり、わいわいしながら進められているので個人的には楽しみながら勉強できているので、このディスカッションが最も大切だと感じています。

 

■別の輪読会に派生すること

他のメンバーも useEffectガイド の輪読会を開催してくれるなど別の輪読会に派生しています!(嬉しい…)

個人的にもひとりで読み切るのが辛かったのですごく助かりました。

輪読会という方式が広がっていけば効率良く勉強できそうなので、どんどん広げていきたいですね。

 

課題

まだ探り探り進めているので、いくつか課題もあります。

 

■ディスカッションへの参加について

大体参加者が5人前後くらいで収まっているのですが、

人数が増えてくるとディスカッションに積極的に参加する人/しない人が別れてくる印象がありました。(この辺りはオンラインでやる難しさですね。。)

前回3人しか集まらなかった時は、全員で話ができたので

細かくチーム分けするのも面白いかも知れません。

 

■輪読会のスタイルについて

輪読会にも色々な方法があるようで、

- 担当を決めてまとめて発表するスタイル
- 本を順番に回し読むスタイル
- 問題のみ共有し、事前に読んできて話し合うスタイル

などがあります。

 

リーダブルコードなどの、概念的な読み物の場合は順番に読んでいく方式で良さそうですが、他の読み物の場合どうなのか?など、進め方の課題もまだまだ残っています。

 

おわりに

輪読会を通し、個人的には楽しく、刺激を受けながら勉強できています。

まだまだ課題はありますが、まずは継続して、より良い方式を模索していく予定です!

 

余談ですが、参加者の皆さんに取り扱いたい本のアンケートをとり、興味がある本がいっぱい出てきました。

全て輪読会で扱えないので自分一人でも読んでおきたいと思います…!

 

Wizではエンジニアを募集しております。

興味のある方、ぜひご覧下さい。

【フロントエンドエンジニア】 場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】 勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

 

 

バーチャルオフィスという働き方

はじめに

皆様こんにちは、フロントエンドエンジニアの松本です。

本日はWizクリエイティブチームの働き方についてご紹介したいと思います。

現在、Wizクリエイティブチームの全職種(ディレクター、デザイナー、フロントエンド、バックエンド、ライター)は会社に出社する必要がある場合を除き、基本的にフルリモートで勤務しております。

もちろん出社したい時は出社できるので、かなり柔軟な働き方で働かせて頂いております。

そんな中「バーチャルオフィス」を導入してみて感じたメリットやデメリットに関して共有したいと思います。 

バーチャルオフィスとは?

結論から言うと、ブラウザー上で動作するアバターやバーチャル空間を活用したワークスペースです。

リモートワークで失われたオフィス機能の代替を目的とした、1つの居場所を共有する体験を離れていても実現します。

 

バーチャルオフィスはoViceを導入しています。

oViceを導入する前はSpatialChatやRemoなどのトライアルを試験的に運用していました。

料金体系や機能的な部分を総合して、oViceに至ったと言う感じです。

基本的にバーチャルオフィス空間には、Wizのクリエイティブチームの全職種が滞在しています。

今回はoViceの機能的な部分の説明は割愛します。

イメージしやすいようにざっくり説明すると、以下の画像のようにアイコン同士の距離が近いとお互いの声が聞こえる仕組みになっています。

f:id:yukiji_03:20210215160632p:plain

バーチャルオフィスを導入した経緯

まず最初にフロントエンドチーム内で「気軽に雑談や相談ができるのっていいよね」ということで運用を開始しました。

バーチャルオフィス導入前のリモート環境では、Zoomを使っていました。

Zoomを使っていて課題と感じた部分は以下の3つです。

  • 気軽な相談ができない
  • 雑談の機会が極端に減った
  • 何か用がある時はSlackで連絡 ▶︎ Zoom立ち上げの手順が面倒

上記の理由から「実際のオフィスのように、すれ違って雑談や相談ができるような環境が理想的」ということがきっかけでスタートしました。

バーチャルオフィス活用術  

 弊社での活用例は以下の通りです。

  • 各職種の朝会
  • 1on1面談
  • ちょっとした打ち合わせや会
  • 社内LT会や勉強会、もくもく会など
  • 業務後の雑談など

バーチャルオフィスのメリットとして、実際のオフィスのような1on1ルームや会議室などの「個室」を設ける事ができます。

新たな取り組みとして、バーチャルオフィス上で4月新卒入社のインターン生の教育を行っています。

緊急事態宣言が出ている事もあり、リアル出社が難しい状況でも問題なく教育が行えています。

バーチャルオフィスを導入して良かったところ

良かったと思う主な理由は3つあります。

1. コミュニケーションのハードルが下がった

用がある時しか繋がないZoomに比べて、コミュニケーションのハードルが下がりました。

コミュニケーションがバーチャルオフィス上で完結します。

実際のオフィスのように「近づいて話す」だけで、シームレスにコミュニケーションが取れる環境が非常に気に入ってます。

また、クリエティブチームは東京と福岡が主な拠点なので、プロジェクト内でデザイナーが福岡、エンジニアは東京といった事がよくあります。

バーチャルオフィス導入後は、拠点関係なく全員バーチャルオフィスに滞在しているので、他職種とのコミュニケーションのハードルも下がったと思います。

 

2. コミュニーケーションが増えた

これはコミュニケーションのハードルが下がった事で、単純にコミュニケーションをする機会が増えました。技術的な相談や雑談の機会が増えた事で、リモート環境でも孤独を感じず、リアルなオフィスのように働く事が可能となりました。

 

3. 勉強会などのイベントが活発になった

バーチャルオフィスでライトに集まれるということで、社内での勉強会も活発になりました。

LT会、もくもく会、技術書の輪読会など、社内イベントが活発になったと思います。

バーチャルオフィスの課題

良かったところはあるものの、課題と感じる部分もあります。

  1. 基本アイコンなので顔を見る機会が減る

カメラをオンにする事で顔を見ながら会話する事が可能ではあるものの、アイコンで近づくだけで会話が可能な"シームレスの快適さ"から、音声だけで会話をやり取りする事が多いです。顔を見ながら話した方が良さそうな1on1では、会議室に入ってカメラをオンにしています。

 

  1. 心理的安全性が確保されているのが条件

既存の社員で、お互いの顔と人となりを把握している人同士は良いものの、新入社員の方で、既存社員の顔やキャラクターが分からない状況では、コミュニケーションのハードルが高いと感じました。このような問題を解決すべく、新入社員の方が入社されたタイミングで、一度はカメラをオンにして自己紹介を行う施策を全職種間で行っています。今後も心理的安全性を確保できるような施策を考えていく事は課題だと感じました。

まとめ

バーチャルオフィスを導入する事で、リモートワークで起こりがちなコミュニケーションロスや孤独感などをあまり感じなくなりました。

しかし、新入社員の方など、バーチャルオフィスに慣れていない方へのケアは継続して行っていく必要があると感じています。

また、バーチャル空間の市場がまだまだこれからなので、新機能の追加やUXの向上なども楽しみであります。

最後まで見て頂きありがとうございました。

 

Wizではエンジニアを募集しております。

興味のある方、ぜひご覧下さい。

【フロントエンドエンジニア】 場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】 勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

 


TerraformでCodeDeploy+CodePipeline (GitHub Ver2)を実装する

こんにちは。バックエンドエンジニアの高橋です。

あるプロジェクトで実装したCodeDeployとCodePipelineを使ったデプロイをTerraform化したので、その実装例を紹介したいと思います。

バランサーとEC2

f:id:wiz_tak:20210212143033j:plain

今回はバランサーに複数のウェブサーバがぶら下がっていてそこにインプレースデプロイする構成とします。

デプロイフロー

f:id:wiz_tak:20210212114735j:plain CodePipelineソースアクションのGitHubバージョンですが、1が非推奨の為バージョン2で実装します。

※ Terraformでは、バージョン2のGitHub接続の為のCodeStartConnectionが一部使えないので注意(後述)

Terraformで実装

前提

VPC、EC2、ALBといったリソースは既に構築済みである事とします。

また各環境とモジュールのそれぞれに必要な変数が設定されているものとします。

ディレクトリ構成

.
├── envs
│   ├── develop
│   │   ├── backend.tf
│   │   ├── deploy
│   │   │   ├── backend.tf
│   │   │   ├── main.tf
│   │   │   └── variables.tf
│   │   ├── main.tf
│   │   └── variables.tf
└── modules
    └── deploy
        ├── codedeploy
        │   ├── main.tf
        │   └── output.tf
        ├── codepipeline
        │   └── main.tf
        └── provider
            └── main.tf

CodeDeploy

必要なリソースのインポート

$ terraform import module.codedeploy.aws_vpc.main_vpc vpc-0******
$ terraform import module.codedeploy.aws_lb_target_group.alb_target_group arn:aws:******
$ terraform import module.codedeploy.aws_iam_role.ec2_deploy_role RoleNameSample

①インポートしたリソースの定義

// VPC
resource "aws_vpc" "main_vpc" {
  ~~
}

// ALB Target Group
resource "aws_lb_target_group" "alb_target_group" {
  ~~
}

// EC2にアタッチしたロール
resource "aws_iam_role" "ec2_attach_role" {
  ~~
}

②デプロイアプリケーションとデプロイグループの作成

// アプリケーション作成
resource "aws_codedeploy_app" "deploy_application" {
  compute_platform = "Server"
  name             = "${var.project}-deploy"
}

// デプロイグループ作成
resource "aws_codedeploy_deployment_group" "deploy_group" {
  app_name               = aws_codedeploy_app.deploy_application.name
  deployment_group_name  = "${var.stage}-deploy-group"
  service_role_arn       = aws_iam_role.ec2_attach_role.arn
  deployment_config_name = "CodeDeployDefault.OneAtATime"

  // デプロイ対象のEC2タグ
  ec2_tag_set {
    ec2_tag_filter {
      key   = "Deploy"
      type  = "KEY_AND_VALUE"
      value = var.stage
    }
  }

  // 失敗時のロールバック
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }

  // ロードバランサーの有効化
  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "IN_PLACE"
  }

  // ターゲットグループ
  load_balancer_info {
    target_group_info {
      name = aws_lb_target_group.alb_target_group.name
    }
  }
}

CodePipeline

①IAMロール作成とポリシーアタッチ

ポリシーは長いので割愛します。

ActionにはCodeDeployやS3など必要なリソースを適宜セットしてください。

// IAMロール作成
resource "aws_iam_role" "codepipeline_role" {
  name = "${var.project}-${var.stage}-pipeline-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

// ポリシーの作成
resource "aws_iam_role_policy" "codepipeline_policy" {
  name = "${var.project}-${var.stage}-pipeline-policy"
  role = aws_iam_role.codepipeline_role.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "iam:PassRole"
      ],
      "Resource": "*",
      "Effect": "Allow",
      "Condition": {
        "StringEqualsIfExists": {
          "iam:PassedToService": [
            "cloudformation.amazonaws.com",
            "elasticbeanstalk.amazonaws.com",
            "ec2.amazonaws.com",
            "ecs-tasks.amazonaws.com"
          ]
        }
      }
    },
    ~~
}
EOF
}

②S3バケット作成

アーティファクトストアとなるS3バケットを作成します。

resource "aws_s3_bucket" "codepipeline_bucket" {
  bucket        = "${var.project}-${var.stage}-codepipeline-${var.region}"
  acl           = "private"
  force_destroy = false
}

③パイプライン作成

ソース/デプロイステージのみを作ります。

(ビルドステージは今回はスキップします)

~~
variable "codestar_connection_arn" {}
~~


resource "aws_codepipeline" "codepipeline" {
  name     = "${var.project}-${var.stage}-pipeline"
  role_arn = aws_iam_role.codesart_connection.arn

  // アーティファクトストア
  artifact_store {
    location = aws_s3_bucket.codepipeline_bucket.bucket
    type     = "S3"
  }

  // ソースステージ
  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["SourceArtifact"]

      configuration = {
        ConnectionArn    = var.codestar_connection_arn
        FullRepositoryId = var.repository
        BranchName       = var.repository_branch
      }
    }
  }

  // デプロイステージ
  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeploy"
      input_artifacts = ["SourceArtifact"]
      version         = "1"

      configuration = {
        ApplicationName     = var.codedeploy_application_name
        DeploymentGroupName = var.codedeploy_deploy_group_name
      }
    }
  }
}

aws_codestarconnections_connectionリソースについて

Terraformで保留状態になっており使用出来ません。

(2021年2月12日現在)

その為、事前にAWSコンソールのCodePipelineから適当にアプリケーションを作成し、発行されたarnを変数定義する必要があります。

f:id:wiz_tak:20210212135004p:plain

入出力アーティファクトについて

パイプラインの各ステージで必要な output_artifacts/input_artifacts の箇所は実装する際に少し迷いそうですが、

以下の図の様なワークフローを理解すると分かり易いのかなと思います。

f:id:wiz_tak:20210212135658j:plain

まとめ

Terraformで言語化すると作成するリソースにはどのリソースやコンポーネントが必要なのかといった全体像の把握が改めて出来る点が良いなと思っています。

ただ一部保留や未対応であったりと100%がTerraformで対応出来る訳ではない点が残念です。

最後に

Wizではエンジニアを募集しております。 興味のある方、ぜひご覧下さい。

【フロントエンドエンジニア】

場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】

勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

第2回LT会を行いました。

第2回LT会レポ

今回のLT会の内容は

発表者: 4名

制限時間: 発表時間 5分 + 質問時間 10分

テーマ: 自由

で行いました。

また今回はCommentScreenというツールを使用し、 リアルタイムで参加者のコメント確認できるようにしました。

f:id:nakamoto03:20210210195024p:plain
comment screen

CommentScreenを使用するとこのように画面にコメントが流れるようになります

それでは1つずつ発表を紹介していきます。

JSモジュールバンドラのこれからについて考える

f:id:nakamoto03:20210212085535p:plain
JSモジュールバンドらのこれからについて考える
1人目の方にはJSモジュールバンドラについて発表していただきました。
現状JSモジュールバンドラとして圧倒的にwebpackが使用されている中、esbuild、Snowpack(The faster frontend build tool)の関心/満足度が上がっており両者共にwebpackを上回ってます。(参照: State of JS 2020) そこでesbuildとSnowpackを実際に使い、その所感を発表していただきました。 詳しくはSnowpack 試してみましたにまとめられています。気になる方は、記事をみていただけると幸いです。  

モリーを認識したプログラム

2人目の方にはCPU・メモリ・ハードディスクなどの違いから、Javaにおけるインスタンスを生成した際のメモリ使用量、メモリの解放のタイミングなどエンジニアが最低限知っておくべき内容に関して紹介していただきました。

PHPのInterfaceの使いどころ

f:id:nakamoto03:20210211225453p:plain
Interface
3人目の方にはInterfaceを使い、「依存性の注入」を使う事で、あるクラスが依存している別のオブジェクトを外部から渡し、クラス間の依存度を下げる設計方法について、またそのメリットに関して紹介していただきました。 詳しくはPHPのInterface -メリットと使い所-にてInterfaceについて投稿されています。気になる方は、記事をみていただけると幸いです。

TypeScript導入のススメ

f:id:nakamoto03:20210211225351p:plain
TypeScript導入のススメ
ラストの方には、TypeScriptとは何か、JS=>TSへの移行ツールなどなどTypeScriptを導入するにあたってコストや、使用する際の最低限のルールについてといった内容を紹介していただきました。

おわり

第2回はこのような内容でした。

この記事ではざっくりとした内容しか紹介できませんでしたが、

LT会の内容をもっと社外へ公開できるよう目指していきたいです。

Wizではエンジニアを募集中です。

興味のある方は是非覗いてみてください!↓

【フロントエンドエンジニア】 場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】 勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

 

Vue3.0における状態管理(Vuex,Provide / inject)

こんにちは、フロントエンドエンジニアの小玉です。

今回は、Vue.jsにおける状態管理についてお話したいと思います。

Vue2.x時代、多くの方はVuexを使用していたのではないでしょうか?Vue2.x、Vue3.xともに公式のドキュメントにおいても、状態管理のセクションでVuexが紹介されています。
ただVue3.0以降は、Vuexに打って変わる状態管理方法がいくつか提唱されていますので、それぞれの内容を簡単に見つつ比較したいと思います。

Vuex

VuexVue.jsのアプリケーションのための状態管理ライブラリであり、単純なグローバルオブジェクトとは異なり、Vuexのストアはリアクティブです。
また、Vuexは以下のように単方向のデータフローになるので、変更される状態の追跡が明示的です。
vue-devtoolsを使用すると、getterで得られる値、mutationcommitした履歴などが確認できます。

vuex.png

vuex.vuejs.org

actions

  • stateを直接更新することはなく、mutationsを経由することでstateを更新する。
  • 非同期の処理を入れることができる。

mutations

  • actionsから受け取った値をstateにセット

mutationsでは単にstateの値を変更するだけの処理を行うことが多いかと思います。
それに対しactionsでは比較的複雑なロジックを記述しmutationsに対して値を返すイメージです。

実際のコード

今回は簡単なカウンターアプリを実装します。
SFC内部で全て完結させる場合は上記のような形になります。

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="decrement">-</button>
    <button @click="increment">+</button>
  </div>
</template>

<script lang="ts">
import { reactive, computed, defineComponent } from "vue";
export default defineComponent({
  name: "App",
  setup() {
    const state = reactive({
      count: 0,
    });
    const count = computed(() => state.count);
    return {
      count,
      increment() {
        state.count += 1;
      },
      decrement() {
        state.count -= 1;
      },
    };
  },
});
</script>

では、実際にVuexを使用し、コンポーネントを分割した場合はどのような記述になるでしょうか。
ディレクトリ構成は以下のような形で進めてみます。

/src
 ├ components
 │  ├ DecrementBtn.vue
 │  └ IncrementBtn.vue
 ├ store
 │  └ index.js
 └ App.vue

Vuexのストアの定義を以下のようにします。

▼store/index.js

import { createStore } from "vuex";

const state = {
  count: 0
};
const mutations = {
  increment(state) {
    state.count++;
  },
  decrement(state) {
    state.count--;
  }
};
const actions = {
  increment: ({ commit }) => commit("increment"),
  decrement: ({ commit }) => commit("decrement")
};

export default createStore({
  state,
  actions,
  mutations
});

▼親コンポーネント(App.vue)

<template>
  <div>
    <p>{{ count }}</p>
    <increment-btn />
    <decrement-btn />
  </div>
</template>

<script lang="ts">
import { computed, defineComponent } from "vue";
import { useStore } from "vuex";
import IncrementBtn from "./components/IncrementBtn.vue";
import DecrementBtn from "./components/DecrementBtn.vue";

export default defineComponent({
  components: {
    IncrementBtn,
    DecrementBtn,
  },
  setup() {
    const counter = useStore();
    const count = computed(() => counter.state.count);

    return {
      count,
    };
  },
});
</script>

▼子コンポーネント(DecrementBtn.vue)

<template>
  <button @click="decrement">+</button>
</template>
<script>
import { defineComponent } from "vue";
import { useStore } from "vuex";

export default defineComponent({
  setup() {
    const counter = useStore();
    return {
      decrement: () => counter.dispatch("decrement"),
    };
  },
});
</script>

実際の挙動の確認はこちら↓

codesandbox.io

簡単なアプリケーションということもありますが、コンポーネント間のストアへのアクセス方法や、データフローなど非常に明示的であることがわかるかと思います。

Provide / inject

Provide / injectは、これまでコンポーネントの階層の深さに関係なく、親コンポーネントから子階層へ依存関係を提供するプロバイダとして機能します。
これまでは親コンポーネントから子コンポーネントにデータを渡す際、propsを使用していたかと思います。
しかし、階層が深くなるにつれバケツリレーのように伝搬していくのは非常に面倒でした。

components_provide.png

v3.ja.vuejs.org

こちらはVue2.2から実装されているものでしたが、Vue3.0からはComposition APIのリリースに伴い注目されています。

Composition API

Composition APIは、ロジックをカプセル化することでコンポーネント間でのロジックの再利用を可能にするAPIです。
特に大規模なアプリケーションになると、Vue2.xまではViewとロジックが密結合になっており、SFCが冗長になってしまうことが問題でした。
しかし、Composition APIを活用することでSFCにはViewに関することのみを記述し、ロジックを外部に切り出すようなことも可能になります。

実際のコード

基本的なディレクトリ構成は変わらず、以下のような形。

/src
 ├ components
 │  ├ CountView.vue
 │  ├ DecrementBtn.vue
 │  ├ ParentProvider.vue
 │  └ IncrementBtn.vue
 ├ store
 │  └ index.ts
 │  └ key.ts
 └ App.vue

コンポーネント内でしていた状態管理の部分を切り抜き、ストアを作成しキーを定義します。

▼index.ts

import { reactive } from "vue";
export default function counterStore() {
  const state = reactive({
    count: 0
  });
  return {
    get count() {
      return state.count;
    },
    increment() {
      state.count += 1;
    },
    decrement() {
      state.count -= 1;
    }
  };
}
export type CounterStore = ReturnType<typeof counterStore>;

▼key.ts

import { InjectionKey } from "vue";
import { CounterStore } from "./index";

const CounterKey: InjectionKey<CounterStore> = Symbol("counterStore");
export default CounterKey;

provideは( key , value )を受け取り、provideされた値はコンポーネントからキーを用いてinjectを取り出せます。

▼親コンポーネント(ParentProvider.vue)

<template>
  <div>
    <slot />
  </div>
</template>

<script lang="ts">
import { provide } from "vue";
import counterStore from "../store/index";
import CounterKey from "../store/key";

export default {
  setup() {
    provide(CounterKey, counterStore());
    return {};
  },
};
</script>

これでParentProvider.vue以下のコンポーネントはどの階層においてもストアオブジェクトを受け取ることが可能です。

▼子コンポーネント(CountView.vue)

<template>
  <p>{{ count }}</p>
</template>
<script>
import { defineComponent, inject, computed } from "vue";
import CounterKey from "../store/key";

export default defineComponent({
  setup() {
    const counter = inject(CounterKey);
    const count = computed(() => counter.count);
    return {
      count,
    };
  },
});
</script>

ストアオブジェクトを受け取る側はinjectにキーを指定します。同様の記述でボタン関係も実装します。
最終的には以下のようにまとめることでVuexやSFC1つで記述していたものと同様の動きをします。

▼App.vue

<template>
  <div>
    <parent-provider>
      <count-view />
      <increment-btn />
      <decrement-btn />
    </parent-provider>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import ParentProvider from "./components/ParentProvider.vue";
import CountView from "./components/CountView.vue";
import IncrementBtn from "./components/IncrementBtn.vue";
import DecrementBtn from "./components/DecrementBtn.vue";

export default defineComponent({
  components: {
    ParentProvider,
    CountView,
    IncrementBtn,
    DecrementBtn,
  },
  setup() {
    return {};
  },
});
</script>

実際の挙動の確認はこちら↓

codesandbox.io

まとめ

provideは、今回のようなルートコンポーネント以外のコンポーネントからも使用することができます。
つまり、限定的な範囲でストアを定義することが可能となります。
Vuexではグローバルに定義する必要があったため、プロジェクトの規模によってはスコープの小さい状態管理ができるProvide / injectを使用するのもいいかもしれません。
一方で、Vuexは、Fluxでデータフローなどが決まっているため、チーム内でのガイドライン等を作る手間が少なく、さらにDevToolでのデバックができることも大きな利点かと思います。
チームとしてComposition APIを使用し、かつ規模の小さいプロジェクトの場合Provide / injectを使用し、大規模なプロジェクトの場合Vuexを使う。そんな選択が今後できるかと思います。

参考にさせていただいた記事等


最後になりますが、Wizではエンジニアを募集中です!

興味のある方は是非覗いてみてください↓

【フロントエンドエンジニア】
場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly

【バックエンドエンジニア】
勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly

Laravelで既存画像のWebP対応

今回はPHPのLaravelで既存画像のWebP化及び切替表示について実装例を紹介してみようと思います。

目的

記事の画像をWebP化することによって記事の表示スピードを早くする。

WebP(うぇっぴー)とは

軽量の新画像形式で開発したグーグルによると、画像をWebP化することによって可逆形式ではpngの26%、不可逆形式では25〜34%もファイルサイズを減らすことができると紹介されています。 詳細については[こちら]をご覧ください。

Chromeブラウザのみの対応だったWebP 画像が Edge, Firefox 等の最新版で使用でき、8割近いブラウザで使えるようになってきたそうです。 WebP対応ブラウザ確認は[こちら]をご覧ください。

たくさん画像を使う記事サイトで高速化のためにWebPを使うことが有効ですが、サポートしないブラウザもあるのでWebP対応ブラウザはWebPを、非対応ブラウザは既存画像(jpegpng等)を利用するなど切替表示の必要があります。

現状

1) 画像ファイルURL

画像ファイルURLは、認証制御の理由で実際のファイルURLを利用しないで下記のようにAPIで取得します。

src="https://xxxxx.jp/api/image/1"
2) 画像ファイルの情報

画像ファイルの情報は下記のようにDBのテーブルに格納しています。

id name context_type created_at
1 image01.jpg image/jpeg 2018-02-08 16:13:38
2 image01.png image/png 2018-02-08 16:13:59

App\Models\ImageFileには上記テーブルの項目が入っています。

実装手順

1) 画像の新規登録&変更登録時対応

管理画面で画像の新規登録&変更登録する際に、アップロードされた画像からWebP画像を作成し別途保存する処理を追加

2) 画像表示の切替処理

サイトで画像を表示する際に、WebP対応ブラウザかどうか判定してWebP対応ならWebP画像、非対応なら既存の画像タイプを表示するように切替処理を.htaccessで設定

3) 既存画像の対応

バッチで一括で既存画像をWebp化する仕組みを作る

前提条件

1) libwebpのインストール

下記のコマンドで

$ php -r 'print_r(gd_info());'

下記が表示される場合、サポートされていないのでlibwebpライブラリをインストールする必要があります。

[WebP Support] => 0 
或は
[WebP Support] => 

下記のコマンドでlibwebpをインストール

$ sudo yum --enablerepo=remi-php73 install php73-gd

下記のコマンドでapache再起動

$ service httpd reload

下記のコマンドで確認

$ php -r 'print_r(gd_info());'

下記が表示されればWebPがサポートされていると考えられます。

[WebP Support] => 1

2) LaravelにIntervention Imageを追加

Composer Installation

$ php composer.phar require intervention/image

config/app.phpにパッケージを登録

'providers' => [
    Intervention\Image\ImageServiceProvider::class,
]

'aliases' => [
    'Image' => Intervention\Image\Facades\Image::class,
]

下記のコマンドで、configフォルダ内にimage.phpという設定ファイルが作成されます。

Laravelでconfigファイルを公開(作成)する場合

$ php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravelRecent"

Laravelのバージョンが4以下の場合

$ php artisan config:publish intervention/image

これでLaravelでのIntervention Imageインストールは完了です。

              

1. 画像の新規&変更登録時対応

ルート

// 画像アップロード
Route::post('/image', 'ImageController@uploadImage');
// 画像取得
Route::get('/image/{id}', 'ImageController@getImage);

blade

Laravel:
{{ Form::file('image') }}

html:
<input name="image" type="file">

Controller

ImageController.php

<?php 
use App\Models\ImageFile;
use Intervention\Image\ImageManagerStatic as Image;

//画像アップロード
public function uploadImage(Request $request) {
        //  画像情報を取得
        $file = $request->file('image');
        
         // 画像情報をDBに保存
        $ImageFile = new ImageFile();
        $ImageFile->name            = $file->getClientOriginalName();
        $ImageFile->context_type  = $file->getMimeType();
        $ImageFile->created_at     = Carbon::now();
        $ImageFile->save();

        // storageフォルダ以下に画像を保存(物理的なファイルのURLがないパターン)
        $file->move(storage_path(config('system.path.imgae')), $ImageFile->id);

        // 画像を読み込む: make()
        $path = storage_path(config('system.path.imgae')).$ImageFile->id;
        $image = \Image::make($path);

        // WebPを作成
        $webp_path = storage_path(config('system.path.imgae').$ImageFile->id .'.webp');
        $image->save($webp_path);
}

2. 画像表示の切替処理

.htaccess処理

.htaccessで画像取得のURLが /image/{id}の場合、/image/{id}?webp=1にリダイレクトさせることによって、 ブラウザによって自動的に切り替えできるようにします。

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    RewriteCond %{HTTP_ACCEPT} image/webp
    RewriteCond %{REQUEST_URI} ^/image/[0-9]*$
    RewriteCond %{QUERY_STRING} !webp=[^&]*
    RewriteRule ^(.*)$ $1?webp=1 [L]
</IfModule>

.htaccessはできることが多いので良かったら[こちら]も参考に見てください。

画像取得処理

.httaccessのリダイレクトでURLに追加した?webp=1を取得し、対応ブラウザかWebPだったらWebPに切り替えるようにします。

ImageController.php

<?php
use App\Models\ImageFile;
use Intervention\Image\ImageManagerStatic as Image;

public function getImage(Request $request, $id) {
    
    // ファイル情報を取得
    $imageFile = ImageFile::find($id);
    if (!$imageFile) {
        return;
    }

    $contextType = $imageFile->context_type;
    $fileName = $imageFile->id;
    
    // 対応ブラウザかWebPだったらWebPに切り替える
    $webp = $request->input('webp');
    if($webp == true){
        $contextType = 'image/webp';
        $fileName = $imageFile->id . '.webp';
    }

    // ファイルをダウンロードさせる
    $headers = array('Content-Type' => $contextType, 'Content-Disposition' => 'inline; filename="'.$imageFile->name.'"');
    $pathToFile = storage_path(config('system.path.imgae')).$fileName;
    $response   = response()->file($pathToFile, $headers);

    return $response;
}

3. 既存画像のWebp化対応

バッチで一括で記事既存画像のWebp化の仕組みを作ります。

app/Console/Kernel.php

<?php
use App\Console\Commands\CreateWebpFile;
class Kernel extends ConsoleKernel
{
    protected $commands = [
        CreateWebpFile::class,
    ];
}

下記のコマンドでクラスを作成

$ php artisan make:command CreateWebpFile

app/Console/Commands/CreateWebpFile.php

<?php
namespace App\Console\Commands;
use Illuminate\Support\Facades\DB;
use Intervention\Image\ImageManagerStatic as Image;

class Kernel extends ConsoleKernel
{
    protected $signature = 'CreateWebpFile';
    
     public function handle()
    {
        $imgaeFile= DB::table('imgae_file')->get();
        if (!$imgaeFile) {
            return;
        }

        foreach ($imgaeFile as $image) {
            // id取得
            $imageId = $image->id;

            $Path = storage_path(config('system.path.imgae')) . $imageId;
            $PathWebp = $Path . '.webp';

            if(file_exists($Path) && !file_exists($PathWebp)){
                // webp保存
                $image = Image::make($Path)->orientate();
                $image->save($PathWebp);
            }
        }
    }
}

プロジェクトディレクトリに入って、下記のコマンドでWebPファイルが作成されました。

$ php artisan CreateWebpFile

さいごに

バックエンドエンジニアとしてWizに入社後、アプリケーション実装だけでなくSEO対策、SSL更新、メールサーバー構築、WEBサーバー構築など幅広く挑戦・成長できるチャンスをたくさんもらいました。

Wizではエンジニアを募集しております。 興味のある方、ぜひご覧下さい。

【フロントエンドエンジニア】 [場所にとらわれず自社メディア成長に貢献したいフロントエンドエンジニア募集! - 株式会社WizのWebエンジニアの求人 - Wantedly]

【バックエンドエンジニア】 勤務地自宅を叶える!バックエンドエンジニアとして事業を成長させたい方募集 - 株式会社WizのWebエンジニアの求人 - Wantedly