6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Rails】 Webpack経由でBootStrap、material-icons、vue.js、自作css、sassを一つにまとめて、capistranoでdeployする方法

Last updated at Posted at 2018-06-15

初めに

こんにちは。今回の記事ではwebpackを利用してJSやCSS、SCSSを一纏めにし、capistranoでデプロイする方法がようやくできたのでここに執筆します。
筆者はまだ、webpack自体がなんなのか非常に曖昧な所があり、この記事の執筆が完了するまでの間には明確に理解できているという願掛けも込めてどのような方法を取ったのか共有します。そのせいか、色々なツールの公式ドキュメントの直訳みたいなところになってしまっているところもありますが、追々直していきます汗

各ツールのバージョン

  • Rails 5.2.0
  • webpack 3.12.0
  • ruby 2.5.1p57
  • bootstrap-material-design-icons 2.2.0
  • bootstrap.native 2.0.23(JSのみ利用)
  • bootstrap 4.1.1(CSSのみ利用)
  • vue 2.5.16
  • その他、自作のJSやCSS,SASS

webpackとは

webpackとは、複数のJavaScriptを一つのファイルにまとめる事ができるモジュールバンドラです。仕組みとしては、アプリケーションで必要とする全てのモジュールをマッピングし、依存関係のグラフを内部的に構築している模様です。
webpack4以降では各アプリケーションの設定が基本的には必要なく、https://webpack.js.org/configuration/ に乗っ取ってカスタマイズすればあらかたコンパイルできる模様です。

さて、最初にJSのファイルをまとめると供述してしまったのですが、webpackは基本的に以下の5つの定義でグラフを構築します。

  1. Entry
  2. Output
  3. Loaders
  4. Plugins
  5. Mode

ここで、特に重要なのが、 LoadersPlugins の二つです。

LoadersはJS以外の拡張子のファイルやモジュールを依存グラフに追加できるように変換してくれます。使い方として、

  1. test 項目で変換対象のファイルを識別
  2. use 項目で変換対象のファイルで利用するローダーを指定

する事でJS以外のファイルをwebpackに追加する事ができます。

さらにPluginsでは、Loadersで変換を行なったファイルに対して機能拡張を行なってくれます。ここで扱うPluginはnpmやYarnなどのパッケージマネージャ経由でインストールし、 require() で利用したいプラグインを追加することができます。
webpackの概要はこのあたりにしておいて、次から Rails アプリケーションの例でwebpackを導入してみましょう。

Railsにwebpackを導入

webpackを導入したRailsアプリを構築するために、新たにRailsアプリを作成します。
手順としてはhttps://techracho.bpsinc.jp/hachi8833/2017_12_26/49931 の記事を参考にします。
この記事の手順を要約すると、

  1. app/javascriptsapp/frontend に名前変更
  2. application.html.erbjavascript_include_tag "application"javascript_pack_tag "application" に置き換え
  3. application.html.erbstylesheet_link_tag 'application', media: 'all'javascript_pack_tag "application" に置き換え
  4. webpacker.yml でバンドルを探索する場所を frontend に変更
  5. application_controller.rbfrontend をコントローラが見つけられるように指定

HTMLのテンプレートエンジンを slim に置き換えている場合は適宜読み変えてください。

capistranoの導入

Capistrano 関係のGemは以下を入れています。

Gemfile
(中略)
  gem 'capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano-ext'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano-npm'
  gem 'capistrano3-puma'
  gem 'capistrano-postgresql'

本当は npm ではなく、より機能が豊富な yarn でdeploy時にパッケージを追加するべきなのですが、一旦 npm で都度deploy時にパッケージをインストールするようにします。

deploy.rb は以下のように書きました。

config/deploy.rb
(中略)
namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :check_revision do
    on roles(:app) do
      # unless `git rev-parse HEAD` == `git rev-parse origin/master`
      #   puts "WARNING: HEAD is not the same as origin/master"
      #   puts "Run `git push` to sync changes."
      #   exit
      # end
    end
  end

  desc 'Run rake npm install'
  task :npm_install do
    on roles(:web) do
      within release_path do
        execute("cd #{release_path} && npm install")
      end
    end
  end
  desc 'Initial Deploy'
  task :initial do
    on roles(:app) do
      before "deploy:restart", "puma:start"
      invoke "deploy"
    end
  end

  desc "Restart application"
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      Rake::Task["puma:restart"].reenable
      invoke "puma:restart"
    end
  end
  before :starting, :check_revision
  before 'deploy:assets:precompile', 'deploy:npm_install'
  after :finishing, :compile_assets
  after :finishing, :cleanup
end

先ほど申し上げた通り、deploy時にnpm installでdeploy先ディレクトリの node_modules配下にパッケージをインストールするようにします。

Webpackの設定

最後に、webpackの設定です。

webpack/production.js

const webpack = require('webpack');
const path = require('path');

/**
 * Require webpack plugins
 */
const environment = require('./environment');
const ManifestPlugin = require('webpack-manifest-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

/**
 *  Entries
 */
const entries = {
    application: ['./frontend/packs/application.js'],
    markdown_preview: ['./frontend/packs/markdown_preview.js']
}

module.exports = Object.assign({}, environment.toWebpackConfig(), {
    entry: entries,
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.sass$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=image/svg+xml'
                }],
            },
            {
                test: /\.woff(\d+)?(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.(jpg|png|gif)$/,
                use: [{
                    loader: 'file-loader?name=[name]-[hash].[ext]'
                }],
            }
        ]
    },
    plugins: [
        new ManifestPlugin(),  // manifest.jsonを出力するプラグイン
        new ExtractTextPlugin({ // define where to save the file
            filename: "./application.css",
            allChunks: true,
        }),
    ],
    resolve: {
        modules: [
            'node_modules',
            path.join(__dirname, 'frontend')
        ],
        alias: {
            vue: 'vue/dist/vue.esm.js'
        },
        extensions: ['.js', '.css', '.scss'],
    }
})

各設定を見てみましょう。

  • extract-text-webpack-plugin

extract-text-webpack-plugin は、JSの中でimportされているcssファイルをJSの中で展開せずに、外部CSSファイルとして展開するために、定義しています。

  • entries
const entries = {
    application: ['./frontend/packs/application.js'],
    markdown_preview: ['./frontend/packs/markdown_preview.js']
}

entriesでは、JSファイルを展開する際のファイル名を明示的に指定します。
今回のプロジェクトでは、javascript_pack_tag "application" (および、vueのmarkdownプレビュー用のJS)を slimのheadに読み込むように設定しているため、
application.js (プロジェクトで共通利用するjsファイル)とmarkdown_preview.js と名前づけています。

  • test

            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },

上のjsファイルでは、 node_modules をコンパイルするのは避けたいため、 excludeでコンパイル対象から除外しています。
また、 babel-loader のloaderを利用して、 JSをBabelでコンパイルできるようにしています。


            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.sass$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader"
                    }, {
                        loader: "sass-loader"
                    }],
                    // use style-loader in development
                    fallback: "style-loader"
                })
            },
            {
                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=image/svg+xml'
                }],
            },
            {
                test: /\.woff(\d+)?(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
                use: [{
                    loader: 'url-loader?mimetype=application/font-woff'
                }],
            },
            {
                test: /\.(jpg|png|gif)$/,
                use: [{
                    loader: 'file-loader?name=[name]-[hash].[ext]'
                }],
            }
        ]
    },
    plugins: [
        new ManifestPlugin(),  // manifest.jsonを出力するプラグイン
        new ExtractTextPlugin({ // define where to save the file
            filename: "./application.css",
            allChunks: true,
        }),
    ],
    resolve: {
        modules: [
            'node_modules',
            path.join(__dirname, 'frontend')
        ],
        alias: {
            vue: 'vue/dist/vue.esm.js'
        },
        extensions: ['.js', '.css', '.scss'],
    }
})

対して、js以外のファイルは各ファイルに応じたloaderを利用してコンパイルできるようにします。
なお、extract-text-webpack-plugin 経由でコンパイルする場合は fallbackstyle-loader を使わないようにします。
style-loader はJSのコード中でstylesheetをrequire出来るようになるloaderですが、extract-text-webpack-pluginで外部CSSファイルに展開したいので、これを定義してしまうと設定が競合してしまいます。

BootStrapやmaterial-iconsで定義されている画像もここで一緒にコンパイルします。

  • webpack-manifest-plugin

webpack-manifest-plugin では、webpackでコンパイルしたファイルよりmanifest.jsonを自動的に生成してくれます。 manifest.jsonは、Webアプリやサイトを表示する方法を制御してくれるjsonファイルです。

  • resolve

    resolve: {
        modules: [
            'node_modules',
            path.join(__dirname, 'frontend')
        ],
        alias: {
            vue: 'vue/dist/vue.esm.js'
        },
        extensions: ['.js', '.css', '.scss'],
    }

最後の resolve の項目ですがこれにより、コンパイルされたモジュールの解決方法を定義します。
なお、今回、 Vue.js も利用しているのですが、 Vue.js の書き方によっては、ブラウザでアクセスした際にコンパイルが走るような設定をする必要があります。今回の書き方がそうでした。
そこで、webpackの alias の設定を設定することで、 vueファイルをimport(require)できるようにしてあります。
以上で、BootStrap, material-icons, vue.js, 自作css, sassをwebpack経由でコンパイルすることができます。

後は
% bundle exec cap production deploy
でサーバデプロイをしてあげましょう。

まとめ

ということでwebpackによる設定を行い、capistranoでdeployする手順でした。
フロントエンドエンジニアの方には「なんだこの設定は」って突っ込まれそうな設定ですが(実際deployには1分程度かかる。主に、npm install)、及第点な設定は書けたような気がするので、今後はコンパイルが早くなるような設定を目指します。

また、Rails5.1よりAPIモードでrails newすることが可能となったので、vueでMVVMを任せてRailsはAPIとして通信するようなWebアプリを作っていきたいですね。
今回はここまで。

参考文献

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?