Search code examples
cssruby-on-railsyarnpkgimport-maps

Rails 7 with Import Maps and CSS Bundling: Assets Not Served from /builds Directory


Rails 7 with Import Maps and CSS Bundling: Assets Not Served from /builds Directory

I'm working on a Rails 7 application where I'm using Import Maps for JavaScript and CSS Bundling via Yarn instead of Sprockets. Despite following the setup instructions, my assets aren't being served correctly from the /builds directory.

Issue:

When loading my homepage, the CSS file is not found, and I get the following error in the browser console:

Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Request URL: http://localhost:3000/stylesheets/application.css

The rendered HTML for the home page includes:

<link rel="stylesheet" href="/stylesheets/application.css" media="all" data-turbo-track="reload">

However, my CSS file is located at /app/assets/builds/application.css.

Setup:

  • Rails Version: 7.1.3.4
  • Ruby Version: 3.2.2
  • CSS Bundling: Using Yarn scripts for compiling SCSS files
  • JavaScript: Using Import Maps

Directory Structure:

app/assets
├── builds
│   └── application.css
├── config
│   └── manifest.js
├── stylesheets
│   ├── application.bootstrap.scss
│   ├── application.scss

Config Files:

config/environments/development.rb:

Rails.application.configure do
  config.cache_classes = false
  config.eager_load = false
  config.consider_all_requests_local = true
  config.server_timing = true
  config.hosts << "improvement.rocks"

  if Rails.root.join("tmp/caching-dev.txt").exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true
    config.cache_store = :memory_store
    config.public_file_server.headers = {
      "Cache-Control" => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false
    config.cache_store = :null_store
  end

  config.active_storage.service = :local
  config.action_mailer.raise_delivery_errors = false
  config.action_mailer.perform_caching = false
  config.active_support.deprecation = :log
  config.active_support.disallowed_deprecation = :raise
  config.active_support.disallowed_deprecation_warnings = []
  config.active_record.migration_error = :page_load
  config.active_record.verbose_query_logs = true

  config.public_file_server.enabled = true
  config.public_file_server.headers = {
    'Cache-Control' => "public, max-age=#{1.hour.to_i}"
  }
end

app/assets/config/manifest.js:

//= link_tree ../images
//= link_tree ../builds

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Improvement.Rocks</title>
    <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet">
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbo-track': 'reload' %>
    <%= javascript_importmap_tags %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header page-scroll">
          <a class="navbar-brand" href="#page-top">The Improvement Project</a>
        </div>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
          <ul class="nav navbar-nav navbar-right">
            <%= render 'layouts/navigation_links' %>
          </ul>
        </div>
      </div>
    </nav>
    <main role="main">
      <%= yield %>
    </main>
  </body>
</html>

Steps Taken:

  1. Ensured that application.css is generated in the /builds directory using the following Yarn script:

    "scripts": {
      "build:css": "yarn build:css:compile && yarn build:css:prefix",
      "build:css:compile": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
      "build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
      "watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec "yarn build:css"",
      "build:js": "esbuild app/javascript/application.js --bundle --outfile=app/assets/builds/application.js"
    }
    
  2. Added public file server configuration in development.rb.

Help Please!

I need help understanding why the assets are not being served from the /builds directory and why the generated HTML still references /stylesheets/application.css instead of /builds/application.css. I'm at a loss for the next step.


Solution

  • You still need sprockets:

    # Gemfile
    
    gem "sprockets-rails"
    

    The only alternative to sprockets is propshaft.

    cssbundling-rails compiles css, then sprockets does the actual serving and precompilation for production and it handles asset urls.


    why the assets are not being served from the /builds

    Nobody's there to serve them. Sprockets serves asset from /assets url, when a request comes in rails will look in /public/assets for the exact match then sprockets will look in asset paths (Rails.application.assets.paths) and respond with a first matching file (ignoring the digest part):

    # sprockets generates the url
    >> helper.stylesheet_path("application")
    => "/assets/application-a72913f4c604bc8f97dd576fc8777bf029401f6af0e14b6a6c9d3874acfe73e4.css"
    
    # and responds with a file
    >> Rails.application.assets.find_asset("application.css").filename
    => "/home/alex/code/stackoverflow/app/assets/builds/application.css"
    >> Rails.application.assets.find_asset("application.css").source
    => "/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid
    ...
    

    JavaScript: Using Import Maps

    You're using esbuild and importmaps just load esbuild bundle. Pick one or the other, not both.

    https://github.com/rails/importmap-rails#installation
    https://github.com/rails/jsbundling-rails#installation

    Don't need javascript_include_tag 'application' with importmaps.