Why am I having to recompile assets?

I’m guessing I’m making a blunder somewhere.

So I started a new project for Rails 7 with -c bootstrap (but no other command line option).

In the auto-generated application.bootstrap.scss with the following default content:

@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';

, I try to add at the bottom a custom css style, for eg.

div {
  border: 1px solid black;
}

However, refreshing the view or even restarting the server has no effect on the webpage.

If I kill the server and run rails assets:precompile then restart the server, the changes I made do show up. But I don’t think it’s supposed to be like that, is it? Note that the server is running with the test environment.

Any clue what I could be doing wrong?

After assets have been precompiled, they will always need to be precompiled before changes appear. To return to dynamic assets run the command below:

rails assets:clobber
2 Likes

Appreciate the reply… in addition to what you said, it seems I have to run the server using ./bin/dev (as I only found out after some googling). Would you happen to know what the right way to import jquery would be?

1 Like

That depends on if you are using import maps or a bundler like esbuild

Import maps:

Esbuild:

The bin/dev command is using the foreman gem to run concurrent commands, you can see which in your Procfile.dev file in the root of your project what those commands are (and you can look at the bin/dev file itself to see the foreman command ran to make this work). If you haven’t worked with javascript package managers, modules or bundlers before then you’ll want to spend some time digging more into that since it’s a different paradigm than to how rails dealt with javascript before. No more global accessibility to javascript objects unless specifically configured to do so, meaning no global accessibility to $ for jQuery without more configurations.

appreciate the links! I will check them out in a bit. Edit: I did ran the ack command on my project directory and lots of references to esbuild turned up (particularly one in a file called package.json) so I guess that’s what I’m using.

Actually I’m new to the whole web development thing altogether… working on a student project (with a looming deadline). The problem with finding beginner friendly Rails resources seems to be a lot of refer back to older ways of doing things (generally speaking, but also the asset pipeline in particular) and a lot of them lead me down rabbit holes that I often can’t tell are worth burrowing into or not.

(This isn’t a complaint… just the situation I’m finding myself in.)

When I started a new project with rails new project_name -c bootstrap does that use esbuild by default? In general, how can I tell?

I know you said “no more global accessibility etc.”, but what are the consequences of importing the jquery (or any other javascript/css library) in the application layout as above? I realise that every view using these would have to be rendered inside application layout (which happens by default, but can be changed) but are there any other consequences? I’m guessing not being able to override styles (for css) easily might be one.

I apologise if my thoughts are scattered all over the place but that’s basically how I’m feeling right now lol.

Rails is a very mature framework that’s been around for a long time, which is both great from a stability and feature rich perspective, but also potentially frustrating from a learning perspective because there may be a lot of outdated information. Last time I looked at the rails guides for app javascript best practices it was fairly lacking, mostly because it’s not entirely it’s responsibility to go in-depth about JS development.

If you are just using this for a project and don’t need the complexity of a JS bundler, you can bring back UJS which is how things were done by default before. You can then take out turbo, stimulus, and sass to really bring down the complexity and not even need to us esbuild or a sass compiler. Then the guides will make more sense. You can still use bootstrap in this case too, you’ll just be importing it differently (similar to how the link mentioned bringing in jQuery with a cdn).

Global namespace pollution is not inherently bad, similar to a lot of coding paradigms it’s more meant for the longevity and maintainability of an application. For learnings sake it’s still a good idea to practice the proper methods so we don’t get into bad habits, but brevity is also important when learning other concepts. Putting it in the global namespace can cause issues because in larger projects it can be altered or removed, or something done to it in some obscure file somewhere else, by someone else, and cause subtle/malicious bugs that can become very hard to find, or cause deeper issues that the app then begins to depend on but progressively becomes harder to maintain.

As for how to tell it’s using esbuild, you can look in the package.json file (which is what yarn uses to manage the javascript packages used in the app) and see what it’s installed, and the app specific commands that are set up (under the scripts key). When running rails new you would have also seen in the output what it was installing and esbuild would have been mentioned a few times there.

“The problem with finding beginner friendly Rails resources seems to be a lot of refer back to older ways of doing things (generally speaking, but also the asset pipeline in particular) and a lot of them lead me down rabbit holes that I often can’t tell are worth burrowing into or not.”

This is understandable. The differences between erb templating and newer javascript build processes can be confusing as hell if you’re totally new to rails and web dev in general.

The main rails app that I develop started in rails 4.2, then 5.0, and now 7.0.4 so I avoided all of the javascript build stuff.

It’s old school erbs with jQuery, bootstrap, and a few other thingss and it works well, but there are some things to keep in mind.

If I were you I’d first go way back to basic web pages. In your layouts/application.erb file just link the css and javascripts manually and - this is important - add a check for whether you’re on dev, test, or prod. For dev/test you would link the files just like any other basic web page. For prod you would use "the rails way"of linking so the assets would need to be precompiled.

You’ll be able to tell because the link on dev would just be the normal or while the rails precompiled assets will be " (or whatever the latest compiled asset name is).

I absolutely loathe precompiling css and javascript when developing - I want to make a change and reload the page and see the changes immediately.

You could also just add a switch like “use_precompiled_assets = true/false” set somewhere (ENV variable or the environment files). That way you can switch between the precompiled and non just by switching the setting without having to restart the server.

There have been countless times where I’ve forgotten to precompile and I don’t see changes and it drove me nuts, that’s why I added the switch.

One other thing - the “out of the box” compiler for javascript (can’t remember the name) doesn’t play well with es 6 javascript (at least the one I was using in rails 4 and 5) but there’s a drop in replacement that work exactly the same.

One last thing, you definitely want to have precompiled assets in production.

The amount of time to load javascript and css the old school way vs precompiled can be more than expected (100s of ms). Also the size of those scripts are shrunk way down when precompiled. Basically it’s all due to caching, and you’ll need to set the “serve static assets” if you’re on Puma. For nginx it’s set differently.

Rails is awesome, but I’d start old school and avoid the esbuild/forman/whatever’s the newest latest and greatest thing until you know how it works.

I don’t even know how the yarn esbuild etc. stuff works, and I don’t want to - old school is the way to go : )

Good luck.

1 Like

At the bottom is the output from running your command. One of the confusing things is that bootstrap installs esbuild. But that doesn’t show up very clearly. And other magic happens which isn’t noted clearly.

Look at /Gemfile

# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"

# Bundle and process CSS [https://github.com/rails/cssbundling-rails]
gem "cssbundling-rails"

Something in Rails new with bootstrap adds jsbundling-rails with the bootstrap option.

You don’t say where you added the css. I think it should be in the app/assets/stylesheets/application.bootstrap.scss file.

And running the server with ./bin/dev should set things up to recompile. Not rails server

create app/assets/stylesheets/application.css later gets changed to `app/assets/stylesheets/application.bootstrap.scss’ although that isn’t noted in the command line output. Two changes in the file name.

Rails has hired people to work on documentation. But now so many options it’s hard to keep up.

Good luck.

➜ rails new quicktestdefault -c bootstrap
Ignoring rbs-2.8.2 because its extensions are not built. Try: gem pristine rbs --version 2.8.2
Using  from /Users/me/.railsrc
      create
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  .gitattributes
      create  Gemfile
         run  git init from "."
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>
Initialized empty Git repository in /Users/me/Documents/Ruby/RailsTrials/quicktestdefault/.git/
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images
      create  app/assets/images/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/cable.yml
      create  config/puma.rb
      create  config/storage.yml
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/assets.rb
      create  config/initializers/content_security_policy.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/new_framework_defaults_7_0.rb
      create  config/initializers/permissions_policy.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/master.key
      append  .gitignore
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  tmp
      create  tmp/.keep
      create  tmp/pids
      create  tmp/pids/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor
      create  vendor/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/channels/application_cable/connection_test.rb
      create  test/test_helper.rb
      create  test/system
      create  test/system/.keep
      create  test/application_system_test_case.rb
      create  storage
      create  storage/.keep
      create  tmp/storage
      create  tmp/storage/.keep
      remove  config/initializers/cors.rb
      remove  config/initializers/new_framework_defaults_7_0.rb
         run  bundle install
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...
Using rake 13.0.6
Fetching concurrent-ruby 1.2.2
Fetching minitest 5.18.0
Using builder 3.2.4
Fetching rack 2.2.6.3
Using racc 1.6.2
Using crass 1.0.6
Using mini_mime 1.1.2
Using erubi 1.12.0
Using marcel 1.0.2
Using date 3.3.3
Using websocket-extensions 0.1.5
Using public_suffix 5.0.1
Using bundler 2.4.3
Using bindex 0.8.1
Fetching regexp_parser 2.7.0
Using matrix 0.4.2
Fetching msgpack 1.6.1
Fetching timeout 0.3.2
Using nio4r 2.5.8
Using thor 1.2.1
Fetching zeitwerk 2.6.7
Using method_source 1.0.0
Using io-console 0.6.0
Using rexml 3.2.5
Using rubyzip 2.3.2
Using websocket 1.2.9
Fetching sqlite3 1.6.1 (arm64-darwin)
Installing timeout 0.3.2
Installing zeitwerk 2.6.7
Installing regexp_parser 2.7.0
Installing msgpack 1.6.1 with native extensions
Installing minitest 5.18.0
Fetching nokogiri 1.14.2 (arm64-darwin)
Installing rack 2.2.6.3
Using websocket-driver 0.7.5
Using addressable 2.8.1
Using puma 5.6.5
Using reline 0.3.2
Fetching selenium-webdriver 4.8.1
Installing concurrent-ruby 1.2.2
Using net-protocol 0.2.1
Fetching irb 1.6.3
Using net-imap 0.3.4
Using net-pop 0.1.2
Using net-smtp 0.3.3
Fetching mail 2.8.1
Using rack-test 2.0.2
Using i18n 1.12.0
Using tzinfo 2.0.6
Using sprockets 4.2.0
Using activesupport 7.0.4.2
Using globalid 1.1.0
Using activemodel 7.0.4.2
Using activejob 7.0.4.2
Using activerecord 7.0.4.2
Installing sqlite3 1.6.1 (arm64-darwin)
Installing irb 1.6.3
Installing mail 2.8.1
Using debug 1.7.1
Installing selenium-webdriver 4.8.1
Installing nokogiri 1.14.2 (arm64-darwin)
Using rails-dom-testing 2.0.3
Using loofah 2.19.1
Using xpath 3.2.0
Using webdrivers 5.2.0
Using rails-html-sanitizer 1.5.0
Using capybara 3.38.0
Using actionview 7.0.4.2
Using actionpack 7.0.4.2
Using jbuilder 2.11.5
Using actioncable 7.0.4.2
Using activestorage 7.0.4.2
Using actionmailer 7.0.4.2
Using actionmailbox 7.0.4.2
Using railties 7.0.4.2
Using actiontext 7.0.4.2
Using sprockets-rails 3.4.2
Fetching turbo-rails 1.4.0
Using jsbundling-rails 1.1.1
Using stimulus-rails 1.2.1
Using web-console 4.2.0
Using cssbundling-rails 1.1.2
Using rails 7.0.4.2
Installing turbo-rails 1.4.0
Using bootsnap 1.16.0
Bundle complete! 16 Gemfile dependencies, 73 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
         run  bundle binstubs bundler
       rails  javascript:install:esbuild
Compile into app/assets/builds
      create  app/assets/builds
      create  app/assets/builds/.keep
      append  app/assets/config/manifest.js
      append  .gitignore
      append  .gitignore
Add JavaScript include tag in application layout
      insert  app/views/layouts/application.html.erb
Create default entrypoint in app/javascript/application.js
      create  app/javascript
      create  app/javascript/application.js
Add default package.json
      create  package.json
Add default Procfile.dev
      create  Procfile.dev
Ensure foreman is installed
         run  gem install foreman from "."
Successfully installed foreman-0.87.2
1 gem installed
Add bin/dev to start foreman
      create  bin/dev
Install esbuild
         run  yarn add esbuild from "."
yarn add v1.22.19
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
└─ esbuild@0.17.11
info All dependencies
├─ @esbuild/darwin-arm64@0.17.11
└─ esbuild@0.17.11
✨  Done in 2.67s.
Add build script
         run  npm pkg set scripts.build="esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets" from "."
         run  yarn build from "."
yarn run v1.22.19
$ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets

  app/assets/builds/application.js      62b
  app/assets/builds/application.js.map  93b

✨  Done in 0.25s.
       rails  turbo:install stimulus:install
Import Turbo
      append  app/javascript/application.js
Install Turbo
         run  yarn add @hotwired/turbo-rails from "."
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 3 new dependencies.
info Direct dependencies
└─ @hotwired/turbo-rails@7.3.0
info All dependencies
├─ @hotwired/turbo-rails@7.3.0
├─ @hotwired/turbo@7.3.0
└─ @rails/actioncable@7.0.4
✨  Done in 1.56s.
Run turbo:install:redis to switch on Redis and use it in development for turbo streams
Create controllers directory
      create  app/javascript/controllers
      create  app/javascript/controllers/index.js
      create  app/javascript/controllers/application.js
      create  app/javascript/controllers/hello_controller.js
Import Stimulus controllers
      append  app/javascript/application.js
Install Stimulus
         run  yarn add @hotwired/stimulus from "."
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ @hotwired/stimulus@3.2.1
info All dependencies
└─ @hotwired/stimulus@3.2.1
✨  Done in 0.90s.
       rails  css:install:bootstrap
Build into app/assets/builds
       exist  app/assets/builds
   identical  app/assets/builds/.keep
File unchanged! The supplied flag value not found!  app/assets/config/manifest.js
Stop linking stylesheets automatically
        gsub  app/assets/config/manifest.js
File unchanged! The supplied flag value not found!  .gitignore
File unchanged! The supplied flag value not found!  .gitignore
Remove app/assets/stylesheets/application.css so build output can take over
      remove  app/assets/stylesheets/application.css
Add stylesheet link tag in application layout
File unchanged! The supplied flag value not found!  app/views/layouts/application.html.erb
      append  Procfile.dev
Add bin/dev to start foreman
   identical  bin/dev
Install Bootstrap with Bootstrap Icons and Popperjs/core
      create  app/assets/stylesheets/application.bootstrap.scss
         run  yarn add sass bootstrap bootstrap-icons @popperjs/core from "."
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 21 new dependencies.
info Direct dependencies
├─ @popperjs/core@2.11.6
├─ bootstrap-icons@1.10.3
├─ bootstrap@5.2.3
└─ sass@1.59.2
info All dependencies
├─ @popperjs/core@2.11.6
├─ anymatch@3.1.3
├─ binary-extensions@2.2.0
├─ bootstrap-icons@1.10.3
├─ bootstrap@5.2.3
├─ braces@3.0.2
├─ chokidar@3.5.3
├─ fill-range@7.0.1
├─ fsevents@2.3.2
├─ glob-parent@5.1.2
├─ immutable@4.3.0
├─ is-binary-path@2.1.0
├─ is-extglob@2.1.1
├─ is-glob@4.0.3
├─ is-number@7.0.0
├─ normalize-path@3.0.0
├─ picomatch@2.3.1
├─ readdirp@3.6.0
├─ sass@1.59.2
├─ source-map-js@1.0.2
└─ to-regex-range@5.0.1
✨  Done in 3.68s.
      insert  config/initializers/assets.rb
Appending Bootstrap JavaScript import to default entry point
      append  app/javascript/application.js
Add build:css script
         run  npm pkg set scripts.build:css="sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules" from "."
         run  yarn build:css from "."
yarn run v1.22.19
$ sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules
✨  Done in 1.74s.

Same issue here with bootstrap, rails 7. You can see in the css command wants to use:

app/assets/stylesheets/application.bootstrap.scss

yarn build:css --watch                                                         [24/01/1 | 9:49:03]
yarn run v1.22.21
$ yarn build:css:compile && yarn build:css:prefix --watch
$ sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules
$ postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css --watch

Then in application.bootstrap.scss I have:

@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
@import "components/pick";
@import "components/navbar";

If i change anything in a component, yarn does not pick it up.

However, if I change the yarn command to :slight_smile:

yarn watch:css

it all works.

Is this a difference using the cssbuilding-rails gem that differs from rails?