Styling — Dark Theme

Website Rebuild with Ruby on Rails

Chapter 5 · Styling — Dark Theme

Three siblings, three positions on asset bundling: Next.js bundles by mandatory framework design, Django ships nothing at all, Laravel pre-wires an optional bundler (Vite) without being one itself. Rails 7+ reaches a fourth position that isn't just another point on the same spectrum — it's a deliberate attempt to avoid needing a JavaScript bundler in the first place.

Propshaft: Fingerprinting, Not Bundling

Rails 7.1+ defaults to Propshaft as its asset pipeline — a genuinely minimal one. Propshaft doesn't compile Sass, doesn't transpile anything, and doesn't bundle multiple files into one. It does exactly one job: it fingerprints static asset files with a content-based digest for cache-busting, and serves them. Any real compilation this site's own assets might need is delegated to separate, opt-in tools — not baked into the default pipeline the way Next.js's bundler is baked into that framework.

<!-- app/views/layouts/application.html.erb --> <%= stylesheet_link_tag "application" %>

app/assets/stylesheets/application.css holds this site's own dark theme — plain CSS, no preprocessing needed for it, so Propshaft's own "just fingerprint and serve" behavior is already the entire pipeline this chapter requires.

Importmap: No Bundler for JavaScript, Deliberately

# config/importmap.rb pin "application", preload: true
<!-- app/views/layouts/application.html.erb --> <%= javascript_importmap_tags %>

javascript_importmap_tags renders a browser-native <script type="importmap"> block, mapping bare module names to real file URLs — resolved directly by the browser's own native ES module support, with no bundling step running anywhere, in development or production. app/javascript/application.js holds this site's own copy-to-clipboard script, the same small enhancement used throughout every other course on the site, needing nothing more than what the browser already provides.

A philosophy, not just a fourth flavor
Next.js's bundler is mandatory — there's no path around it. Laravel's Vite is optional in principle but pre-wired and expected. Django offers no pipeline to opt into at all. Rails 7's own Importmap default is a deliberate design position: for an application that doesn't need a large client-side JavaScript framework, the browser's own native module resolution is treated as sufficient, and reaching for Node/npm/a bundler is an opt-in escalation (via jsbundling-rails) rather than a default assumption. That's a genuinely different starting philosophy, not a fourth data point on the same spectrum.

Four Positions on Asset Bundling

Next.jsDjangoLaravelRails
Default JS pipelineA bundler, mandatoryNoneVite, pre-wired but a real bundlerImportmap — no bundler at all
Node/npm required?AlwaysNever (nothing built in)Yes, for npm run buildNot for the default case
CSS handlingBundled with the buildServed as-is via collectstaticBundled via ViteFingerprinted and served by Propshaft, no processing
Forward reference
Since nothing here depends on Node or a build step, Chapter 11's own deployment story stays simple on this front too — bin/rails assets:precompile runs Propshaft's own fingerprinting during deploy, with no separate JavaScript build pipeline to coordinate.

Hands-On Exercises

Exercise 1

Explain what Propshaft actually does — and, just as importantly, what it deliberately doesn't do — compared to a real bundler like the one built into Next.js.

📄 View solution
Exercise 2

Explain how Importmap resolves a JavaScript module without a bundler, and why this is described in this chapter as a philosophical position rather than just a fourth entry on the same spectrum as Next.js, Django, and Laravel.

📄 View solution
Exercise 3

Explain why this course's own dark theme CSS needs nothing more than Propshaft's default fingerprint-and-serve behavior, with no preprocessing step involved.

📄 View solution

Chapter 5 Quick Reference

  • Propshaft — Rails 7.1+'s default asset pipeline: fingerprint and serve, no compilation or bundling
  • stylesheet_link_tag "application" — renders the fingerprinted CSS link tag
  • Importmap — browser-native ES module resolution, no bundler required for the default case
  • javascript_importmap_tags — renders the native <script type="importmap"> block
  • A genuine fourth philosophy — not "bundle by default," "nothing built in," or "bundler pre-wired," but "avoid needing a bundler at all"
  • Next chapter: The Database: ActiveRecord ORM