ベーシックアドベントカレンダー 19日目の記事です。

formrun を開発している橋濱( @kotahashihama )です!
formrun3周年をお祝いする インフォグラフィックLP のコーディングを担当しました。

久しぶりのページ制作案件でした。
今回、WebデザイナーからWebエンジニアに転向したこともあり、
「デザイナー時代より効率的なコーディング作業がしたいぞ!」 と思い、

  • Pug
  • Sass(SCSS記法)
  • Webpack

を選定して作業を進めました。

今日はなんと、ちょうどそのLPのリリース日というグッドタイミング!
この記事では、上記の技術を使ったコーディングで感じた利点を 制作スピードの観点 からご紹介したいと思います。

ファイルの分割

PugもSassも、最終的に1つにコンパイルされるファイルを複数のファイルに切り分けてコーディングすることができます。
今回のコーディングでは大きく、Pug、SassそれぞれのファイルをLPのセクションごとに切り分けて作業しました。

pug
┣━ index.pug
┣━ ogp.pug
┣━ _section1.pug
...
┗━ _section8.pug
scss
┣━ style.scss
┣━ variables.scss
┣━ mixins.scss
┣━ snippets.scss
┣━ animations.scss
┣━ _section1.scss
...
┗━ _section8.scss

プログラミングの世界では当たり前にモジュール化されていますが、最終的に1つにまとめるHTMLやCSSを、部品単位ごとにコーディングできるのはやはり見通しがよく快適です。

切り出せるということは使いまわしができます。
決まりのパターンには include@extend でがんがんスニペットを使っていきましょう!

コーディングのスタイルは、 大雑把に全体をコーディングしてから細部を詰めていくスタイル が、使いまわし箇所を見つけやすいのでおすすめです。

データと構造の分離

たとえば、HTMLでこんなリストがあったとします。

<div class="company-list">
  <div class="company-list__item">
    <div class="company-name">ベーシック株式会社</div>
    <div class="company-logo">
      <img src="images/user-voices/basic-logo.png" alt="ベーシック株式会社">
    </div>
    <div class="user-info">
      <div class="user-info__face">
        <img src="images/user-voices/basic-hashihama.png" alt="橋濱幸太">
      </div>
      <div class="user-info__name">橋濱幸太</div>
      <div class="user-info__message">メッセージ</div>
    </div>
  </div>

  <div class="company-list__item">
    <div class="company-name">ベーシック株式会社</div>
    <div class="company-logo">
      <img src="images/user-voices/basic-logo.png" alt="ベーシック株式会社">
    </div>
    <div class="user-info">
      <div class="user-info__face">
        <img src="images/user-voices/basic-yamada.png" alt="山田花子">
      </div>
      <div class="user-info__name">山田花子</div>
      <div class="user-info__message">メッセージ</div>
    </div>
  </div>

  <div class="company-list__item">
    <div class="company-name">ベーシック株式会社</div>
    <div class="company-logo">
      <img src="images/user-voices/basic-logo.png" alt="ベーシック株式会社">
    </div>
    <div class="user-info">
      <div class="user-info__face">
        <img src="images/user-voices/basic-tanaka.png" alt="田中太郎">
      </div>
      <div class="user-info__name">田中太郎</div>
      <div class="user-info__message">メッセージ</div>
    </div>
  </div>
  ...
</div>

Pugなら変数と繰り返し文を使ってこう書けます。

ul.user-voice-list
  -
    const list = [
      {
        company: {
          name: 'ベーシック株式会社',
          logoPath: 'images/user-voices/basic-logo.png'
        },
        user: {
          name: '橋濱幸太',
          facePath: 'images/user-voices/basic-hashihama.png',
          message: 'メッセージ'
        }
      },
      {
        company: {
          name: 'ベーシック株式会社',
          logoPath: 'images/user-voices/basic-logo.png'
        },
        user: {
          name: '山田花子',
          facePath: 'images/user-voices/basic-yamada.png',
          message: 'メッセージ'
        }
      },
      {
        company: {
          name: 'ベーシック株式会社',
          logoPath: 'images/user-voices/basic-logo.png'
        },
        user: {
          name: '田中太郎',
          facePath: 'images/user-voices/basic-tanaka.png',
          message: 'メッセージ'
        }
      },
      ...
    ]
  each item in list
    li.user-voice-list__item
      .company-name #{item.company.name}
      .company-logo
        img(src=item.company.logoPath, alt=item.company.name)
      .user-info
        .user-info__face
          img(src=item.user.facePath, alt=item.user.name)
        .user-info__name #{item.user.name}
        .user-info__message #{item.user.message}

データを変数に用意しておいて、 each ... in ~ でノードを繰り返します。

これのいいところは、データと構造の繰り返しが別になっていることにより、

  • データの修正は変更箇所が見つけやすく
  • 構造の変更はeach内を編集すれば一括で変更できること

です。

この例だと分かりづらいかもしれませんが、ノード数が多いほど利点を実感できます。

制作をしていると文言やデザインの変更がままあるので、むしろデザイナーだからこそ恩恵を受けられるのではと思います!
検索と置換で頑張ってもいいですが、ヒューマンエラーに気を遣う&時間を取られちゃいますからね。

今回は使いませんでしたが、デザインがちょっとずつ違う場合も、 if と組み合わせることにより柔軟に対応できそうです。

セレクタにネストできるメディアクエリ

Sassのネスト記法の恩恵により、各セレクタのスコープ内にメディアクエリを書くことができるので、同じセレクタ同士でのブレイクポイントごとのスタイルの違いが俯瞰しやすいです。

.box {
  border-radius: 9px;

  @media screen and (max-width: 896px) {
    border-radius: 6px;
  }

  @media screen and (max-width: 480px) {
    border-radius: 4px;
  }

  &__content {
    margin-bottom: 190px;

    @media screen and (max-width: 896px) {
      margin-bottom: 140px;
    }

    @media screen and (max-width: 480px) {
      margin-bottom: 180px;
    }

    .box-note {
      width: 100%;

      &__heading {
        font-size: 1.4rem;

        @media screen and (max-width: 896px) {
          font-size: 1.2rem;
        }

        @media screen and (max-width: 480px) {
          font-size: 1.1rem;
        }
      }
    }
  }
}

これと同じことをCSSでやろうと思うと、

.box {
  border-radius: 9px;
}

@media screen and (max-width: 896px) {
  .box {
    border-radius: 6px;
  }
}

@media screen and (max-width: 480px) {
  .box {
    border-radius: 4px;
  }
}


.box__content {
  margin-bottom: 190px;
}

@media screen and (max-width: 896px) {
  .box__content {
    margin-bottom: 140px;
  }
}

@media screen and (max-width: 480px) {
  .box__content {
    margin-bottom: 180px;
  }
}


.box .box-note {
  width: 100%;
}

.box .box-note__heading {
  font-size: 1.4rem;
}

@media screen and (max-width: 896px) {
  .box .box-note__heading {
    font-size: 1.2rem;
  }
}

@media screen and (max-width: 480px) {
  .box .box-note__heading {
    font-size: 1.1rem;
  }
}

こんな感じで全セレクタと同じ階層にズラッと並んで、せいぜい空行の開け方とコメントで管理するか、ぐらいで全体的に判読性が微妙です。
(CSSでメディアクエリを書くなら、記述の冗長さという理由もあって、もっと大きなセレクタグループごとに囲っちゃうでしょうし)

メディアクエリとネストの相乗効果で 視線移動の少ないCSS が書けます!

コンパイルしてバンドル

デザイナー時代はGulpやタスクランナーを使っていませんでした。
が、プロダクトの開発で使っていることもあり、今回、勉強ついでにWebpackに挑戦してみました。

こちら、今回LPコーディング時に実際使った秘伝のタレです。

const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const webpack = require('webpack')

module.exports = {
  mode: 'development',
  entry: ['@babel/polyfill', './src/js/entry.js'],
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        use: ['pug-loader']
      },

      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              url: false
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
              config: {
                path: 'postcss.config.js'
              }
            }
          },
          {
            loader: 'sass-loader'
          }
        ]
      },

      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filepath: './dist/index.html',
      template: './src/pug/index.pug'
    }),
    new MiniCssExtractPlugin({
      filename: 'style.css'
    }),
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      numerator: 'jquery-numerator'
    })
  ],
  watch: true
}

また、 postcss.config.js の中身はこうなってます。

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

何をやっているかというと、

  • PugのコンパイルとHTML出力
  • SassのバンドルとCSS出力
  • autoprefixer でCSS出力時にプレフィックスを付与
  • BabelでレガシーブラウザへのJS対応

です。

最後には、バンドルされてこの形で出てきます。

dist
┣━ bundle.js
┣━ index.html
┗━ style.css

WebpackはあくまでもJSを一つにまとめるモジュールバンドラーなので、PugやSassをコンパイルする処理を組み込むように書かなければいけません。

最初はGulpでやろうと思い、途中でWebpackに乗り換えた経緯があります。
そのため、Webpackもタスクランナーだ という認識でいて、この辺りで混乱しました。

これらの作業を自動的にやってくれて、かつ webpack-dev-server プラグインがローカルサーバーをホットリロードしてくれる…。
Webpackでコーディングだけに集中できる環境を作りましょう!

おわりに

「あぁ、デザイナーのときに知っていれば!」
…とは思いましたが、エンジニアになった今だからこそ短期間でプログラマブルなテクニックを習得できるようになったのだと思います。

今後も引き続き効率化していきます!