作業ログ Railsにwebpack導入 2019/10/13

この記事の続きです

kenta-s.hatenadiary.jp

ユーザー登録からログインまでをマテリアルデザインでいい感じの見た目になるところまで目指します。

まず

ざっくりと流れを考えると、

  1. ユーザー登録とログインが動作することを確認
  2. webpack導入(webpackerは使わない)
  3. マテリアルデザイン導入(materialized cssでしたっけ?)
  4. 見た目整える

のような感じになると思います

まず http://localhost:3000/users/sign_up にアクセスすると、webpackerを消したので以下のようなエラーが出ます。

undefined method `javascript_pack_tag' for #<#<Class:0x00007fca896de970>:0x00007fca896d2af8>

assetsはwebpackで管理するようにするのでlayouts/application.html.erbを修正します

<!DOCTYPE html>
<html>
  <head>
    <title>Alder</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

-    <%= stylesheet_link_tag 'application', media: 'all' %>
-    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

でました。シンプルで良いですね(よくない)。

f:id:Kenta-s:20191013115726p:plain

ところで、webpackerは普通にwepack.config.jsを使えるようにしてさえくれたら喜んで使う人は多いと思いますが、なんであんな風なんでしょうね。

deviseのviewをgenerateします

github.com

$ bundle exec rails generate devise:views
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

いまのままではUserモデルに追加したdisplay_nameがprecense: trueなのでユーザー登録ができません。

Display name can't be blank

フォームに以下を追加しておきます

<%= f.label :display_name %><br />
<%= f.text_field :display_name %>

ということはstrong_parameterもいじらないといけないのでdeviseのcontrollerもgenerateします

github.com

と思ったんですが、ApplicationControllerにメソッドを追加すればいいんでした。ドキュメントに書いてましたね。

class ApplicationController < ActionController::Base
+  before_action :configure_permitted_parameters, if: :devise_controller?

+  protected

+  def configure_permitted_parameters
+    devise_parameter_sanitizer.permit(:sign_up, keys: [:display_name])
+  end
end

登録からログインまで動作も問題ありませんでした。

続いてwebpack導入です

$ yarn add webpack
$ yarn add webpack-cli

yarn dev でdevelopment用にビルド、yarn build でproduction用にビルドできるように、package.jsonにscriptsを追加しておきます。

{
  "name": "alder",
  "private": true,
  "dependencies": {
    "@rails/actioncable": "^6.0.0-alpha",
    "@rails/activestorage": "^6.0.0-alpha",
    "@rails/ujs": "^6.0.0-alpha",
    "webpack": "^4.41.1",
    "webpack-cli": "^3.3.9"
  },
+  "scripts": {
+    "build": "webpack --mode=production",
+    "dev": "webpack --mode=development"
+  },
  "version": "0.1.0"
}

つぎはwebpack.config.jsです。ちゃんとやるとしんどいポイントなので、とりあえず動くところまでやります。

const ManifestPlugin = require('webpack-manifest-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

module.exports = {
  context: path.resolve(__dirname, 'app', 'javascript', 'packs'),
  entry: {
    application: './application.js',
  },
  output: {
    path: path.resolve(__dirname, 'public', 'packs'),
    filename: '[name]-[hash].js'
  },
  plugins: [
    new ManifestPlugin({
      fileName: 'manifest.json',
      publicPath: '/packs/',
      writeToFileEmit: true,
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name]-[hash].css',
      chunkFilename: '[id].css',
      ignoreOrder: false,
    }),
  ]
};

というわけでマニフェストCSSを良い感じに使いたいのでプラ銀追加します。

$ yarn add webpack-manifest-plugin
$ yarn add mini-css-extract-plugin

webpackerが生成したapplication.jsをエントリーポイントにしておきます。application.jsの中身はとりあえず以下です。

console.log('hello work')

続いて、RailsのViewから読み込めるようにヘルパーメソッドを作ります。

module WebpackBundleHelper
  class BundleNotFound < StandardError; end

  def asset_bundle_path(entry, **options)
    raise BundleNotFound, "Could not find bundle with name #{entry}" unless manifest.key? entry
    asset_path(manifest.fetch(entry), **options)
  end

  def javascript_bundle_tag(entry, **options)
    path = asset_bundle_path("#{entry}.js")

    options = {
      src: path,
      defer: true
    }.merge(options)

    if options[:async]
      options.delete(:defer)
    end

    javascript_include_tag '', **options
  end

  def stylesheet_bundle_tag(entry, **options)
    path = asset_bundle_path("#{entry}.css")

    options = {
      href: path
    }.merge(options)

    stylesheet_link_tag '', **options
  end

  private

  def manifest
    return @manifest if @manifest
    @manifest ||= JSON.parse(File.read(Rails.root.join('public', 'packs', 'manifest.json')))
  end
end

これでmanifest.jsonからハッシュ値付きのファイル名を解決できるようになるはずです

<!DOCTYPE html>
<html>
  <head>
    <title>Alder</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
+    <%= javascript_bundle_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

ビルドします

$ yarn dev
yarn run v1.19.0
$ webpack --mode=development
Hash: 0a5a7288b52342b5060e
Version: webpack 4.41.1
Time: 104ms
Built at: 10/13/2019 1:13:00 PM
                              Asset      Size       Chunks                         Chunk Names
application-0a5a7288b52342b5060e.js  4.54 KiB  application  [emitted] [immutable]  application
                      manifest.json  68 bytes               [emitted]
Entrypoint application = application-0a5a7288b52342b5060e.js
[./application.js] 752 bytes {application} [built]
Done in 1.28s.

うまくいってそうですね。

$ cat public/packs/manifest.json
{
  "application.js": "/packs/application-0a5a7288b52342b5060e.js"
}

ブラウザで開いていたタブをリロードするとコンソールに

hello work

と表示されました。webpackの導入はこれで(とりあえず)OKです。

キリが良いのでいったんここで記事を切ります。