Wiz テックブログ

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

知ってると得するかもしれないCSS(Sass)のニッチなテクニック集

f:id:wiz_sasaki:20210927144411j:plain

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

今回は私が実際に実務で使用しているCSS(Sass)のちょっとしたテクニック集を紹介しようと思います。

否定疑似クラス「:not()」を利用した余白のとり方

:not():first-childを指定することで、最初の要素以外という指定ができます。

<ul class="list">
  <li class="list-item"></li>
  <li class="list-item"></li>
  <li class="list-item"></li>
  <li class="list-item"></li>
</ul>
.list-item {
  &:not(:first-child) {
    margin-top: 16px;
  }
}

これの何が良いのかというと、リストのようなレイアウトは最初もしくは最後の余白を0にしたいことがほとんどだと思います(他の要素への影響を最小限にするため)。 .list-itemに直接margin-topをとってしまうと.list-item:first-childmargin-topを打ち消す記述をしないといけません。:not()を使用することで不要な記述を減らすことができます。

「&」を利用した連続する同じ要素間の余白

ネスト内で& + & とすることで次の要素が同じだったらという指定ができます(ここでは.l-section)。

<section class="l-section">
  <h2>セクション1</h2>
</section>

<section class="l-section">
  <h2>セクション2</h2>
</section>

<section class="l-section">
  <h2>セクション3</h2>
</section>
.l-section {
  & + & {
    margin-top: 50px;
  }
}

先程の&:not(:first-child)に似ていますが、要素の間に別の要素が入って来た時の挙動が微妙に違います。

注意点としてこの方法はネストの親でのみ使用可能です。

ちなみに以下と同じ意味です。

.l-section {
  + .l-section {
    margin-top: 50px;
  }
}

詳細度を無理やり上げる

CSSには詳細度というのもが存在します。上書きするにはその詳細度を上回る必要があります。 !importantを使えば簡単に上書き可能ですが、宗教上の理由で!importantを使えない方がほとんどだと思います。以下はその代替方法です。

CSS詳細度計算サイト : https://specificity.keegan.st/

<div class="content">
  <p class="text">この文字の色は赤です。</p>
</div>
//詳細度 : 1, 1, 0
.text {
  &:not(#_) {
    color: red;
  }
}

//詳細度 : 0, 1, 0
.text {
  color: blue;
}

//詳細度 : 0, 2, 0
.content {
  .text {
    color: blue;
  }
}

上記のように&:not(#_)とすることで、.text#_ではないという指定になります。こうすることでIDを指定した時と同等の詳細度を得ることができます。

この方法のメリットは!importantを使用せずに高い詳細度を得ることにあります。AMPのような!importantが使用するとエラーが出る環境だと使う可能性があります。

可読性はあまり良くないので、そもそもこれを使わなくて済むようなCSS設計を心がけたほうが良いです。

他に有名な詳細度を上げる方法として:not(:root)という方法も存在します。

画像の比率を固定する

様々なデバイスに対応しないといけない現在のWEB制作において画像の比率を維持するということは非常に重要なことです。以下はpaddingの仕様を利用した画像の比率維持方法です。

ここでの画像は1920px * 1080pxを想定しています。

<div class="image-wrapper">
    <img src="/path/to/image.jpg" width="1920" height="1080" alt="比率固定したい画像">
</div>
.image-wrapper {
  position: relative;
  &::before {
    content: '';
    display: block;
    padding-top: calc((1080 / 1920) * 100%);
  }
  img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

上記のCSSで1920px * 1080pxの比率は画面の幅が変わっても維持されます。

ここで重要なのは疑似要素::beforepadding-topです。padding-topは%で指定するとその値は親要素の幅に対しての割合となります。

以下の計算式に当てはめると幅に対する高さの比率を求めることができます

(高さ / 幅) * 100%

これを利用して疑似要素で高さをとり、imgをabsoluteで浮かせて配置することで画像の比率の維持が可能です。

しかし、非常に可読性が悪くなるのでこういった面倒な計算をするものはmixin化すると使い勝手がよくなります。

@mixin aspect-ratio($width, $height, $first: true) {
  position: relative;
 
  &::before {
    content: '';
    display: block;
    padding-top: ($height / $width) * 100%;
  }

  @if $first == true {
    & > :first-child {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
  }
}
@include aspect-ratio(1920, 1080);

親要素を突き破って横幅100%

背景のみ横幅100%にして色をつけたいときなどに役に立ちます。

margin-right: calc(50% - 50vw);
margin-left: calc(50% - 50vw);

padding-right: calc(50vw - 50%);
padding-left: calc(50vw - 50%);

ポイントはpaddingで内要素の幅を保っていることです。ですのでpadding削除すると完全に横幅100%になります。

状況に応じて使い分けていくと良いでしょう。

否定形のメディアクエリ

not演算子を使うとメディアクエリの内容を反転することができます。

例えば、767px以下768以上のスタイルを書きたい場合があるとします。

.text { 
  @media screen and (min-width: 768px) { 
    color: red; 
  } 
}
.text { 
  @media screen and (max-width: 767px) { 
    color: blue; 
  } 
}

このように書きたくなりますが、これは特定の条件でスタイルが当たりません。

特定の条件とは768px〜767pxの間である画面幅が767.5pxのなどの場合はスタイルが当たりません。これはブラウザの拡大率や画面の解像度の組み合わせによって起こる可能があります。

.text { 
  @media screen and (min-width: 768px) { 
    color: red; 
  } 
}
.text { 
  @media not screen and (min-width: 768px) { 
    color: blue; 
  } 
}

上記のようにメディアクエリにnot演算子を組み合わせることによって画面幅が768pxより小さい場合という指定が可能です。

つまり画面幅が767.5pxの場合でもスタイルが当たります。

メディアクエリについての詳しい解説はコチラ

ユーザビリティを考慮したコンテンツの非表示方法

以下のように実装するとテキストは見えないけどスクリーンリーダーは読み上げ可能を実現できます。

<p class="visually-hidden">このテキストは見えないけどスクリーンリーダーは読み上げます。</p>
.visually-hidden {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%); 
  margin: -1px;
}

詳しくは下記サイトのHiding content visuallyの箇所をご確認ください。

https://medium.com/@matuzo/writing-css-with-accessibility-in-mind-8514a0007939

他のメリットとしてはdisplay: noneではないのでフォーカスが可能な点が挙げられます。

最後に

今回は「CSSのニッチなテクニック集」という内容でパッと思いついたものを紹介してみました。知っている人は「なんだそんなことか」と思われる内容だったかもしれません。

CSSはかなり自由に書けてしまうのでオレオレコーディングになりがちです。特に:not()を利用したものは便利な反面、知らない人は「ん?」ってなることが多いです。

他の人もCSSを触ることを考慮して複雑な内容ほどコメントを残しておくと良いでしょう。

それでは今回は以上となります。この記事が少しでも誰かの役の立てればいいなと思います。

また、

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

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

careers.012grp.co.jp

リモートで社会人スタートした新卒エンジニアの本音

アイキャッチ画像 こんにちは!フロントエンドエンジニアの島田です。
前回インターン研修について記事を書きましたが、今回はリモートワークについてお話しします。

研修・実務のほとんどをリモートで行ってきた新卒エンジニアが、リモートワークに対する本音を語っていきたいと思います。

1.リモートワークのメリット

早速良かった点からお話ししていきます!^^

その1 通勤ストレスがゼロ

まずこちらが真っ先に思い浮かびました。笑
通勤時間って、合計するとかなり時間がかかると思いませんか?
会社まで片道30分だとしても一日で60分、一週間で5時間も移動に時間を使っていることになりますよね…😓

しかし!これがリモートになると出勤や退勤で使う時間が0になる上に、満員電車のストレスからも逃れることができちゃいます!(大学へ行くのに電車で40分、片道1時間10分かけていた私からしましたら、本当に天国でした…!)

そして最も有り難かったのが、有効活用できる時間が大幅に増えたことです!
本来通勤に使う時間で机に向かって勉強したり、趣味に使ったりと…浮いた時間を自由に使っています。

その2 リラックスした環境で仕事できる

自分の家や部屋って落ち着きますよね。
リモートワークは出社する必要がないため、自分の部屋で仕事することができます!

落ち着いた環境で黙々と作業できるのは、集中できて良いと感じています。
時々の出社も、気分転換のような感じで新鮮で楽しいです!

その3 コミュニケーション量が増えた

私がWizに入社するまでは、

「リモートで仕事するって聞いたけど、会社の人とのコミュニケーションはどうなるのかな…?」
「リモートじゃ話す機会も少ないだろうし、先輩方や同期と仲良くなれるか心配だ…」

という、リモートワークに対する不安がいくつかありました。
が!実際に入社してから感じたことは、

「あれっ?全然問題なくコミュニケーション取れてる」

でした。笑

WizではoViceというツールを使って、業務に取り組んでいます。
有り難いことにここでは雑談会等、コミュニケーションの活性化を図るイベントが沢山行われています。そういうイベントに参加することで、色々な職種の方とお話しする機会があります。

それ以外でも、業務終わりにoViceに残って雑談することもあり、コミュニケーションが不足していると感じることはあまり無かったです!

2.リモートワークのデメリット

何かと便利なリモートワークですが、もちろん良い点ばかりではありません😰
ここでは、リモートで作業している際に「ここはちょっと大変だな…」と感じたことについてお話ししていきます。

その1 相手に何かを伝える手段が限られている

リモートワークでは、相手の顔を見ながら説明する機会が少ないです。基本的に文章や音声のみ、必要に応じて画面共有しながらやりとりをしています。

最初の頃は自分の伝えたい内容が上手く相手に伝わらなかったり、逆に相手の説明をなかなか理解できないといったことがありました。
慣れてしまえば問題ないのですが、慣れるまでが大変でした。

その2 慣れるまでは話しかけづらい

リモートで作業している時は相手の様子がわかりません。そのため、先輩に質問をしたい時に

「今話しかけても大丈夫だろうか?作業の邪魔にならないだろうか…」

と、必要以上にタイミングを気にしてしまい、対面の時よりも話しかけづらいと感じてしまうことが多々ありました。

こちらも慣れてしまえば問題ないのですが、最初はリモートで他の人に話しかけることのハードルが高いと感じました。

その3 環境によっては集中できない

先程「リラックスした環境で仕事できる」と記述しましたが、自宅付近の環境によってはそれが難しい場合もあります。

在宅で仕事をしている際に気づいたことですが、外の音って意外と聞こえてくるんですよね😓
近所で工事していたり外で子供が騒いでいたりすると、集中力が切れやすくなって逆に仕事が捗らないことが多いです…(体験談)

こればっかりは仕方のないことですが、しっかり集中できる環境で仕事したいのなら、自室の環境に加え外の環境も良いところを選ばなければと強く感じました。

余談ですが、仕事机や椅子も自分の身長やスタイルに合ったものを使わないと、腰が痛くなったり疲れやすくなります。
多少高くても、自分に合ったものの利用をオススメします…!

3.まとめ

リモートワークには便利な点が多い反面、快適なリモート生活を得るには、慣れることが必要だったり周囲の環境が大きく影響します。

この記事を通して、リモートワークについて少しでも参考になりましたら幸いです!^^

最後に…

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

careers.012grp.co.jp

ServiceWorkerをサクッと導入してみた

        f:id:wiz-akakura:20210922142424p:plain

こんにちは、インフラエンジニアの赤倉です。

今回は「ServiceWorker」のオフラインキャッシュを使ってWebページの読み込み高速化を実現してみたいとおもいます。

ServiceWorkerとは

ServiceWorker とは、ブラウザが Web ページとは別にバックグラウンドで実行するJavaScript環境のことです。ページ内のコンテンツをブラウザ側にキャッシュしてオフライン状態でも利用することができます。

対応ブラウザ

また、ServiceWorkerの稼働条件としてWebページのHTTPS化が必須となっております。(またはlocalhostでも実行可)

ServiceWorkerのライフサイクル

register
  • ブラウザにServiceWorkerのJavaScriptファイルのパスを知らせます。
  • ServiceWorkerが操作できるスコープ(後述)を指定します。
install
  • ブラウザにキャッシュが生成されます。
  • キャッシュ名(=バージョン)の定義やキャッシュ対象を指定します。
activate
  • ServiceWorkerのJavaScriptファイルを更新します。 ServiceWorkerはサーバ上のJavaScriptファイルとバイト単位で差が生じた場合に新しいファイルと認識します。
  • ユーザがブラウザを閉じたり、ページを更新したことを契機にServiceWorkerが更新されます。

スコープとは

ServiceWorker が管理するURLの範囲を示します。
スコープはServiceWorkerのスクリプトファイルが設置されているパス以下の階層を指定する必要があります。なお、このパス制限はService-Worker-Allowed: ヘッダで解除可能です。

ServiceWorker 導入

前置きが長くなってしまいましたが、ここからが導入手順になります。

準備するファイル
DocumentRoot
├── main.js
├── serviceworker.js

今回はどちらのファイルもDocumentRootに配置するものとします。

ファイル名に特に制約はありません。

1. ファイル作成
main.js

registerを行うファイルです。serviceworker.jsのパスを示します。

if ("serviceWorker" in navigator) {
  window.addEventListener("load", function () {
    //今回はDocRoot以下をServiceWorkerのスコープとします
    navigator.serviceWorker.register("/serviceworker.js", { scope: "./" }).then(
      function (registration) {
        // 登録成功
        console.log(
          "ServiceWorker registration successful with scope: ",
          registration.scope
        );
      },
      function (err) {
        // 登録失敗
        console.log("ServiceWorker registration failed: ", err);
      }
    );
  });
}
serviceworker.js

ServiceWorkerのコアファイルです。※コードの細かい説明は省略します。

//キャッシュ名(=バージョン)を指定する
var
CACHE_NAME = "cache-v1";
//キャッシュするファイル or ディレクトリを指定する
var urlsToCache = [ "/", ]; // install self.addEventListener("install", function (event) { event.waitUntil( caches.open(CACHE_NAME).then(function (cache) { console.log("Opened cache"); return cache.addAll(urlsToCache); }) ); }); // activate self.addEventListener("activate", function (event) { var cacheWhitelist = [CACHE_NAME]; event.waitUntil( caches.keys().then(function (cacheNames) { return Promise.all( cacheNames.map(function (cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); }); // fetch self.addEventListener("fetch", function (event) { event.respondWith( caches.match(event.request).then(function (response) { if (response) { return response; } var fetchRequest = event.request.clone(); return fetch(fetchRequest).then(function (response) { if (!response || response.status !== 200 || response.type !== "basic") { return response; } var responseToCache = response.clone(); caches.open(CACHE_NAME).then(function (cache) { cache.put(event.request, responseToCache); }); return response; }); }) ); });
2. main.js読み込み

ページの共通ヘッダなどに記載します。

<script src="/main.js"></script>

導入後の動作チェック

ServiceWorkerが動作していることはブラウザから確認可能です。

 

下記はGoogleChromeの検証モードの画面です。

[Status Code]に"(from service worker)"と記載されていればオフラインキャッシュ化成功です。

f:id:wiz-akakura:20210922133406p:plain

 

また、[Application]の[Service Woerkers]でも動作していることが確認できます。

f:id:wiz-akakura:20210922141334p:plain

効果

今回の検証では100MBの画像を読み込んだページでオフラインキャッシュの効果を試してみました。

 

ServiceWorkerなしの状態では画像の読み込みに852msかかっていますが・・

f:id:wiz-akakura:20210922122154p:plain

これがServiceWorkerのオフラインキャッシュを利用すると249msまで短縮されていることがわかります。

f:id:wiz-akakura:20210922122158p:plain

この検証はローカルのMAMP環境で実施したので読み込み時間にそこまで大きな差はありませんが、本番サーバであればもっと顕著に効果が出るとおもいます。

さいごに

ServiceWorkerを使ってオフラインキャッシュを作ることによって、画像やcss、 jsファイルなどの静的コンテンツの読み込みをコストゼロで高速化することができるので、非常に便利ですね。コストや技術的な理由でキャッシュサーバを用意できない場合などに導入を検討してみては如何でしょうか?

 

株式会社Wizではエンジニアを募集しています!

↓↓↓興味ある方はぜひご覧ください!↓↓↓

 

新卒がタスクの恐怖で殺されないためには(仮)

f:id:daisuke_john:20210916160830p:plain

こんにちは、フロントエンドの益田です。
OJT期間を終えてタスク管理について相談する機会があったので、今回は入社して5ヶ月経った自分なりのタスクの進め方について書いていきたいと思います。

1. 期限を確認する

まずは、緊急か緊急でないかの確認をして、緊急を要する要件でないのならTODOリストに入れて一旦忘れるようにしています。
人によって管理の仕方が違うと思いますが、私はノートに殴り描きしてからリスト化するようにしています。

2. 日数がかかるタスクは定期的にハートビートを送る

大きなタスクや並行しているタスクがある場合は進捗を報告をして、一人で悩む時間を一定以上掛けないようにしています。(生存報告のようなもの)

  • ハートビート:心臓の鼓動、拍動、心拍という意味

3. なるべく早く反応する

ディレクターやデザイナーさんとのやり取りや、差し込みでタスクが降ってきた時にすぐに回答できない場合などは、特に以下の2つを意識するようにしています。

1. すぐに対応できない場合は期限を切って対応する。
2. ここではあくまで「反応」するだけ。
(あなたのメッセージはたしかに受け取りましたよ!と返すだけ)

メリットとしては、友達とのLINEでも同じだと思いますが相手の安心感が得られることと自分の未着手のタスクが1個減ります。

4. 初回レビューのタイミングは予め決めておく

タスクに着手する前から「ざっくりこれぐらい作って、いったんレビューしてもらう」という計画を立てて進めています。 早めのタイミングにレビューをして貰うことで後半になっての修正が入った場合に対応しやすくなったと感じています。
通常業務以外にも目標ややりたいことリストなどの振り返りにも活用でき、2週間や月1などスケジュールを決めて定期的な見直しをしてフィードバックを貰っています。

5. タスクを大きい粒度と小さい粒度に分けて管理する

工数分かりづらいタスクや期間が長いタスクがある場合には、Notion, ノート, リマインダーを使い分けて行なっています。
とりあえずノートに書き出してそこからNotionやリマインダーに設定をして管理しやすいようにしています。

  • ノート : 殴り書き、情報・思考の整理に使う
  • Notion : タスクの進捗。工数見積もりなど
  • リマインダー : 細かい作業など

6. 時間で区切る

フィードバックで貰う事が多かったのが「時間で区切る」でした。
1つのタスクを終えて次に取り掛かりたいタイプなので苦手な部分ではあるのですが、時間(タイムボックス)で区切ることで並行しながらタスクを進められ、自分のタスクが計測しやすいので工数が見積もりづらいという部分もカバーされていくと思います。

タイムボックス
 タスクに合わせて時間を決めるのではなく、時間を固定する手法を、「決まったサイズ(時間)の箱にタスクを入れる」というイメージでタイムボックス化(timeboxing)と言います。
エンジニアの知的生産術 ――効率的に学び,整理し,アウトプットする:書籍案内|技術評論社

7. マインドマップを描いて状況を俯瞰する

現状のタスクを洗い出したり、タスクの詳細を整理する時に使っています。 メリットとして、文章が苦手な人でも単語で書けるので楽に始められることと、朝の5分〜10分程度ノートに書き出して気持ちの整理と優先順位付けを行うことでスッキリした状態で始められるのでお勧めです。派生して書き出せるので、企画でのアイディア出しにも使っています。

その他Wizでやっていること

KPT

Wizでは隔週で1on1を行なっておりその中で進捗と以下のKPT(Keep, Problem, Try)を使ってフィードバックをしてもらっています。次週に向けての改善点も見えてくるので細かく洗い出して振り返る時間を作っています。  f:id:daisuke_john:20210916152823j:plain

雑談会

コミュニケーション1on1以外にも他職種も含めた雑談会を月に1回程度行なっています。 デザイナーやディレクター・ライターの方も参加して頂けるので、業務での相談なども気軽にできるようになったと感じています。
雑談会の詳細については下記の記事に記載していますので気になった方は是非ご覧下さい。
コロナ禍での他職種とのコミュニケーションについて - Wiz テックブログ

参考

スケジュールにバッファを設けるのは悪か? - ユニファ開発者ブログ

【新人ITエンジニア向け】僕が仕事をする上で大事にしているポイントあれこれ - give IT a try

おわりに

今回は入社して5ヶ月経って個人的に役に立ったことを書いてみました。
現状、模索段階ということもあり自分に合ったやり方を今後見つけていきたいと思います。

また、

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

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

careers.012grp.co.jp

php-cs-fixerをつかったコードフォーマットの自動化(git hook)

こんにちは、バックエンドエンジニアの三井です。

社内でコードフォーマットを統一する活動を行っており、phpのパッケージを使ったフォーマッターの導入と、git hookを使ったフォーマットの自動化に成功したので概要をまとめていきたいと思います!

導入したphpパッケージ


今回導入したパッケージはphp-cs-fixerです。

導入環境

laravel: 8.60.0
composer: 2.1.6
php: 8.0.9

概要

composer で導入できるphpのパッケージです。導入後コマンドを打つことで任意のファイルにフォーマッターをかけることができます。
フォーマットルールについて、PSR12をはじめ多くのフォーマットに対応しており、設定ファイルを作成することでオリジナルのフォーマットルールを生成することができます。
導入時はPSR12のみを導入して運用してましたが、今後社内でフォーマットを統一するためにルールの取り決めも行っていきたいと思っております!

導入方法・設定


インストール

今回は私の動作環境であるlaravelプロジェクト内での使用を想定して行います。

# パッケージのインストール
composer require friendsofphp/php-cs-fixer
# 設定ファイルをプロジェクトディレクトリに追加
touch .php-cs-fixer.dist.php
# gitignoreにキャッシュファイルを追加
.php-cs-fixer.cache

設定ファイル

設定ファイル.php-cs-fixer.dist.php内を以下のように記述します。 ルールはここで確認することができ、PSR12等複数のルールをまとめたルールセットを指定することも可能です。

<?php
/*
 * This document has been generated with
 * https://mlocati.github.io/php-cs-fixer-configurator/#version:3.1.0|configurator
 * you can change this configuration by importing this file.
 */
$config = new PhpCsFixer\Config();
return $config
        ->setRiskyAllowed(true)
    ->setRules([
        '@PSR12' => true, // ここにルール追記
    ])
    ->setFinder(PhpCsFixer\Finder::create()
        ->exclude([ // 除外ファイル
              'vendor'
        ]) 
        ->in(__DIR__)
    )
;

setRiskyAllowed() : riskyなルール(コードを書き換える破壊的な修正を含むルール)を有効にするかを設定します。
setRules() : 使用したいルールをここで列挙します。
setFinder()...->exclude(['']) : 除外したいディレクトリ名を列挙することでそのディレクトリ内のファイルは無視されます。

ルール設定方法

ルール生成用のツールで生成ファイルを作ることができます。

https://mlocati.github.io/php-cs-fixer-configurator/#version:3.1

f:id:mtimti:20210915192833p:plain

このページでは使用可能なルールが列挙されており、ページ内の歯車マークをクリックすると使用するルールの選別ができるようになります。

f:id:mtimti:20210915193249p:plain

またその状態で+ボタンを押すことでルールセットの指定も可能です。 最後にExportボタンを押すことで設定ファイルの出力ができます。

f:id:mtimti:20210915193037p:plain

f:id:mtimti:20210915193201p:plain

実行コマンド

# version バージョン確認
$ ./vendor/bin/php-cs-fixer --version

# dry-run (修正はされないが、--diffオプションで修正前後の確認ができる)
$ ./vendor/bin/php-cs-fixer fix -v --diff --dry-run

# fix 実際に修正実行
$ ./vendor/bin/php-cs-fixer fix -v --diff
--dry-run # 変更なし実行
--diff # 差分出力
--allow-risky # riskyルールの許可

git hookの概要


詳しい説明は割愛しますが簡単に説明すると、gitのコミット等のコマンドの直前・後にスクリプトを実行できるという仕組みです。 今回は"コミット直前にフォーマッターを実行し、修正後コミットする"という機構を作ります。 隠しディレクトリ.gitの配下にhooksディレクトリを作りpre-commitファイルを作成します。

#!/usr/bin/env bash

#ルートの設定(laravel projectの配下を指定)
ROOT=$(git rev-parse --show-toplevel)/projdir 

PHP_CS_FIXER="./vendor/bin/php-cs-fixer"
HAS_PHP_CS_FIXER=false

if [ -x $ROOT/vendor/bin/php-cs-fixer ]; then
    # パッケージの存在チェック
    HAS_PHP_CS_FIXER=true 
fi

LIST=$(git status | grep -e '\(modified:\|new file:\)'| grep '\.php' | cut -d':' -f2)
if $HAS_PHP_CS_FIXER; then
    ERRCNT=0
    for file in $LIST
    do 
  # --dry-runオプションで先にエラーチェック
        ./projdir/vendor/bin/php-cs-fixer fix $file --diff --dry-run --path-mode=intersection --config=$ROOT/.php-cs-fixer.dist.php >/dev/null 2>&1; 
        ERR=$?
  # ステータスコードが0(修正なし)か8(修正あり)以外はエラーに該当するのでエラーメッセージを表示
        if [ $ERR != 0 -a $ERR != 8 ]; then
            ERRCNT=$((ERRCNT+1))
            echo "php-cs-fixer-$ERR エラーが検出されました $file"
            echo "error $ERRCNT"
        else
   # 修正実行
            ./projdir/vendor/bin/php-cs-fixer fix $file --path-mode=intersection --config=$ROOT/.php-cs-fixer.dist.php >/dev/null 2>&1;
            git add "$file";
        fi
    done
        if [ $ERRCNT -gt 0 ]; then
            echo "php-cs-fixer $ERRCNT 件エラーが検出されました"
            exit 1
        fi
            echo "php-cs-fixer フォーマット修正が完了しました"
else
    echo ""
    echo "Please install php-cs-fixer, e.g.:"
    echo ""
    echo "composer require --dev fabpot/php-cs-fixer:dev-master"
    echo ""
    exit 1
fi

これでcommit時にこのスクリプトが自動実行され、フォーマッターが動きます。

参考にさせていただいたソース

実際に開発で運用してみた感想


今回フォーマッターの統一に至った経緯としては社内で使用エディタが統一されていなかったため、各々のフォーマットでコーディングされていたという状況を受けてのことでした。
実際に使ってみて思ったこととしては、エディタ内のフォーマッターと合わせて運用すると良いかもしれない、ということです。コミット直前に修正を行うデメリットとして、コードを書いている途中で整ったコードを見れないという点です。commit内容としては整っているコードですが、"実装中にコードを読みやすくする"にすることが目的なら本末転倒だと思います。そのため、"自分がわかりやすいように"エディタのフォーマッターも兼ねて運用するのも有効だと思いました。

Laravelで初めてのテストとTDDをサクッとやってみる【初心者向け】

TDDのイメージ

こんにちは、バックエンドエンジニアの中嶋です。

今年の春に入社して主にLaravelを扱って開発をしていますが、

プロダクト開発はTDD(テスト駆動開発)で行うことが多くなってきました。

もちろん規模や目的によってTDDを導入すべきかは検討が必要ですが、

テストなんて面倒だしそもそも何からやって良いかわからない」という心理的なハードルがあることで導入していないパターンも割と多いのではないでしょうか?

そこで今回は、

  • Laravelを用いてまだテストコードを書いたことがない
  • TDD(テスト駆動開発)を実践したことがない
  • 気にはなるけど実践するのはハードルが高くて..

という方に向けてTDDの雰囲気だけでも知ってもらうべく、その手順を簡単に紹介したいと思います。

今回やること

  • PHPUnitを用いてLaravelで実装した簡易APIのFeatureテストを手軽に行う
  • GETでアクセスすると保存されているデータをJSON形式で返すというシンプルなもの

  • 手軽に試して雰囲気をお伝えするのが目的なので、登録処理やデータベースのテストはやらない

↓もし「APIって何?」という方はこちらに簡単に纏めているので良ければご覧ください
REST APIとは?ざっくりと理解してみる【初心者向け】 - Wiz テックブログ

TDD(テスト駆動開発)とは..

テスト駆動開発はその名の通りテストを中心とした開発手法のことです。

Test-Driven Development」の頭文字を取ってTDDと呼ばれることが多く、

XP(エクストリームプログラミング)の考案者であるケント・ベック氏が編み出した手法とされています。

概要をざっくり一言でまとめると、

ちゃんと動く状態を保ちながら機能を素早く実装し、きれいなコードを目指してリファクタリングしていく

です。

そのプロセスとしては

  1. きちんと動作するか?のテストを作成する
  2. テストを実行して失敗することを確認する
  3. コードが汚くても良いので最低限仕様を満たす実装を行う
  4. テストを実行して成功するようになったことを確認する
  5. テストが成功することを確認しながらきれいなコードにリファクタリングしていく TDDのフロー

というような流れになります。

コツとしては「テスト作成」「機能実装」「リファクタリング」を、それぞれ最小の単位で素早く行うことです。

テストや機能を素早く実装し、テストが常にグリーンの状態になる..このテストに守られている安心感があると、開発のリズムも良くなってくるはずです。

逆にテストなしに大きな単位で実装やリファクタリングを行うと、

「あれ?なんで動かないんだ?」「どこで間違えたんだ?」ということに時間や思考を取られるので、

テストに守られた状態になれるというだけでもかなり恩恵が得られると思います。

テストの手順

さて、概要がわかったところで「PHPUnit」を用いたテストの作成をしていきます。

PHPにおけるテストはこのPHPUnitデファクトスタンダード(事実上の標準)と言えると思います。

そしてLaravelでもあらかじめ用意されており、そのまま使用することができます。(必要に応じて設定は必要です)

(Tips) 設定ファイルについて

Laravelのプロジェクトディレクトリ直下にphpunit.xmlというファイルが用意されています。

これはPHPUnitのテスト実行の設定ファイルで、

今回のような簡易なテストではデフォルトのまま実行することができますが、テストケースをグループ化したりテスト用のデータベースを用意する場合など必要に応じて編集します。

【手順①】テストしたいことリストを作成

まずは「どんな機能をテストしたいか」をリストアップします。

今回の例では「お知らせAPI」という新着ニュースのデータを返す簡易なAPIをテストしたいので、

  • 'api/notifications' というエンドポイントにGET メソッドでアクセスできる(ステータス200を返す)
  • 「お知らせ」データがjson形式で取得できる
  • 取得したJSONデータがデータベースに登録されている値と一致する ..

といった項目などがリストアップできると思います。

これはテストコードの中にコメントアウトとして書く程度でもOKです。(今回はGETでアクセスできるかまで検証します)

【手順②】 テストクラスを作成

実際のテストコードを作成するにはartisanのmakeコマンドを使います。

今回の例ではNotificationApiTestというクラスを作成します。

$ php artisan make:test NotificationApiTest

これでTests/Feature/NotificationApiTest.phpが雛形として生成されました。

(Tips) FeatureテストとUnitテストについて

このように特にオプションを付けないで実行すると、Featureテストのディレクトリにファイルが生成されます。

tests
├── CreatesApplication.php
├── Feature
    ├── ExampleTest.php
    └── NotificationApiTest.php
├── TestCase.php
└── Unit
    └── ExampleTest.php

これはTests\TestCaseクラスを継承していて、LaravelがPHPUnitを拡張したものになります。

Laravelの機能を利用したテストを行う場合はこちらを用いるのが良いと思います。

一方、上記コマンドで--unitというオプションを付けて実行するとtests/Unitディレクトリにファイルが生成されます。

こちらはPHPUnit\Framework\TestCaseクラスを継承しており、Laravelの機能を利用しない範囲で粒度の細かいテストを行う場合はこちらを用いると良いと思います。

【手順③】 失敗するテストを書く

先に述べた手順のとおり、機能実装する前にテストを作成します。

本来はテスト用のデータベースやもっと細かいテスト項目を用意すべきですが、今回はお試しなので簡易な格好としています。

・Tests/Feature/NotificationApiTest.php

<?php
..
class NotificationApiTest extends TestCase
{

    /**
     * @test
     */
    public function GETで正常にアクセスできるか()
    {
        // エンドポイントにGETでアクセス
        $response = $this->get('/api/notifications');

        // ステータスコード200が返るか
        $response->assertStatus(200);

    }

テストメソッドの内容としては、エンドポイントにGETでアクセスする擬似リクエストを作成し、

その結果をアサーションでチェックしています。

(Tips) テストメソッドの名前について

今回のテストメソッド名は日本語になっていて違和感のある方も多いと思います。

これは@testノーテーション(/**の部分)を設けていることで、メソッド名に日本語を用いても問題なく実行できるというものです。

@testノーテーションを使わない場合はメソッド名をtestFoo()のようにする必要があるのですが、

どちらも動作は同じなので、メソッド名を毎回考えるのが面倒なのと日本語の方がテスト結果を見た時にも直感的でわかりやすいという理由で、 個人的には@testノーテーションを使用してメソッド名は日本語で定義することが多いです。

(Tips) assertメソッドについて

assertメソッドは「テストが成功したか」「失敗したか」をチェックして結果を返すものです。

テストコードを書くにあたってはこのassertメソッドをいかに知っているかが大事だったりします。

これは本当にたくさんの種類があるので具体的な紹介は公式ドキュメントに譲りたいと思いますが、

今回のようなAPIのテストにおける代表的なものを少しだけ紹介すると以下のようなものがあります。

メソッド テスト内容
assertSuccessful() HTTPステータスコードが200番台なら成功
assertStatus($status) HTTPステータスコードが$statusと一致していれば成功
assertJson(array $data, $strict=false) レスポンスされたjsonの配列に$dataが含まれていれば成功
assertExactJson(array $data) レスポンスされたjsonの配列が$dataと一致していれば成功
【手順④】 テスト実行 →失敗する

最低限のテストを作成したら、まずは実行して失敗することを確認します

vendor/bin/phpunit tests/Feature/NotificationApiTest.php
PHPUnit 8.2.5 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.51 seconds, Memory: 30.00 MB

There was 1 failure:

1) Tests\Feature\NotificationApiTest::GETで正常にアクセスできるか
Response status code [404] does not match expected 200 status code.
Failed asserting that false is true.

/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:89
/var/www/html/tests/Feature/NotificationApiTest.php:39

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

当然ですが、エンドポイントを実装していないのでステータスコード404(NotFound)が返り失敗します。

【手順⑤】 最低限の実装 →テストを成功させる
エンドポイントを定義

404が返っているので、まずはエンドポイントを定義します。

api.php

<?php
..
Route::get('notifications', 'Api\NotificationApiController@index')->name('notifications');
もう一度テストを実行
PHPUnit 8.2.5 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 2.58 seconds, Memory: 30.00 MB

There was 1 failure:

1) Tests\Feature\NotificationApiTest::GETで正常にアクセスできるか
Response status code [500] does not match expected 200 status code.
Failed asserting that false is true.

/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:89
/var/www/html/tests/Feature/NotificationApiTest.php:39

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

こちらも当然ですが失敗します。

今度は404ではなく、ステータスコード500(ServerError)が返りました。

エラー内容はこれではわかりませんが、エンドポイントにはアクセスできたものの処理を実装していないのでサーバーエラーが返る状態ですね。

APIの処理を実装

次にAPIの処理本体を最低限のレベルで実装します。 内容自体は今回は重要ではないので無視していただいてOKです。

・NotificationApiController.php

<?php
..
class NotificationApiController extends Controller
{
    public function index()
    {
        $data = Notification::getNotificationsIndex();
        $status = 200;
        $message = 'Success';

        return ResponseUtil::jsonResponse($status, $message, $data);
    }
}
三度、テストを実行

この状態で改めてテストを実行します。

PHPUnit 8.2.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 1.97 seconds, Memory: 30.00 MB

OK (1 test, 1 assertion)

これでようやくテストグリーン(OKの状態)になりました🙌

あとはテストコードを信頼できるレベルに強化(DBも含めてデータが正しいかチェックする等)しつつ、

この繰り返しでテストグリーンを保ちながら、例えばAPI処理をtry〜catchの形にリファクタリングしたり、新たな機能を実装していくことができます。 TDDのフロー

今回は簡易な内容での導入でしたが、

実際の開発でも是非テストに守られた状態で実装していく気持ち良さを体感してみてはいかがでしょうか??

Wizでは良いものをワイワイと作るべく、エンジニアを絶賛募集しています!(ワイズと読みます)

↓↓↓興味ある方はぜひご覧ください!↓↓↓

careers.012grp.co.jp

ドキュメント作成に向いている静的サイトジェネレータはどれ?

f:id:rainymoment0616:20210827092425p:plain

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

現在、チーム内でドキュメントを作成する機会が多くなってきました。

社内では、様々な方法でドキュメントを作成しているのですが、そのひとつの方法として、静的サイトジェネレータ(Static Site Generator:SSG)があります。

今回は、ドキュメント作成にあたって自分が試してみたSSGをいくつかご紹介したいと思います。

静的サイトジェネレータ(SSG)とは?

静的サイトジェネレータとは、入力ファイルから簡単に静的ページを生成してくれるツールのことです。

有名なものだと、Next.jsやGatsby、Nuxtがありますね。

実際に表示に利用されるものは静的ページのため、動的ページに比べてサイトパフォーマンスが高く、SPAに比べてSEO面で優れています。

一方、大規模サイトのようなページ数の多いものは生成するのに時間がかかったり、更新頻度が多い場合に頻繁に生成を行わなくてはいけないというデメリットもあります。

ドキュメント作成するにあたってのポイント

今回、ドキュメント作成にあたって、下記をポイントに置いています。

  • マークダウン形式で書くことができる(誰もが簡単にドキュメントを書くことができるし、他サービスなどへの移行も簡単)
  • ドキュメントとしての見た目が担保されたデザインである
  • バージョン管理ができる
  • 環境構築が簡単

ドキュメント作成に向いていると感じるSSG

VuePress

https://vuepress.vuejs.org/

Vue.jsベースのドキュメント作成に特化したSSGです。

f:id:rainymoment0616:20210827083538p:plain
VuePressを利用したドキュメントの画面

  • 環境構築が簡単
  • Vue.jsの知識がなくてもマークダウンを利用したドキュメント作成が可能
  • デフォルトのテーマがシンプルかつ、ドキュメント向き
  • GitHub等のバージョン管理ツールでの管理が可能

マークダウンの中で、Vueコンポーネントを利用することができるのも魅力のひとつで、独自に作ったコンポーネントも利用することが可能です。

Docusaurus

https://docusaurus.io/

Facebook製の、Reactベースのドキュメント作成に特化したSSGです(アイコンの怪獣が可愛い)。

f:id:rainymoment0616:20210827083557p:plain
Docusaurusを利用したドキュメントの画面

  • Reactの知識がなくても、マークダウンを利用したドキュメント作成が可能(※)
  • CodeSandBoxやStackBlitzを利用してお試し開発が可能
  • デフォルトのデザインがドキュメント向け
  • GitHub等のバージョン管理ツールでの管理が可能

開発環境を作成する前に、お試しで弄ることができるのは、魅力的なポイントのひとつだと思います。

※一部ページはReactコンポーネントで構成されているため、カスタマイズを行う場合にはReactの知識が少し必要になってきます

Hugo

https://gohugo.io/

GitHubのスター数53000超えの、Go言語ベースのSSGです。

f:id:rainymoment0616:20210827085219p:plain
Hugoを利用したドキュメントの画面

  • ビルド時間が早い
  • マークダウンを利用したドキュメント作成が可能
  • テンプレートデザインが豊富(※)
  • GitHub等のバージョン管理ツールでの管理が可能

テンプレートのデザインがとにかく豊富で、ドキュメント向きのものから、ブログやポートフォリオ、ギャラリーサイトなど、様々なタイプのデザインが揃っています。

(今回、ドキュメント作成にGeekdocというテーマを利用しています。)

※テーマによってはディレクトリ構成が変わったり、独自のコンポーネント記述があるものもあるので、テーマを変更する際は注意が必要です

まとめ

今回はドキュメント作成に向いていると感じたSSGをいくつかご紹介しました。

他にもたくさんあるので、プロジェクトの規模や用途にあったものを試して選んでみてください(『Jamstack.org』で色んなSSGを確認することができますよ)。

株式会社Wizではエンジニアを募集しています!

↓↓↓興味ある方はぜひご覧ください!↓↓↓

careers.012grp.co.jp