-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Override Propshaft digesting for shorter file names #9272
Conversation
lib/extensions/propshaft/asset.rb
Outdated
def digest | ||
@digest ||= Digest::SHA1.hexdigest("#{content}#{version}")[0...7] | ||
@digest ||= original_digest[0...7] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does the original method we're overriding also set the @digest
ivar? if so... this may not actually override it?
update: looks like yes it does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@digest ||= original_digest[0...7] | |
@shorter_digest ||= original_digest[0...7] # rubocop:disable Naming/MemoizedInstanceVariableName |
rubocop will yell because the ivar name doesn't match but we can override
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I thought I had tested this, but checking again, it does look like it reverted back to the original behavior with this change. Maybe it's best to just duplicate as it was in my earlier iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed/reverted in 72ac3b7.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but if it were up to me, I'd say we skip this?
My thinking:
- it's a tiny bit brittle due to monkeypatching
- Also we have no way to alert/check in the rare case there was ever a SHA collision
- For a the 3% decrease in asset size, maybe not be worth it?
One other aspect of this I hadn't noted previously is that this also brings some consistency to what we're already doing with JavaScript files, which are already digested with a fingerprint limited to just 8 characters. Let me hold and explore a few things, though I do kinda feel motivated to continue on this:
|
Created upstream pull request at rails/propshaft#155 |
Since this affects the file names for all compiled assets, this also has some benefit on the total size of HTML markup (image and stylesheet tags), as well the preload headers we include in responses. Some quick testing of before/after Markup size:
Before: 4598 bytes "Link" Header size:
Before: 1632 bytes |
changelog: Internal, Performance, Reduce size of bundled assets
This reverts commit e8fe2d8.
Also cleaner math on bit-size of hash
72ac3b7
to
47bab96
Compare
Updated to 8 characters in 47bab96 to be consistent with JavaScript digest (and coincidentally also component ID generation). I tested this in a review-app to get a sense of how this should behave in a production-like environment and it looks good there. Unless there's opposition, I'll plan to merge this in the morning. |
🛠 Summary of changes
This explores overriding Propshaft's default digesting logic in order to produce shorter file names. Since these files names are referenced in stylesheets (e.g.
url(/public/assets/...)
) and are not likely to be well-optimized by DEFLATE/LZ77/Huffman-based compression, it's theorized that they may be contributing to larger overall file size.The downside of this is it increases the likelihood of a hash collision, though it's quite unlikely, both that a hash collision would occur in the first place, and that two versions of a collided file hash would be cached in a user's browser such that conflicts could occur.
To my knowledge, Propshaft does not expose a way to customize this behavior, so the implementation here monkeypatches the original class method. The logic is largely the same, except the digest is shortened from its original result to take only the first 7 characters.
https://github.com/rails/propshaft/blob/18979c176132f432f70f8d7f7ff97ece035cba5d/lib/propshaft/asset.rb#L23-L25
Performance Impact:
This affects the file names for all compiled assets, notably impacting:
url(...)
file paths within CSS filesResults on
application.css
:Before: 27.6kb brotli'd, minified
After: 26.7kb brotli'd, minified
Diff: -0.9kb (-3.26%)
Markup size:
Before: 4598 bytes
After: 4155 bytes
Diff: -433 bytes (-9.6%)
"Link" Header size:
Before: 1632 bytes
After: 1467 bytes
Diff: -165 bytes (-10.1%)
📜 Testing Plan
Observe:
public/assets
have a fingerprint in file path that is only 7 characters in length (compare to longer onmain
)public/assets/application-*.css
), references to other files are accurate and use the shorter fingerprint