Skip to content

Migrate the asset pipeline from Sprockets to Propshaft - #535

Merged
cycomachead merged 1 commit into
cycomachead/211-upgrade-css-compilation/1from
cycomachead/211-propshaft-spike
Aug 6, 2026
Merged

Migrate the asset pipeline from Sprockets to Propshaft#535
cycomachead merged 1 commit into
cycomachead/211-upgrade-css-compilation/1from
cycomachead/211-propshaft-spike

Conversation

@superconductor-for-github

@superconductor-for-github superconductor-for-github Bot commented Aug 5, 2026

Copy link
Copy Markdown

Stacked on #527. Base branch is cycomachead/211-upgrade-css-compilation/1, so this diff shows only the Propshaft delta. Merge #527 first (see Before this can land below).

Follow-on to the Dart Sass migration in #527. Same compiler, different plumbing: Propshaft digests and serves what is on disk and does no transformation of its own, so Dart Sass writes app/assets/builds/application.css and Propshaft takes it from there.

sprockets-rails      ->  propshaft
dartsass-sprockets   ->  dartsass-rails

Propshaft has been the default asset pipeline for new Rails apps since Rails 8.

What goes away

  • app/assets/config/manifest.js
  • the config.assets.precompile list in config/initializers/assets.rb
  • five Sprockets-only environment settings: assets.debug, assets.digest, assets.quiet, assets.compile, assets.css_compressor

Four things that needed handling

1. Font Awesome webfonts 404'd. font-awesome-sass emits font-path() only when Sprockets' Sass helpers are loaded, and otherwise falls back to #{$fa-font-path}/#{$file} — i.e. ../webfonts/…, which resolves to nothing. Fixed by setting $fa-font-path to the gem's logical asset path and letting Propshaft rewrite the url() to a digested one.

2. Gem Sass sources leaked into public/assets. Propshaft has a single load path and serves everything on it, and the bootstrap and font-awesome-sass gems put their Sass sources there so application.scss can @import them — so the first precompile digested 110 .scss partials into public/assets (11 MB → 9.2 MB once fixed).

They can't simply be excluded: dartsass-rails derives its --load-path list from config.assets.paths, and Propshaft's excluded_paths removes entries from that same array, so excluding them breaks the build. config/initializers/dartsass.rb therefore passes the two directories to Dart Sass explicitly as --load-path and then takes them off Propshaft's path. (font-awesome-sass/assets/fonts has to stay — that's where the webfonts live.)

3. The CSS is a build artifact now. A checkout without app/assets/builds/application.css raises Propshaft::MissingAssetError rather than rendering unstyled, which is the right failure mode but means:

  • bin/setup builds it, so a fresh clone doesn't 500 on first page load
  • Procfile.dev gains a css process (rails dartsass:watch), so make dev keeps it fresh
  • the three workflows that invoke rspec/cucumber directly get a Build CSS step

4. Dev output regressed. dartsass-rails defaults to --style=compressed --no-source-map in every environment, where sassc-rails only compressed outside development. Development now builds expanded, with source maps pointing back at the .scss sources.

Verification

  • rspec — 584 examples, 0 failures
  • rspec --tag a11y — 40 examples, 0 failures, and all 40 screenshots pixel-compared against the Sprockets build: 20 byte-identical, the rest differing only where generated timestamps change column widths. No styling changes.
  • cucumber — unchanged at the same 5 pre-existing failures (enrollments.feature, requests.feature; these fail on main too)
  • Production boot: CSS, webfonts and all 27 local importmap assets return 200; no .scss in public/assets
  • Development boot: 200, expanded CSS with a working digested source map
  • Compiled CSS is token-for-token identical to the Sprockets output (14,377 tokens both) apart from minifier cosmetics — nested calc() and leading zeros. 351,895 vs 358,871 bytes.
  • Checked .platform/.ebextensions for gzip_static: nothing there, so losing Sprockets' .gz files is a non-issue.

Relationship to #527

#527 has been rebased onto main and is now mergeable: clean. Resolving it kept the SCSS approach, deleted public/datatables/ and dropped the two <link> tags — and went further, per review: the vendored DataTables copies are gone entirely, replaced by @imports out of node_modules pinned to the same versions as the JavaScript in config/importmap.rb.

That means node_modules is a prerequisite for compiling CSS, and #527 already adds the Node setup to the Docker build stage, the three test workflows and bin/setup. This PR carries the dartsass-rails equivalent of that load-path config.

Merge #527 first; this one is stacked on it.

Please check

Blazer (/admin/blazer) and Faultline (/admin/errors). Both engines boot and their assets precompile, and Blazer 3.4 ships a Propshaft-aware layout branch that lists each file individually instead of relying on //= require — but I couldn't authenticate to click through either UI.

@superconductor-for-github
superconductor-for-github Bot force-pushed the cycomachead/211-upgrade-css-compilation/1 branch from e1889f9 to 9388a3a Compare August 5, 2026 22:23
Follow-on to the dartsass-sprockets migration. Same Dart Sass compiler,
different plumbing: Propshaft digests and serves what is on disk and does no
transformation, so Dart Sass writes app/assets/builds/application.css and
Propshaft takes it from there.

  sprockets-rails      -> propshaft
  dartsass-sprockets   -> dartsass-rails

What goes away: app/assets/config/manifest.js, the config.assets.precompile
list, and five Sprockets-only environment settings (assets.debug, .digest,
.quiet, .compile, .css_compressor).

Four things needed handling:

1. Font Awesome webfonts. font-awesome-sass emits font-path() only when
   Sprockets' Sass helpers are loaded, and otherwise falls back to
   "#{$fa-font-path}/#{$file}" -- i.e. ../webfonts/..., which 404s. Set
   $fa-font-path to the gem's logical asset path and let Propshaft rewrite
   the url() to a digested one.

2. Gem Sass sources leaking into public/assets. Propshaft has a single load
   path and serves everything on it, and the bootstrap and font-awesome-sass
   gems put their Sass sources there so application.scss can @import them --
   110 .scss partials were being digested into public/assets. They cannot
   simply be excluded, because dartsass-rails derives its --load-path list
   from config.assets.paths and Propshaft's excluded_paths removes entries
   from that same array. Pass the two directories to Dart Sass explicitly and
   then take them off Propshaft's path.

3. The CSS is a build artifact now. A checkout without it raises
   Propshaft::MissingAssetError rather than rendering unstyled, so bin/setup
   builds it and the three CI workflows that run rspec/cucumber directly get
   a Build CSS step. Procfile.dev gains a css process, so `make dev` keeps it
   fresh.

4. Dev output. dartsass-rails defaults to --style=compressed --no-source-map
   in every environment, where sassc-rails only compressed outside
   development. Build expanded with source maps in development.

Verified: rspec 584/0; the a11y suite 40/0 with all 40 screenshots
pixel-compared against the Sprockets build (20 byte-identical, the rest
differing only where generated timestamps change column widths); cucumber
unchanged at the same 5 pre-existing failures. Production and development
boot with CSS, webfonts and all 27 local importmap assets returning 200. The
compiled CSS is token-for-token identical to the Sprockets output apart from
minifier cosmetics (nested calc(), leading zeros).

Co-Authored-By: Claude <noreply@anthropic.com>
@superconductor-for-github
superconductor-for-github Bot force-pushed the cycomachead/211-propshaft-spike branch from 108729a to 85b5404 Compare August 5, 2026 22:28
@cycomachead
cycomachead marked this pull request as ready for review August 6, 2026 02:16
@cycomachead
cycomachead merged commit 9c923ec into cycomachead/211-upgrade-css-compilation/1 Aug 6, 2026
14 checks passed
@cycomachead
cycomachead deleted the cycomachead/211-propshaft-spike branch August 6, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants