0
0

More than 1 year has passed since last update.

Railsでページごとに読み込むCSSを限定する

Last updated at Posted at 2021-11-13

Railsで以下の課題があり、解決のためのコードを試し書きしたので概要を記載する

  • ページ単位でCSSのスコープを限定したい
  • 不要なCSSファイルの読込みを削減したい

Disclaimer

この記事は、以下の記事を参考に自分用にカスタマイズしたもの。
Rails の CSS にスコープを持たせてファイル分割する

意図通りに動くのは確認したが、実運用に耐えるのかは不明。
ページ単位は細かすぎて割に合わないので、普通にController単位のスコープ限定/呼び出しにしたほうが良さげ。

課題

railsで以下を実現したい。

ページ単位でCSSのスコープを限定したい

常にビューテンプレートのトップレベルに一定のクラス名を設定すれば実現できるが、
もう少し強制力をもたせたい。

不要なCSSファイルの読みこみを削減したい

現状では、「全てのページで全てのCSSファイルを読み込んでいる」ので、
ページ単位で必要なものだけ読み込む形にしたい。

現状はこういう感じ。

application.scss
@import 'bases/_variable';
@import 'bulma/bulma';

@import 'bases/_base';
@import 'partials/*';
@import 'pages/*';

application.html.slim

doctype html
html
  head
    /...省略
    = stylesheet_link_tag 'application', media: 'all'
    = javascript_pack_tag 'application'

   /...省略

  body
    /省略

解決策

前提

  • SCSS, bulmaを利用
  • rails 6.1.3.2
  • sprockets 4.0.2
  • sprockets-rails 3.2.2

具体的な実装方針/規約

簡単に言うと、"#{controller_name}/#{method_name}"に対応したcss-scope属性を
ページのトップレベル要素として記述し、対応する名前のSCSSをファイルのみを呼び出す。

以下詳細。

  • ビューテンプレート(例: app/views/articles/run_melos.html.scss)
    • css-scope属性を各ページテンプレートの最上位の要素に記載
    • #content_forcss-scopeをlayoutsファイルに渡す
  • layoutsファイル(例: app/assets/layouts/application.html.slim)
    • = stylesheet_link_tag yield(:css_scope) でビューテンプレートに対応するSCSSファイルを呼び出す
  • SCSSファイル(構成)
    • ビューテンプレートに対して、1:1でSCSSファイル(以下「ページSCSS」)を作成
    • ページSCSSのfullpathは、app/assets/stylesheets/pages/{#controller_name}/#{method_name}.scss
    • パーシャルに対して、N:1でSCSSファイル(以下パーシャルSCSS)を作成
  • SCSSファイル(記載内容)
    • ページSCSSから、必要なSCSSファイル(パーシャルSCSS含む)を都度importする
    • ページSCSSのトップレベルに、対応するビューテンプレートのcss-scopeを記載
  • アセットパイプラインの設定ファイル(config/initializers/assets.rb)
    • app/assets/stylesheets/pages/以下のファイルをprecompile対象にするよう設定

実装のポイント

application.html.slim
html 
  head
    / 既存のSCSS体系からの移行用
    - if content_for?(:css_scope)
      / baseでbulma/bulmaを読み込んでいる
      = stylesheet_link_tag 'base', media: 'all'
      = stylesheet_link_tag yield(:css_scope),  media: 'all'
      = stylesheet_link_tag 'partials/_header', media: 'all'
      = stylesheet_link_tag 'partials/_footer', media: 'all'
    - else
      = stylesheet_link_tag 'application', media: 'all'

application_helper.rb
# content_forでcss_scopeをapplication.html.slimに渡しつつ、
# data-css-scopeを設定した(ビューテンプレート内の)トップレベルのHTML要素を作成する
def with_css_scope(tag_name: :div, css_scope:, **options)
  content_for :css_scope, css_scope

  tag_options = options.deep_merge(data: { 'css-scope': css_scope })
  tag.public_send(tag_name, tag_options) { yield }
end
app/views/articles/run_melos.html.slim
= with_css_scope(css_scope: 'pages/articles/run_melos')
  h1.title なぜメロスは怒ったのか
  / 以下普通にHTMLを記述
app/assets/stylesheets/pages/articles/run_melos.scss
// 適宜このSCSSで利用しているSCSSファイルを呼ぶ
@import 'bases/_variable';
@import 'bases/_base';
@import 'partials/_helpers';
@import 'partials/_button';
@import 'partials/_qa';

[data-css-scope='pages/articles/run_melos'] {
  .title {
    font-weight: bold;
  }
  // 以下省略
}
config/initializers/assets.rb
# 省略

Rails.application.config.assets.precompile += %w( 
  application.css 
  partials/_header.css 
  partials/_footer.css 
)

stylesheets_path = 'app/assets/stylesheets/'
Pathname(stylesheets_path + 'pages').
  glob('**/**.scss').
  map  { |e| e.relative_path_from(stylesheets_path).sub('scss', 'css').to_s }.
  each { |path| Rails.application.config.assets.precompile << path }

# 省略
# 注記: https://railsguides.jp/asset_pipeline.htmlにはProcを...assets.precompileに渡す方法が書かれているが
# 仕様が変わったのか受け付けてくれないのでPathnameで個別にファイル名を拾って渡している

実装のポイント(+α)

各View Templateから@virtual_pathが参照できるので活用する。

articles/run_melos.html.slim
/ @virtual_pathの値は articles/run_melos
= with_css_scope(css_scope: "pages/#{@virtual_path}")
  h1.title 何故メロスは怒りやすいのか
  / 以下普通にHTMLを記述

SCSSファイルからbulmaのクラスや変数を呼び出している場合は、該当するbulmaファイルを個別に読み込む。

sample.scss
// 変数
@import 'bulma/sass/utilities/initial-variables';


// ユーティリティっぽいクラス
@import 'bulma/sass/utilities/_all';
@import 'bulma/sass/base/_all';

0
0
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
0
0