フロントエンド開発Blog

オレには鈍器がある

このエントリーをはてなブックマークに追加

CSS Modules , ES2015 , JavaScript , Vue.js , Vuex , babel , webpack

前回「Vue.jsで家計簿管理システムを作った」ではサーバやDBなどの環境周りの話でした。今回はwebpackを使った開発環境の話・大枠のファイル構造・Store周りを解説します。

htdocs/kakeibo/app/webroot/package/ ディレクトリ

Vue.jsでの開発はこのディレクトリを基準に作成します。package.jsonを見ていただくとどんなライブラリを使用しているかわかります。

package.json

利用できるnpmコマンドは2つ。


"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }

それぞれnpm run devnpm run buildというコマンドを実行します。

devフロントエンド開発に特化した開発用Buildで、webpack-dev-serverが立ち上がります。bundleしたJSの書き出しは実行されません。また、AJAX周りも本番とは違ってstubのjsonを使用するためLAMP環境を必要としませんのですぐさまフロントエンド開発ができます。

buildは本番用Buildです。bundleしたJSが./static/に書き出されます。

これらコマンドはwebpack.config.jsの設定に準じた動作をします。

webpack.config.js

Buildの仕様には以下のような特徴があります。

  • 環境変数Productionを指定すると本番用のbuild設定になり、それ以外は開発用のbuildになる
  • 開発用Buildは原則webpack-dev-serverをHMRで使用することを想定している
  • 開発用Buildでは./static/bundle.jsに全てのJSコードがbundle化されます(ファイルの書き出しはされない)
  • 開発用Buildのwebpack-dev-serverでは./index.htmlが表示される(ちなみに本番はCakePHPのFormコントローラのindexアクション(forms/indexテンプレート)が使用される)
  • 本番用Buildは./static/以下にベンダー系のJSをまとめたlib.bundle.jsと、家計簿アプリ用の実行系のJSをまとめたbundle.jsを書き出す設定
  • process.env.NODE_ENVという変数がJSファイル内で使用でき、本番用BuildではString型でproductionとなる。よってJS作成時に本番用と開発用でソースコードを分岐できる(./src/config/ajaxUrl.jsの例)

process.env.NODE_ENVの設定はwebpack.config.jsの以下の部分の記述がそれに当たる。


// webpack.config.js 100行目付近

  module.exports.plugins = (module.exports.plugins || []).concat([
    // jsファイル内で process.env.NODE_ENV === 'production' で分岐させるために変数を定義
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),

CSS周りの仕様

  • CSS Spriteの生成にはwebpack-spritesmithを使用しているが、本番用Buildでしか実行されない
  • spritesmithは./src/sprite/以下のpng画像を対象とし、画像を/static/sprite01.pngに、CSSを./src/style/sprite01.cssに出力
  • spritesmithで生成したCSSは個々のJSファイルでimportされて使用する(CSS Modules こんな感じ
  • import対象のCSSファイルはcssnextの記法が使用できる
  • .vueファイル内のscopedCSSでもcssnextの記法が使用できる

css-loaderでも.vueファイル内でもcssnextを使用するためにはそれぞれ別々の設定が必要でした。


//webpack.config.js 20行目付近

// 使用モジュール設定
  module: {
    rules: [
      // .vueファイル style内でcssnextの記法を使用するためpostcss-cssnextをロード
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          cssModules: {
            localIdentName: '[path][name]---[local]---[hash:base64:5]',
            camelCase: true
          },
          postcss: [require('postcss-cssnext')()]
        },
        exclude: "/node_modules/"
      },

まずvue-loaderの設定。postcss: [require('postcss-cssnext')()]とすることでcssnextの記法が使えるようになります。


      //webpack.config.js 40行目付近

      // .css CSSModulesを使用 かつ cssファイル内でcssnextの記法を使用
      {
        test: /\.css$/,
        use: [
          { loader: "style-loader" },
          { loader: "css-loader?modules" },
          { loader: "postcss-loader" }
        ],
        exclude: "/node_modules/"
      },

css-loader?modulesとすることでCSS ModulesとしてJSからCSSをimportできるようにしています。postcss-loaderは同梱のpostcss.config.jsの設定に準じてcssnext記法が使えるようにしています。

正直な話、この規模でCSS Modulesはベスト解ではないと思いますが使ってみたかったのでこのような構成にしています。


CSS周りの概観

vueファイル内のCSS
    ↓
vue-loader + postcss-cssnext


.cssファイル群(./src/style/) ← spritesmithで生成したCSS(./src/sprite/)
    ↓
CSS Modules + postcss-loader + cssnext
    ↓
個々のJSファイルにimportされる

開発用Build向けのProxy

CakePHPで表示する際の静的ファイルパスが/kakeibo/package/static/であるのに対してwebpack-dev-serverは/static/になります。その差異があるとスプライト画像が404になったためdev用に以下を追記しました。


  //webpack.config.js 70行目付近

  // sprite画像のパスをproductionとdevで共用できるようにproxy設定
  devServer: {
    historyApiFallback: true,
    noInfo: true,
    overlay: true,
    proxy: {
      '/kakeibo/package/static/sprite01.png': {
        target: 'http://localhost:8080/static/sprite01.png',
        secure: false,
        pathRewrite: {'^/kakeibo/package/static/sprite01.png' : ''}
      }
    }
  },

もっといい方法はありそうですが、手っ取り早くproxyにしちゃいました。


htdocs/kakeibo/app/webroot/package/src/

ファイル構成概要

main.jswebpackのエントリーポイント。routerなどのグローバル設定をここに記載。
App.jsOPレベルコンポーネント。router-viewと共通ヘッダを持つ。
router.jsvue-routerの設定ファイル。URLパターンとそれに対応するcomponentを紐づける。
style/CSS Modules用のCSSファイル(sprite.cssはwebpack-spritesmithで生成し、吐き出されたもの)
sprite/スプライト化する前のバラの画像
assets/サードパーティ製のアセットが何かあればここに置いて使用。
config/ajaxurlやカラースキームなどのコンフィグを配置
store/VuexのStoreを配置
components/Vue.jsのコンポーネントを格納

いくつか見ていきます。

main.js

主な仕事はAppのマウントです。またここではfilterという、.vueファイルのtemplate内に出力する変数を任意の形に整形して表示する処理をここで書いています。似たようなものに.vueファイル内で定義する computed がありますが、それをどのcomponent上でも使用可能にしたもの、という認識です。

router.js

React等でもお馴染みのrouterです。URLに応じてマウントするcomponentを出し分けてくれます。URLパターンとコンポーネントを紐づけるだけの簡単な記述です。

大量のコメントアウトが書かれていると思いますが、それはwebpackの設定で「routerで設定したコンポーネント毎に吐き出すJSファイルを分割したい」ときに必要な記述です(チャンク分割というそうです)。今回は分割しなくてもgzip圧縮転送で150KBくらいに収まったのと、このくらいならログイン画面にlinkタグpreloadを仕込んでおけばパフォーマンス上さほどクリティカルにならないと判断してベンダーとロジックの分割にだけに留めました。

ログイン画面のテンプレートhtdocs/kakeibo/app/views/users/login.ctpに仕込んだpreloadの記述例


<!-- bundleのpreload -->
<script type="text/javascript">
var link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
link.href = '/kakeibo/package/static/lib.bundle.js';
document.head.appendChild(link);
</script>
<!-- /bundleのpreload -->

./style/

common.cssとsprite01.cssの2つ。sprite01.cssはwebpack-spritesmithで吐き出されたものです。コマンドでnpm run buildを実行するとここにCSSが吐き出されます。common.cssもsprite01.cssもjsからimportして使用します。詳しくはこの記事の冒頭「CSS周りの仕様」を参照ください。

./config/

カラー設定、AJAXURL設定が格納されています。個々のJSファイルからimportして使用します。

./store/

Vuexの肝です。以下のような構成になっています。

index.js他のファイルを取りまとめ、main.jsでマウント時に登録される。Vuexにおけるエントリーポイント的なファイル。
mutations.jsstateとmutationsを保持。mutationsとはStoreのstateを更新する際の受付窓口になります。後述のactionsから値を引き受け、stateを更新します。
actions.js主にAJAX処理が書かれている。ユーザ操作やアプリケーションの変動をトリガーに実行され、AJAXで取得したデータを整形してmutationsに値を渡します。
mutations-types.jsどういうmutationsがあるか一覧できる。mutations呼び出し名の定義。
getters.jsstateを整形してcomponentなどに渡すらしいですが今回は使いませんでした。

Reduxに慣れていると何でもないんですが、最初はとっつきにくいかもしれません。componentからactionsが実行され、AJAX結果がmutationsに渡されmutationsはstateを更新する。という1方向の流れになります。

何をStoreにするか

VuexのStoreは誤解を恐れずに言うならどのコンポーネントからも読み書きできるグローバルなオブジェクトです。グローバル化が不要なデータは、個々のコンポーネントのdata関数にローカルなstateとして用意できます。

考え方としては、コンポーネントをまたいで使用されそうだったり、コンポーネントのマウント/アンマウントの影響を受けたくないデータなんかを格納するといいと思います。

Loading表示、トーストメッセージもStoreに

AJAX中のLoading表示や、ユーザへのメッセージ通知要素も様々なコンポーネントから呼び出せることが必要条件になります。それぞれLoadingNotificationという独立したコンポーネントにし、routerで表示する上位コンポーネントにそれぞれ読み込ませて使用する想定です。よって「コンポーネントをまたぐ」という条件を満たしますのでStoreにデータを用意しました。


//./store/mutations.js
export const state = {
  startYear: 2013, // アプリケーションの開始年
  category: {},    // カテゴリーIDとカテゴリ名が対になったデータ
  monthly: {
    detail: [],           //月別データを格納
    currentYearMonth: "", //現在の年月
  },
  yearly: {
    categoryDataset: [],
    fuufuDataset: [],
    total: {
      dannna: 0,
      yome: 0,
      sum: 0
    }
  },
  today: today, // 今日のリアル年月
  notification: {// ★コレ
    msg: ""
  },
  isLoading: false, // ★コレ // AJAX中フラグ
  formTitleList: [], // 入力フォームのオートコンプリート値
}

次回からいよいよ個々のコンポーネントについてお話できたらと思います。

ページトップへ

関連ページ

ページトップへ