Skip to content

Commit cea7280

Browse files
committed
Make FileUpdateChecker faster with many extensions
Projects using propshaft/importmap-rails can end up constructing FileUpdateCheckers with globs looking like: "{/Users/jon/Developer/rails-importmaps/app/assets/images/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/app/assets/stylesheets/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/stimulus-rails-1.3.4/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/turbo-rails-2.0.11/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/actiontext-8.0.0/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/actiontext-8.0.0/app/assets/stylesheets/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/actioncable-8.0.0/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/activestorage-8.0.0/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/.devenv/bundle/ruby/3.3.0/gems/actionview-8.0.0/app/assets/javascripts/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/app/javascript/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}, /Users/jon/Developer/rails-importmaps/vendor/javascript/**/*.{html,xhtml,text,txt,js,css,ics,csv,vcf,vtt,png,jpeg,jpg,jpe,pjpeg,gif,bmp,tiff,tif,svg,webp,mpeg,mpg,mpe,mp3,mp1,mp2,ogg,oga,spx,opus,m4a,mpg4,aac,webm,mp4,m4v,otf,ttf,woff,woff2,xml,rss,atom,yaml,yml,multipart_form,url_encoded_form,json,pdf,zip,gzip,gz,turbo_stream}}" Generally speaking, globs with nested braces seem to perform very poorly. If the glob looks like `app/assets/stylesheets/**/*.{css,scss,js}`, you just get one system call for each of app, app/assets, app/assets/stylesheets. This is the best-case scenario. If the glob looks like `{app/assets/stylesheets/**/*.{css,scss,js}}` - which ought to be identical - you get three system calls for each of app, app/assets, app/assets/stylesheets. If you add more extensions to the inner glob, you multiply the number of system calls for every directory in the path. Fortunately, we can avoid this performance drop by just passing the directories as separate arguments to `Dir[]` - ie change `Dir["{foo/**/*.{css,js},bar/**/*.{css,js}}"]` into `Dir["foo/**/*.{css,js}", "bar/**/*.{css,js}"]` On a fresh importmap-rails project on macos 15.1 with ruby 3.3.5, `Rails.application.assets.load_path.cache_sweeper.execute_if_updated` takes around 15ms. After this optimization, it's around 0.5ms.
1 parent a8709e6 commit cea7280

File tree

2 files changed

+7
-4
lines changed

2 files changed

+7
-4
lines changed

activesupport/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@
2828

2929
*Taketo Takashima*
3030

31+
* Make `ActiveSupport::FileUpdateChecker` faster when checking many file-extensions.
32+
33+
*Jonathan del Strother*
34+
3135
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/activesupport/CHANGELOG.md) for previous changes.

activesupport/lib/active_support/file_update_checker.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def initialize(files, dirs = {}, &block)
4747
end
4848

4949
@files = files.freeze
50-
@glob = compile_glob(dirs)
50+
@globs = compile_glob(dirs)
5151
@block = block
5252

5353
@watched = nil
@@ -103,7 +103,7 @@ def execute_if_updated
103103
def watched
104104
@watched || begin
105105
all = @files.select { |f| File.exist?(f) }
106-
all.concat(Dir[@glob]) if @glob
106+
all.concat(Dir[*@globs]) if @globs
107107
all.tap(&:uniq!)
108108
end
109109
end
@@ -145,10 +145,9 @@ def compile_glob(hash)
145145
hash.freeze # Freeze so changes aren't accidentally pushed
146146
return if hash.empty?
147147

148-
globs = hash.map do |key, value|
148+
hash.map do |key, value|
149149
"#{escape(key)}/**/*#{compile_ext(value)}"
150150
end
151-
"{#{globs.join(",")}}"
152151
end
153152

154153
def escape(key)

0 commit comments

Comments
 (0)