SPAでない普通のMPA Railsアプリにwebpacker + vueを導入した時の設計メモ

CoffeeScript + jQueryで作られている普通のMPA railsアプリに既存の実装はそのまま、リニューアルする画面からwebpacker + vueに移行したのでその時のメモです。 細かい設定についてはたくさん記事が出ているので、今回はこんな設計方針にしたよというところにフォーカスしてます。

はじめに

webpackについては基本的な設定などは理解してるものの、フロント専任の人がいないのでwebpackerに乗っかることにしました。

無理なく、できるだけシンプルで簡単にやろうという方針です。 フロント専任の人が現れたら、より良くしてもらおうと考えています。

下記を前提としてます。

  • webpackerは、当時最新の3.x
  • SPAではなく、ページごとに画面遷移を伴う一般的なRailsアプリがベース
  • 単一ファイルコンポーネントは利用せず、Railsのviewにvueを組み込む方式

全体構成

root
├── app/frontend
│   ├── images          # 画像
│   ├── javascripts     # js実装はココ
│   ├── packs           # webpackのentryファイル(ページ毎に1entry想定)
│   └── stylesheets     # css
│
├── config
│   ├── webpack  # webpack.configはココ
│   │   ├── loaders  # 追加したloader設定
│   │   │   └── **    
│   │   ├── configuration.js    # 設定ファイル
│   │   ├── custom.js           # 追加config
│   │   ├── development.js      # dev環境向けconfig
│   │   ├── staging.js          # stg環境向けconfig
│   │   ├── production.js       # production環境向けconfig
│   │   └── test.js             # test環境向けconfig
│   │
│   └── webpacker.yml  # webpackerの設定
│
├── node_modules  # yarn installした依存ライブラリはここに出力される
│   └── ...
│
├── public
│   └── assets/packs  # コンパイルしたbundleファイルが出力される
│       └── ...
│
├── .babelrc
├── .eslintrc.js
├── .postcss.config.js
├── package.json
└── yarn.lock

※ webpackerにより追加されたもののみ記載

webpack管理下のファイルは、app/frontendに配置します。

  • entryファイルについて
    • 必要なjs/cssをimportするだけのファイル
    • ページごとにentry fileを作成する(各ページに必要なjs/cssだけを読み込むようにする)
    • app/frontend/packs以下にcontroller同様のパッケージ構成で配置する
  • jsについて
    • app/frontend/javascripts以下にcontroller同様のパッケージ構成で配置する
  • cssについて
    • app/frontend/stylesheets以下にcontroller同様のパッケージ構成で配置する
  • imageについて
    • app/frontend/images以下に配置する

具体例

下記のような画面を持つRailsアプリをもとに具体的に見ていきます。

  • ホーム画面を持つ
  • ユーザー画面の詳細、編集画面を持つ
  • PCとモバイルでレイアウトは別々で、variantでviewを切り分けている
app/controllers
├── users_controller.rb  # show, editアクション
└── home_controller.rb   # indexアクション

app/views
├── users
│   ├── edit.html.slim
│   ├── edit.html+mobile.slim
│   ├── show.html.erb
│   └── show.html+mobile.slim
├── home
│   ├── index.html.slim
│   └── index.html+mobile.slim
└── layouts
    ├── application.html.slim
    └── application.html+mobile.slim

application全体のファイルを準備する

  • app/frontend/packs直下にapplication.jsを作成する
  • app/frontend/stylesheets直下にapplication.scssを作成する

1画面ごとにvueのインスタンスを1つ生成するので、レイアウト全体のスタイルと画像を出力するためのファイルになります。

app/frontend/packs/application.js

/**
 * Entry file for application.
 */
import '@css/application.scss';

// 必要画像を読み込む
function importAll(context) {
  context.keys().forEach(context);
}
importAll(require.context('@img/, true, /\.svg$/));

app/frontend/stylesheets/application.scss

// 全体のcssは必要なものをimportするだけ
// イメージはこのような感じ
@charset "utf-8";

// globals(変数やらmixnやら)
@import "globals/variables";

// third party
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/solid';

// base (リセットcssなど)
@import "reset.css";

// structure(構造系のcss)
@import "header";

// modules
@import "modules/btn-hoge";
@import "modules/btn-foo";

出力したcssをviewで読み込みます。

app/views/layouts/application.html.slim

doctype html
  html lang='ja'
    head
      / stylesheets
      = stylesheet_pack_tag 'application', media: 'all'

      / ページ固有のcssなどをロード
      - if content_for?(:head_tags)
        == yield(:head_tags)
    body
     .main
        / 各画面のコンテンツ
        == yield

     / 各画面のjsをロード
     - if content_for?(:js)
      == yield(:js)

各画面のファイルを準備する

例えば、ユーザー編集画面(editアクション)のファイルを準備します。

  • app/frontend/packs/users/直下にedit.jsを作成する
  • app/frontend/stylesheets/users直下にedit.scssを作成する

各画面のpacksファイルでは、必要なjsとcssを読み込みます。 これにより、jsとcssがpublic以下に出力されます。(出力先は設定ファイルで指定)

app/frontend/packs/users/edit.js

/**
 * Entry file for users/edit
 */
import '@css/users/edit.scss';
import '@js/users/edit';

app/frontend/javascripts/users/edit.js

/**
 * users/edit
 */
export default new Vue({
  el: '.main',
  data:{
  },
  created() {
  },
  methods: {
  },
  computed: {
  }
});

app/frontend/stylesheets/users/edit.scss

@import "globals/variables";

.users-edit {
  ...
}

出力したjs/cssをviewで読み込みます。

app/views/layouts/users/edit.html.slim

/ application.htmlで差し込めるようにしておく

/ head
- content_for :head_tags do
  = stylesheet_pack_tag 'users/edit'

/ js
- content_for :js do
  = javascript_pack_tag 'users/edit'

このような形で、すべての画面分のファイルを準備します。

モバイル用の各画面のファイルを準備する

今回はレスポンシブデザインではなく、PC/モバイルをvariantによって切り分けている前提なので、モバイル用のファイルも準備します。

frontend側は、各パッケージ以下にpacks/mobileのようにmobileパッケージを切って同じようにファイルを用意し出力したjs/cssを読み込みます。

完成形

↓↓↓のような形になります。

app/frontend
├── javascripts
│   └── users
│       ├── edit.js
│       └── show.js
├── packs
│   ├── mobile
│   │   ├── users
│   │   │   ├── edit.js
│   │   │   └── show.js
│   │   └── application.js
│   ├── users
│   │   ├── edit.js
│   │   └── show.js
│   └── application.js
└── stylesheets
    ├── mobile
    │   ├── users
    │   │   ├── edit.scss
    │   │   └── show.scss
    │   └── application.scss
    ├── users
    │   ├── edit.scss
    │   └── show.scss
    └── application.scss

よく見るとjsの実装ファイルは、PC/モバイルと別れていません。画面の作りは違うけど機能は同じ前提で作りました。 万が一異なる場合は、実装ファイルできょうつの処理は親クラスを用意して別の処理は同様にmobileパッケージを切ってそれぞれに実装して、pakcs以下のEntryファイル内で読み込むjsを切り替えるなど一工夫必要になってしまいます。

おわりに

設計メモのため、webpacker周辺知識をあるていど持っていることが前提となってしまいました。(しかも力尽きて全体的に雑に...) 冒頭にも書きましたが、そのあたりはたくさんの方が記事を書いてくれているので調べてみると良いと思います。

具体的にどういう構成で作ったら良いかちょっと悩んでしまって、調べても割にSPA系の記事が多かったような気がするので一つの例として参考になったら嬉しいです。

私はバックエンドのエンジニアなので、css周りでも結構悩んで調べていろいろと発見がありました。 書くのが大変なので、良かったリンクを参考に貼り付けておきます。

qiita.com

気づけばwebpacker4.xがリリースされていたのでアップデートしないと!