config.assets.pathsの中に含まれる全てのapplication.{js,css}がprecompile対象に含まれてしまう問題に対処する

bowerやnpm経由で外部のパッケージに含まれるCSSやJavaScriptを利用したいときに、Rails.configuration.assets.pathsを変更し、Sprocketsを経由してこれを読み込むことがあります。

config.assets.paths << "node_modules"
@import "bootstrap-sass/assets/stylesheets/bootstrap";

なにが困るか

このとき、node_modules内に application.css.ejs.scss のようなファイルが含まれると、rake assets:precompileの対象に含まれてしまう場合があります。bootstrap-sassの例で言えば、node_modules/bootstrap-sass/test/dummy_node_mincer/application.css.ejs.scss が存在するため、これをprecompileしようとして、Sass::SyntaxErrorが発生してしまいます。

なお、この問題は all application.(js,css) files pulled in · Issue #218 · rails/sprockets-rails で報告されていて、次のメジャーバージョンでは修正されるようです。

なぜ起こるか

sprockets-railsでは、lib/sprockets/railtie.rb で最初から /(?:\/|\\|\A)application\.(css|js)$/ に一致するものをprecompile対象にしています。app/assets/javascripts/application.js や、他のGemに含まれるそういったファイルなどが対象ですね。

application\.(css|js)$ なので application.css.ejs.scss には一致しないだろうと思いきや、sprockets/asset_attributes.rb at v2.12.3 · sstephenson/sprockets を見ると分かるように、実際には logical_path という、engine_extensionsから取り出せる幾つかの拡張子を取り除いたパスに対してチェックを行います。

[1] pry(main)> Sprockets::Environment.new.engine_extensions
=> [".coffee", ".jst", ".eco", ".ejs", ".less", ".sass", ".scss", ".erb", ".str"]

このように、.ejs や .scss が登録されているため、/path/to/application.css.ejs.scss のlogical_pathは /path/to/application.css となり、先述した /(?:\/|\\|\A)application\.(css|js)$/ の正規表現に一致することになります。

回避策

/(?:\/|\\|\A)application\.(css|js)$/ を必要としていない場合は、これを取り除きます。::Sprockets::Railtie::LOOSE_APP_ASSETS を忘れるとapp/assets以下のフォントファイルなどがコンパイルされなくなってしまうので、必要な場合は忘れないようにしましょう。

config.assets.precompile = [
  ::Sprockets::Railtie::LOOSE_APP_ASSETS,
  'application.css',
  'application.js',
]