Skip to content

Migrate from sassc to modern CSS compilation - #527

Open
cycomachead wants to merge 4 commits into
mainfrom
cycomachead/211-upgrade-css-compilation/1
Open

Migrate from sassc to modern CSS compilation#527
cycomachead wants to merge 4 commits into
mainfrom
cycomachead/211-upgrade-css-compilation/1

Conversation

@cycomachead

Copy link
Copy Markdown
Contributor

General Info

Changes

Replaces the deprecated sassc-rails (LibSass) gem with dartsass-sprockets, resolving the Gemfile TODO. Dart Sass is the only maintained Sass implementation; everything else about the pipeline (Sprockets, stylesheet_link_tag, manifest.js, gem-provided SCSS) is unchanged.

Key changes:

  • Gemfile — swap sassc-rails for dartsass-sprockets ~> 3.2. (sassc itself remains as a transitive dependency of font-awesome-sass and sassc-embedded, but no longer compiles anything.)
  • config/initializers/dartsass.rb (new) — silences Bootstrap 5.3's @import deprecation warnings (quiet_deps for gem stylesheets, silence_deprecations: ['import'] for our own). Can be removed when Bootstrap 6 ships.
  • application.scss — adds @use "sass:color" and updates mix() calls to color.mix() (the modern Sass module API). Adds %%25 to $escaped-characters so Dart Sass's fractional color serialization doesn't drop bare % into SVG data URIs.
  • DataTables stylesheets — renamed from .css to _*.scss partials and re-imported without the .css extension. Previously, @import "….css" was passed through as a plain CSS @import url(...) rather than being inlined — meaning both DataTables stylesheets 404'd in production since they were never precompiled. Inlining fixes this. The unquote("rgb(var(--x))") LibSass workarounds are also removed, since Dart Sass handles rgb(var(…)) natively.

Testing

  • rspec: 584 examples, 0 failures
  • rspec --tag a11y: 40 examples, 0 failures (all pages × light/dark × role, axe-audited)
  • Pixel-diffed all 40 a11y screenshots before vs. after: 19 byte-identical; all remaining diffs are test-data noise (timestamps, not styling)
  • cucumber: same 5 pre-existing failures, no new failures
  • Production boot verified: single application.css with DataTables rules inlined, no stray @import remaining

Visual Changes

No styling changes — only test-data noise (timestamps) differs between old and new screenshots.

Documentation

No documentation changes required.

Checklist

  • Name of branch corresponds to story

Superconductor Ticket Implementation | App Preview | Guided Review

cycomachead and others added 2 commits August 5, 2026 22:12
- Replace deprecated `sassc-rails` (LibSass) with `dartsass-sprockets`.
- Update `application.scss` to use modern Sass module syntax (`@use "sass:color"`).
- Rename DataTables CSS files to SCSS partials to ensure they are inlined during compilation, fixing a production 404 bug.
- Add `config/initializers/dartsass.rb` to silence Bootstrap `@import` deprecation warnings.
- Fix SVG data URI encoding by adding `%` to `$escaped-characters` to handle Dart Sass color serialization.
- Remove LibSass-specific `unquote()` workarounds for CSS variables.

Co-authored-by: Claude Code <noreply@anthropic.com>
The app carried hand-maintained copies of dataTables.bootstrap5.css and
responsive.bootstrap5.css under app/assets/stylesheets. They had already
drifted from the JavaScript pinned in config/importmap.rb, and one of them
carried a libsass workaround (unquote() around rgb(var(--x))) that Dart Sass
does not need.

Delete both and @import the stylesheets straight out of node_modules instead,
with the two package directories added to the Sass load path. The npm versions
are pinned exactly to the versions pinned in config/importmap.rb -- 2.3.1 and
3.0.4 -- so the CSS and the JavaScript stay in lockstep and both move in a
single, reviewable bump.

This makes node_modules a prerequisite for compiling CSS, so:

  - the Docker build stage installs nodejs/npm and runs npm ci --omit=dev.
    It runs after COPY . . so that npm ci (which wipes node_modules first)
    also clears any node_modules that rode along in the build context; the
    runtime image still carries no Node.
  - the rspec, cucumber and a11y workflows set up Node and npm ci
  - bin/setup runs npm install

Realigning to 2.3.1 changes the compiled CSS by 14 tokens: DataTables added
.dt-left/.dt-justify header alignment rules and tightened the sort-arrow
gutter from 20px to .25rem.

Verified against origin/main with the same app code: rspec 619/0, the a11y
suite 40/0 with all 40 screenshots pixel-compared (17 byte-identical, the rest
differing only in generated timestamps and the slightly narrower sort-arrow
gutter), cucumber unchanged at the same 7 pre-existing failures. Production
precompiles clean, boots, and serves one stylesheet with DataTables inlined
and no stray @import.

Co-Authored-By: Claude <noreply@anthropic.com>
@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

Copy link
Copy Markdown

Rebased onto main (was conflicting after 149c632) and added a second commit.

Conflict resolution. 149c632 fixed the same DataTables 404 by moving both stylesheets to public/ and linking them with plain <link> tags, noting "Keep these as plain <link> tags until the app moves off sassc-rails." This branch is that move: Dart Sass passes rgb(var(--x)) through untouched, so the files inline cleanly and go back to being digested and cache-busted. public/datatables/ and the two <link> tags are gone.

Second commit — load DataTables CSS from npm. Rather than keep vendored copies at all, application.scss now imports the stylesheets straight out of node_modules, with the packages pinned to the exact versions pinned in config/importmap.rb (2.3.1 / 3.0.4). The CSS and the JavaScript can no longer drift, and the unquote() libsass workaround is gone with the vendored file.

This makes node_modules a prerequisite for compiling CSS:

  • the Docker build stage installs nodejs/npm and runs npm ci --omit=dev (after COPY . ., so npm ci also wipes any node_modules that rode along in the build context). The runtime image still carries no Node.
  • the rspec, cucumber and a11y workflows set up Node and run npm ci
  • bin/setup runs npm install

Realigning 2.2.x → 2.3.1 changes the compiled CSS by 14 tokens: DataTables added .dt-left/.dt-justify header alignment rules and tightened the sort-arrow gutter from 20px to .25rem.

Verified against origin/main with the same application code: rspec 619/0 · a11y 40/0, with all 40 screenshots pixel-compared (17 byte-identical, the rest differing only in generated timestamps and the slightly narrower sort-arrow gutter) · cucumber unchanged at the same 7 pre-existing failures. Production precompiles clean, boots, and serves one stylesheet with DataTables inlined and no stray @import.

Not verified: the Docker image build — no Docker available in this environment. The Dockerfile change is the one thing worth a careful look.

…spike

Migrate the asset pipeline from Sprockets to Propshaft
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