-
Notifications
You must be signed in to change notification settings - Fork 113
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
Add uncapped preload headers for style, script assets #11504
base: main
Are you sure you want to change the base?
Conversation
changelog: Internal, Performance, Add preload headers for all style, script assets
header = response.headers['Link'] || '' | ||
header += ',' if header.present? | ||
header += "<#{url}>;rel=preload;as=#{as}" | ||
header += ';crossorigin' if crossorigin | ||
header += ";integrity=#{integrity}" if integrity |
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.
maybe a micro-optimization that isn't worth it, but +=
will create and throw away all these intermediate strings? What if we appended as we went? And add .dup
just incase the incoming Link
string is frozen
header = response.headers['Link'] || '' | |
header += ',' if header.present? | |
header += "<#{url}>;rel=preload;as=#{as}" | |
header += ';crossorigin' if crossorigin | |
header += ";integrity=#{integrity}" if integrity | |
header = response.headers['Link']&.dup || String.new | |
header << ',' if header.present? | |
header << "<#{url}>;rel=preload;as=#{as}" | |
header << ';crossorigin' if crossorigin | |
header << ";integrity=#{integrity}" if integrity |
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.
Micro-optimizations welcome! One of the advantages I was hoping for here is that there might be a small performance win, even just by skipping some of the options we have no need for (and those which are evaluated in the default implementation). One of the other things I was going to explore is if it's possible to switch the JavaScript rendering from javascript_include_tag
to tag.script
similar to the edits made in stylesheet_helper.rb
.
This idea seems good. I might even go a step further and say we don't need to duplicate the header value and could write directly to it? Since we're assigning it anyways at the end of this method.
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.
I might even go a step further and say we don't need to duplicate the header value and could write directly to it? Since we're assigning it anyways at the end of this method.
Something like...
(maybe gains offset by cost of hash lookup?)
def self.append(headers:, as:, url:, crossorigin: false, integrity: nil)
if headers['Link']
headers['Link'] << ','
else
headers['Link'] = ''
end
headers['Link'] << "<#{url}>;rel=preload;as=#{as}"
headers['Link'] << ';crossorigin' if crossorigin
headers['Link'] << ";integrity=#{integrity}" if integrity
end
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.
The dup
is just in case something else sets a frozen string
And with frozen_string_literal: true
at the top of the file, I think we need String.new
instead of ''
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.
Oh or we can use +''
instead of String.new
for a mutable string literal (via StackOverflow)
@@ -16,12 +16,19 @@ def render_javascript_pack_once_tags(...) | |||
concat javascript_assets_tag | |||
@scripts.each do |name, (url_params, attributes)| | |||
asset_sources.get_sources(name).each do |source| | |||
crossorigin = true if local_crossorigin_sources? |
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.
we should be able to directly assign this right? I'd go a step further an inline the entire thing, but that would make us have to wrap line 23 below
crossorigin = true if local_crossorigin_sources? | |
crossorigin = local_crossorigin_sources? |
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.
This branch was actually a few months old, and that change was one of the first things I tried editing when I resurrected it. But there's a significant difference between nil
and false
in how it's used with javascript_include_tag
, which is the reason for the true if
. I can't recall off the top of my head, but I'll make sure to add some test coverage for it when I write those.
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.
ah looks like the source code checks for "not nil" and "true"
maybe it would be easier if we updated local_crossorigin_sources?
to match that expected format?
# Fake boolean to match expected values for `:crossorigin` option in `javascript_include_tag`
# @see ActionView::Helpers::AssetTagHelper#javascript_include_tag
# @return [true, nil]
def crossorigin
true if Rails.env.development? && ENV['WEBPACK_PORT'].present?
end
crossorigin = true if local_crossorigin_sources? | ||
integrity = asset_sources.get_integrity(source) | ||
|
||
if attributes[:preload_links_header] != false |
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.
this means that preload_links_header: nil
will add the preload header, that seems counterintuitive to me?
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.
🛠 Summary of changes
Updates asset helpers to bypass internal
stylesheet_link_tag
andjavascript_include_tag
logic for handling preload response headers, appending preload headers without caps. As of rails/rails#48405 (Rails 7.1), preload headers added by tag helpers are capped at 1kb, which is very limiting, especially given our approach of loading many, smaller assets targeted as sidecar assets for UI components.While the caps have created some incentives to be more intentional with preloading (e.g. #10612), we've already addressed the lowest hanging fruit, and a number of critical scripts are currently not being preloaded (e.g. reCAPTCHA behaviors on the sign-in screen).
References:
📜 Testing Plan
Verify that preload headers are included for all scripts and assets, including
integrity
orcrossorigin
directives as appropriate. Compare against https://secure.login.gov