Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: webpack管理のフロントエンドまわりのファイルも含めてViewを作成するジェネレーターを作った

個人のサービスでwebpackでフロントエンド関連のファイルを管理していてsimpackerを使ってRailsで読み込むみたいなことを行なっているのですが。。。

新規のページをつくるときとかに手でエントリーのJSを用意してページ用のCSSファイルを作ってインポートして、View側にjavascript_pack_tagといった読み込み用のヘルパーを記載するのが面倒だったので、ジェネレーターを自作してコマンド実行でそれらを行えるようにしました。

作成したジェネレーター

作成したのは以下のようなview_with_front_endで引数にcontrollerの名前とactionの配列を渡すとview及びエントリーの.ts,.scssを作成してくれるジェネレーターです。

$ bin/rails g view_with_front_end foo index show
      create  app/javascript/packs/foo/index.ts
      create  app/javascript/stylesheets/foo/index.scss
      create  app/views/foo/index.html.erb
      create  app/javascript/packs/foo/show.ts
      create  app/javascript/stylesheets/foo/show.scss
      create  app/views/foo/show.html.erb

実際に作成されるファイルは以下のような形でviewでは、javascript_pack_tagstylesheet_pack_tagで作成したscsstsをデフォルトで読み込むようにしてくれます。

import "@css/foo/index.scss";

console.log("foo/index");
h1 {
  color: blue;
}
<%= stylesheet_pack_tag 'foo/index' %>
<%= javascript_pack_tag 'foo/index', defer: true %>
<h1>foo/index</h1>

実際のコードは以下の通りです。環境によって変更されるであろう値(エントリーのjsファイルのパス等)は、optionで指定できるようにしています。

# frozen_string_literal: true

class ViewWithFrontEndGenerator < Rails::Generators::NamedBase
  VIEW_EXTENSION = 'erb'
  ENTRY_JS_PATH = 'app/javascript/packs'
  ENTRY_JS_EXTENSION = 'ts'
  CSS_PATH = 'app/javascript/stylesheets'
  CSS_EXTENSION = 'scss'
  CSS_PATH_ALIAS = '@css'

  argument :actions, type: :array, default: [], banner: 'action action'
  class_option :view_extension, type: :string, default: VIEW_EXTENSION
  class_option :entry_js_path, type: :string, default: ENTRY_JS_PATH
  class_option :entry_js_extension, type: :string, default: ENTRY_JS_EXTENSION
  class_option :css_path, type: :string, default: CSS_PATH
  class_option :css_extension, type: :string, default: CSS_EXTENSION
  class_option :css_path_alias, type: :string, default: CSS_PATH_ALIAS

  desc <<~TEXT
    Description:
        Create View with FrontEnd files(JS/CSS).
    Example:
        $ bin/rails g view_with_front_end foo index show
          create  app/javascript/packs/foo/index.ts
          create  app/javascript/stylesheets/foo/index.scss
          create  app/views/foo/index.html.erb
          create  app/javascript/packs/foo/show.ts
          create  app/javascript/stylesheets/foo/show.scss
          create  app/views/foo/show.html.erb
  TEXT

  def create_files
    build_instance_valiables(options)
    actions.map do |action|
      create_file "#{@entry_js_path}/#{@resource}/#{action}.#{@entry_js_extension}", js_file_body(action)
      create_file "#{@css_path}/#{@resource}/#{action}.#{@css_extension}", css_file_body
      create_file "app/views/#{@resource}/#{action}.html.#{@view_extension}", view_file_body(action)
    end
  end

  private

  def build_instance_valiables(options)
    @resource = file_name
    @view_extension = options['view_extension']
    @entry_js_path = options['entry_js_path']
    @entry_js_extension = options['entry_js_extension']
    @css_path = options['css_path']
    @css_extension = options['css_extension']
    @css_path_alias = options['css_path_alias']
  end

  def js_file_body(action)
    <<~ERB
      import "#{@css_path_alias}/#{@resource}/#{action}.#{@css_extension}";

      console.log("#{@resource}/#{action}");
    ERB
  end

  def css_file_body
    <<~ERB
      h1 {
        color: blue;
      }
    ERB
  end

  def view_file_body(action)
    <<~ERB
      <%= stylesheet_pack_tag '#{@resource}/#{action}' %>
      <%= javascript_pack_tag '#{@resource}/#{action}', defer: true %>
      <h1>#{@resource}/#{action}</h1>
    ERB
  end
end

ジェネレーターを自作するときの注意事項

ガイド通りに作るとlib配下をeager_loadingするようにしていると、自動読み込み周りでエラーが発生しました。。。

railsguides.jp

以下の対応が必要なようです。

# NOTE: generatorは自動読み込みでエラーになるのでzeitwerkによるautoloadの対象外にする
# https://github.com/rails/rails/issues/38671
Rails.autoloaders.main.ignore(Rails.root.join('lib/generators/**/*.rb'))

github.com

参考

railsguides.jp