Rails assets paths make site load super slow (Propshaft)

Hi,

In my attempt to try to migrate away from SCSS, I ahve some files I need to import to my CSS from node_modules. I have added their paths to assets.rb like so:

Add additional assets to the asset load path.

Rails.application.config.assets.paths << Rails.root.join("app", "assets", "svg")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "reset-css", "reset-css")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "bourbon")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "@shoelace-style", "shoelace", "dist", "themes")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe", "src", "css", "default-skin")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "css")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "animate.css")

and then referecning them from .css like:

@layer reset, imports, defaults, components;

@import url("/reset.css") layer(reset);

@import url("/light.css") layer(imports);

@import url("/flag-icons.css") layer(imports);

@import url("/animate.css") layer(imports);

But doing this makes the app super super slow to load. I see a white page and it just keeps loading. It does get there but it’s near impossible to develop with.

Ideally I think i’d prefer to have one like like Rails.application.config.assets.paths << Rails.root.join("node_modules") in my assets file and then the rest of the path in the .css file but i’m unsure of the right way or if this will make things worse?

Any ideas how to avoid this? or fix it?

Anyone able to help me here?

Do you see a super large CSS file or anything that indicate the import of CSS is the bottleneck?

Not that I can see.

Question: Should I be adding just node_modules to the asset paths or just lots of highly specialised paths?

Adding the entire node_modules will make it worse I think. Is it just the first request that is slow, or all of them? On first request propshaft is normally slower, because it’s reading all files from path and generating the digests for them. But subsequent requests should be fast as those are already cached (unless you changed any of the css files, in which case it reloads everything again)

Is it still slow if you run rails assets:precompile before? I know that’s not what you want, I just want to see if by eliminating the read and cache steps the problem is solved.

Hi @brenogazzola .

Thanks for replying. Sorry for the delay, just been trying to get answers for you.

Ok, So Every reload seems to take approximately 20 seconds for the page load to complete. That’s with the current assets.rb file (first load and subsequent reloads (without changing files between)).

Running rails assets:precompile also makes no difference, still a 20 second page load time.

Here’s some code I have:

In my layout file:

        <%= stylesheet_link_tag "application-styles", "data-turbo-track": "reload" %>
        <%= stylesheet_link_tag "style2", "data-turbo-track": "reload" %>

assets.rb:

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.

Rails.application.config.assets.version = "1.0"

# Add additional assets to the asset load path.

Rails.application.config.assets.paths << Rails.root.join("app", "assets", "svg")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "reset-css")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "bourbon")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "@shoelace-style", "shoelace", "dist", "themes")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe", "dist")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe-dynamic-caption-plugin")

# Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "css")

# Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "flags")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "animate.css")

The start of my style2.css file:

@layer reset, imports, defaults, components;

@import url("/reset.css") layer(reset);

@import url("/light.css") layer(imports);

/* @import url("/css/flag-icons.css") layer(imports); */

@import url("/animate.css") layer(imports);

@import url("photoswipe.css") layer(imports);

@import url("photoswipe-dynamic-caption-plugin.css") layer(imports);

@import url("https://unpkg.com/open-props") layer(imports);

@import url("/site-header.css") layer(components);

@import url("/site-footer.css") layer(components);

@import url("/attraction.css") layer(components);

@import url("/venue.css") layer(components);

@import url("/contact-options.css") layer(components);

@import url("/gallery-intro.css") layer(components);

@import url("/linkable-tags.css") layer(components);

Also, application-styles is a compiled SCSS sheet, whereas style2 is a pure CSS file. I am using this setup to convert my styles away from SCSS.

Thanks, I look forward to hearing from you!

That’s weird. After running rails asset:precompile you are switching Propshaft to using a manifest file and it has no need to search, read and digest every single file or reload anything.

Just to be sure, after you precompiled, did you check everything was in the public/assets folder? And then you ran rails s instead of bin/dev and loaded the page?

If you did, here’s a coupe of things to try. We will need to try to remove Propshaft step by step until we find the cause:

  1. Remove both link_tag and checking if application is still slow. Is it still slow?
  2. With the link_tags removed, remove all code from assets.rb file. Stop and start Puma. Is it still slow?

Hi @brenogazzola ,

Ah, I did run it after precompiling with bin/dev. I have just tested it this morning by precompiling and running the app with rails s and it’s still super slow.

I can confirm that when i precompile I see all the digested assets in public/assets.

Ok, so removing the two stylesheet_link_tag’s, the app is still slow.

I then removed all code from assets.rb and restarted the server and the app is refreshing at normal and expected speeds.

(This is all with precompiled assets btw)

Deffo looks like the slowness occurs when I have code in assets.rb

Any suggestions? It’s very strange.

Thanks, Neil

@brenogazzola Any thoughts on this? Sorry to badger you, this issue is causing me to delay major work to my app. Really would love to resolve it!

Appreciate the guidance and support.

Could you add this to your development.rb please? It will switch from Rails normal file checker, that needs to recheck every file on each request, to one that waits for OS notifications. You will need to have the listen gem in you Gemfile.

config.file_watcher = ActiveSupport::EventedFileUpdateChecker

Hi @brenogazzola Yes, that’s back to being rapid again.

Any idea why this is now needed when it was not before?

Thanks!

Sprockets used the EventedFileUpdateChecker file checker by default, I think.

In Propshaft, when you have only a few files, the overhead of checking them one by one is not that much on an SSD. I think that when you started adding node_modules folders, the file count raised enough that the overhead became noticeable.

I would hazard a guess that if you are using import maps exclusively, and just consuming JS packages and associated stylesheets that are completely external to your app, then there’s not going to be much difference between scanning everything and scanning only what changed. But previously, with fingerprinting and transpiling and so forth happening in Sprockets, there were so many changes between page loads that they had to cut back to only changed files. I can see the argument that listening to filesystem events is a “hack”, but it’s a useful hack when everything changes each turn of the wheel.

Walter

I was using Propshaft before. Not used Sprockets in ages. I switched to Propshaft pretty shortly after it was first realeased.

I had also been using node modules before too.

It’s just that i’ve added a few more.

My assets.rb file was:

Rails.application.config.assets.paths << Rails.root.join("app", "assets", "svg")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe", "src", "css", "default-skin")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "flags")

and is now:

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.

Rails.application.config.assets.version = "1.0"

# Add additional assets to the asset load path.

Rails.application.config.assets.paths << Rails.root.join("app", "assets", "svg")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "reset-css")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "bourbon")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "@shoelace-style", "shoelace", "dist", "themes")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe", "dist")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "photoswipe-dynamic-caption-plugin")

# Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "css")

# Rails.application.config.assets.paths << Rails.root.join("node_modules", "flag-icons", "flags")

Rails.application.config.assets.paths << Rails.root.join("node_modules", "animate.css")

I’m not using importmaps at all.

Just cssbundling and jsbundling and Propshaft.

Oh, sorry, I didn’t mean you, but rather the Rails core devs. They are quite adamant that we should be using lots of external files through import maps, rather than on-server bundling and compiling. So they might have removed the listen gem. If you were upgrading a project from sprockets to propshaft, there might have been (in other projects) something else that kept listen around, or just the fact that in this project you had so many possible targets to scan… I was sort of straw-manning an explanation for why they didn’t continue to watch filesystem changes, not suggesting that you weren’t using the new shininess.

Walter

1 Like

@walterdavis With Propshaft we are erring on the side of “do not add X unless it is absolutely necessary”, because we don’t want it to become another Sprockets. This also means that we want to keep the amount of external gems down.

So we made an assumption that, with importmaps as default, most projects would have few enough files and the pooling file checker would be enough. But we left the config option and a comment on the README about the OS file checker.

@rctneil It mostly depends on the amount of files inside all the paths, not just the folders themselves. So maybe those few folders might have hundreds (or maybe over a thousand) files.