Module: Shakapacker::Helper
- Defined in:
- lib/shakapacker/helper.rb
Instance Method Summary collapse
- #append_javascript_pack_tag(*names, defer: true, async: false) ⇒ Object
- #append_stylesheet_pack_tag(*names) ⇒ Object
-
#asset_pack_path(name, **options) ⇒ Object
Computes the relative path for a given Shakapacker asset.
-
#asset_pack_url(name, **options) ⇒ Object
Computes the absolute path for a given Shakapacker asset.
-
#current_shakapacker_instance ⇒ Object
Returns the current Shakapacker instance.
-
#favicon_pack_tag(name, **options) ⇒ Object
Creates a link tag for a favicon that references the named pack file.
-
#image_pack_path(name, **options) ⇒ Object
Computes the relative path for a given Shakapacker image with the same automated processing as image_pack_tag.
-
#image_pack_tag(name, **options) ⇒ Object
Creates an image tag that references the named pack file.
-
#image_pack_url(name, **options) ⇒ Object
Computes the absolute path for a given Shakapacker image with the same automated processing as image_pack_tag.
-
#javascript_pack_tag(*names, defer: true, async: false, early_hints: nil, **options) ⇒ Object
Creates script tags that reference the js chunks from entrypoints when using split chunks API, as compiled by webpack per the entries list in package/environments/base.js.
-
#preload_pack_asset(name, **options) ⇒ Object
Creates a link tag, for preloading, that references a given Shakapacker asset.
- #prepend_javascript_pack_tag(*names, defer: true, async: false) ⇒ Object
-
#send_pack_early_hints(config) ⇒ Object
Sends HTTP 103 Early Hints for specified packs with fine-grained control over JavaScript and CSS handling.
-
#stylesheet_pack_tag(*names, early_hints: nil, **options) ⇒ Object
Creates link tags that reference the css chunks from entrypoints when using split chunks API, as compiled by webpack per the entries list in package/environments/base.js.
Instance Method Details
#append_javascript_pack_tag(*names, defer: true, async: false) ⇒ Object
343 344 345 346 347 |
# File 'lib/shakapacker/helper.rb', line 343 def append_javascript_pack_tag(*names, defer: true, async: false) update_javascript_pack_tag_queue(defer: defer, async: async) do |hash_key| javascript_pack_tag_queue[hash_key] |= names end end |
#append_stylesheet_pack_tag(*names) ⇒ Object
330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/shakapacker/helper.rb', line 330 def append_stylesheet_pack_tag(*names) if @stylesheet_pack_tag_loaded raise "You can only call append_stylesheet_pack_tag before stylesheet_pack_tag helper. " \ "Please refer to https://github.com/shakacode/shakapacker/blob/main/README.md#view-helper-append_javascript_pack_tag-prepend_javascript_pack_tag-and-append_stylesheet_pack_tag for the usage guide" end @stylesheet_pack_tag_queue ||= [] @stylesheet_pack_tag_queue.concat names # prevent rendering Array#to_s representation when used with <%= … %> syntax nil end |
#asset_pack_path(name, **options) ⇒ Object
Computes the relative path for a given Shakapacker asset. Returns the relative path using manifest.json and passes it to path_to_asset helper. This will use path_to_asset internally, so most of their behaviors will be the same.
Example:
<%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css"
16 17 18 |
# File 'lib/shakapacker/helper.rb', line 16 def asset_pack_path(name, **) path_to_asset(current_shakapacker_instance.manifest.lookup!(name), ) end |
#asset_pack_url(name, **options) ⇒ Object
Computes the absolute path for a given Shakapacker asset. Returns the absolute path using manifest.json and passes it to url_to_asset helper. This will use url_to_asset internally, so most of their behaviors will be the same.
Example:
<%= asset_pack_url 'calendar.css' %> # => "http://example.com/packs/calendar-1016838bab065ae1e122.css"
27 28 29 |
# File 'lib/shakapacker/helper.rb', line 27 def asset_pack_url(name, **) url_to_asset(current_shakapacker_instance.manifest.lookup!(name), ) end |
#current_shakapacker_instance ⇒ Object
Returns the current Shakapacker instance. Could be overridden to use multiple Shakapacker configurations within the same app (e.g. with engines).
5 6 7 |
# File 'lib/shakapacker/helper.rb', line 5 def current_shakapacker_instance Shakapacker.instance end |
#favicon_pack_tag(name, **options) ⇒ Object
Creates a link tag for a favicon that references the named pack file.
Example:
<%= favicon_pack_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' %>
<link href="/packs/mb-icon-k344a6d59eef8632c9d1.png" rel="apple-touch-icon" type="image/png" />
71 72 73 |
# File 'lib/shakapacker/helper.rb', line 71 def favicon_pack_tag(name, **) favicon_link_tag(resolve_path_to_image(name), ) end |
#image_pack_path(name, **options) ⇒ Object
Computes the relative path for a given Shakapacker image with the same automated processing as image_pack_tag. Returns the relative path using manifest.json and passes it to path_to_asset helper. This will use path_to_asset internally, so most of their behaviors will be the same.
34 35 36 |
# File 'lib/shakapacker/helper.rb', line 34 def image_pack_path(name, **) resolve_path_to_image(name, **) end |
#image_pack_tag(name, **options) ⇒ Object
Creates an image tag that references the named pack file.
Example:
<%= image_pack_tag 'application.png', size: '16x10', alt: 'Edit Entry' %>
<img alt='Edit Entry' src='/packs/application-k344a6d59eef8632c9d1.png' width='16' height='10' />
<%= image_pack_tag 'picture.png', srcset: { 'picture-2x.png' => '2x' } %>
<img srcset= "/packs/picture-2x-7cca48e6cae66ec07b8e.png 2x" src="/packs/picture-c38deda30895059837cf.png" >
55 56 57 58 59 60 61 62 63 |
# File 'lib/shakapacker/helper.rb', line 55 def image_pack_tag(name, **) if [:srcset] && ![:srcset].is_a?(String) [:srcset] = [:srcset].map do |src_name, size| "#{resolve_path_to_image(src_name)} #{size}" end.join(", ") end image_tag(resolve_path_to_image(name), ) end |
#image_pack_url(name, **options) ⇒ Object
Computes the absolute path for a given Shakapacker image with the same automated processing as image_pack_tag. Returns the relative path using manifest.json and passes it to path_to_asset helper. This will use path_to_asset internally, so most of their behaviors will be the same.
42 43 44 |
# File 'lib/shakapacker/helper.rb', line 42 def image_pack_url(name, **) resolve_path_to_image(name, **.merge(protocol: :request)) end |
#javascript_pack_tag(*names, defer: true, async: false, early_hints: nil, **options) ⇒ Object
Creates script tags that reference the js chunks from entrypoints when using split chunks API, as compiled by webpack per the entries list in package/environments/base.js. By default, this list is auto-generated to match everything in app/javascript/entrypoints/*.js and all the dependent chunks. In production mode, the digested reference is automatically looked up. See: webpack.js.org/plugins/split-chunks-plugin/
Example:
<%= javascript_pack_tag 'calendar', 'map', 'data-turbolinks-track': 'reload' %> # =>
<script src="/packs/vendor-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload" defer="true"></script>
<script src="/packs/calendar~runtime-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload" defer="true"></script>
<script src="/packs/calendar-1016838bab065ae1e314.chunk.js" data-turbolinks-track="reload" defer="true"></script>
<script src="/packs/map~runtime-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload" defer="true"></script>
<script src="/packs/map-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload" defer="true"></script>
DO:
<%= javascript_pack_tag 'calendar', 'map' %>
DON’T:
<%= javascript_pack_tag 'calendar' %>
<%= javascript_pack_tag 'map' %>
Early Hints:
By default, HTTP 103 Early Hints are sent automatically when this helper is called,
allowing browsers to preload JavaScript assets in parallel with Rails rendering.
<%= javascript_pack_tag 'application' %>
# Automatically sends early hints for 'application' pack
# Customize handling per pack:
<%= javascript_pack_tag 'application', 'vendor',
early_hints: { 'application' => 'preload', 'vendor' => 'prefetch' } %>
# Disable early hints:
<%= javascript_pack_tag 'application', early_hints: false %>
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/shakapacker/helper.rb', line 112 def javascript_pack_tag(*names, defer: true, async: false, early_hints: nil, **) if @javascript_pack_tag_loaded raise "To prevent duplicated chunks on the page, you should call javascript_pack_tag only once on the page. " \ "Please refer to https://github.com/shakacode/shakapacker/blob/main/README.md#view-helpers-javascript_pack_tag-and-stylesheet_pack_tag for the usage guide" end # Collect all packs (queue + direct args) append_javascript_pack_tag(*names, defer: defer, async: async) all_packs = javascript_pack_tag_queue.values.flatten.uniq # Resolve effective early hints value (nil = use config default) effective_hints = resolve_early_hints_value(early_hints, :javascript) # Send early hints automatically if enabled if early_hints_enabled? && effective_hints && effective_hints != "none" hints_config = normalize_pack_hints(all_packs, effective_hints) send_javascript_early_hints_internal(hints_config) # Flush accumulated hints (sends the single 103 response) flush_early_hints elsif early_hints_debug_enabled? store = early_hints_store store[:debug_buffer] ||= [] store[:debug_buffer] << "<!-- Shakapacker Early Hints (JS): SKIPPED (early_hints: #{effective_hints.inspect}) -->" end sync = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:sync], type: :javascript) async = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:async], type: :javascript) - sync deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:deferred], type: :javascript) - sync - async @javascript_pack_tag_loaded = true capture do # Output debug buffer first if early_hints_debug_enabled? store = early_hints_store if store[:debug_buffer] && store[:debug_buffer].any? concat store[:debug_buffer].join("\n").html_safe concat "\n" end end (async, :javascript, **.dup.tap { |o| o[:async] = true }) concat "\n" if async.any? && deferred.any? (deferred, :javascript, **.dup.tap { |o| o[:defer] = true }) concat "\n" if sync.any? && deferred.any? (sync, :javascript, ) end end |
#preload_pack_asset(name, **options) ⇒ Object
Creates a link tag, for preloading, that references a given Shakapacker asset. In production mode, the digested reference is automatically looked up. See: developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content
Example:
<%= preload_pack_asset 'fonts/fa-regular-400.woff2' %> # =>
<link rel="preload" href="/packs/fonts/fa-regular-400-944fb546bd7018b07190a32244f67dc9.woff2" as="font" type="font/woff2" crossorigin="anonymous">
169 170 171 172 173 174 175 |
# File 'lib/shakapacker/helper.rb', line 169 def preload_pack_asset(name, **) if self.class.method_defined?(:preload_link_tag) preload_link_tag(current_shakapacker_instance.manifest.lookup!(name), ) else raise "You need Rails >= 5.2 to use this tag." end end |
#prepend_javascript_pack_tag(*names, defer: true, async: false) ⇒ Object
349 350 351 352 353 |
# File 'lib/shakapacker/helper.rb', line 349 def prepend_javascript_pack_tag(*names, defer: true, async: false) update_javascript_pack_tag_queue(defer: defer, async: async) do |hash_key| javascript_pack_tag_queue[hash_key].unshift(*names) end end |
#send_pack_early_hints(config) ⇒ Object
Sends HTTP 103 Early Hints for specified packs with fine-grained control over JavaScript and CSS handling. This is the “raw” method for maximum flexibility.
Use this in controller actions BEFORE expensive work (database queries, API calls) to maximize parallelism - the browser downloads assets while Rails processes the request.
For simpler cases, use javascript_pack_tag and stylesheet_pack_tag which automatically send hints when called (combining queued + direct pack names).
HTTP 103 Early Hints allows the server to send preliminary responses with Link headers before the final HTTP 200 response, enabling browsers to start downloading critical assets during the server’s “think time”.
Timeline:
1. Browser requests page
2. Controller calls send_pack_early_hints (this method)
3. Server sends HTTP 103 with Link: headers
4. Browser starts downloading assets IN PARALLEL with step 5
5. Rails continues expensive work (queries, rendering)
6. Server sends HTTP 200 with full HTML
7. Assets already downloaded = faster page load
Requires Rails 5.2+, HTTP/2, and server support (Puma 5+, nginx 1.13+). Gracefully degrades if not supported.
References:
-
Rails API: api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-send_early_hints
-
HTTP 103 Spec: datatracker.ietf.org/doc/html/rfc8297
Examples:
# Controller pattern: send hints BEFORE expensive work
def show
send_pack_early_hints({
"application" => { js: "preload", css: "preload" },
"vendor" => { js: "prefetch", css: "none" }
})
# Browser now downloading assets while we do expensive work
@posts = Post.includes(:comments, :author).where(complex_conditions)
# ... more expensive work ...
end
# Supported handling values:
# - "preload": High-priority, browser downloads immediately
# - "prefetch": Low-priority, browser may download when idle
# - "none" or false: Skip this asset type for this pack
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/shakapacker/helper.rb', line 224 def send_pack_early_hints(config) return nil unless early_hints_supported? && early_hints_enabled? # Accumulate both JS and CSS hints, then send ONCE config.each do |pack_name, handlers| # Accumulate JavaScript hints js_handling = handlers[:js] || handlers["js"] normalized_js = normalize_hint_value(js_handling) if normalized_js send_early_hints_internal({ pack_name.to_s => normalized_js }, type: :javascript) end # Accumulate CSS hints css_handling = handlers[:css] || handlers["css"] normalized_css = normalize_hint_value(css_handling) if normalized_css send_early_hints_internal({ pack_name.to_s => normalized_css }, type: :stylesheet) end end # Flush the accumulated hints as a SINGLE 103 response # (Browsers only process the first 103) flush_early_hints nil end |
#stylesheet_pack_tag(*names, early_hints: nil, **options) ⇒ Object
Creates link tags that reference the css chunks from entrypoints when using split chunks API, as compiled by webpack per the entries list in package/environments/base.js. By default, this list is auto-generated to match everything in app/javascript/entrypoints/*.js and all the dependent chunks. In production mode, the digested reference is automatically looked up. See: webpack.js.org/plugins/split-chunks-plugin/
Examples:
<%= stylesheet_pack_tag 'calendar', 'map' %> # =>
<link rel="stylesheet" media="screen" href="/packs/3-8c7ce31a.chunk.css" />
<link rel="stylesheet" media="screen" href="/packs/calendar-8c7ce31a.chunk.css" />
<link rel="stylesheet" media="screen" href="/packs/map-8c7ce31a.chunk.css" />
When using the webpack-dev-server, CSS is inlined so HMR can be turned on for CSS,
including CSS modules
<%= stylesheet_pack_tag 'calendar', 'map' %> # => nil
DO:
<%= stylesheet_pack_tag 'calendar', 'map' %>
DON’T:
<%= stylesheet_pack_tag 'calendar' %>
<%= stylesheet_pack_tag 'map' %>
Early Hints:
By default, HTTP 103 Early Hints are sent automatically when this helper is called,
allowing browsers to preload CSS assets in parallel with Rails rendering.
<%= stylesheet_pack_tag 'application' %>
# Automatically sends early hints for 'application' pack
# Customize handling per pack:
<%= stylesheet_pack_tag 'application', 'vendor',
early_hints: { 'application' => 'preload', 'vendor' => 'prefetch' } %>
# Disable early hints:
<%= stylesheet_pack_tag 'application', early_hints: false %>
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/shakapacker/helper.rb', line 290 def stylesheet_pack_tag(*names, early_hints: nil, **) return "" if Shakapacker.inlining_css? # Collect all packs (queue + direct args) all_packs = ((@stylesheet_pack_tag_queue || []) + names).uniq # Resolve effective early hints value (nil = use config default) effective_hints = resolve_early_hints_value(early_hints, :stylesheet) # Send early hints automatically if enabled if early_hints_enabled? && effective_hints && effective_hints != "none" hints_config = normalize_pack_hints(all_packs, effective_hints) send_stylesheet_early_hints_internal(hints_config) # Flush accumulated hints (sends the single 103 response) flush_early_hints elsif early_hints_debug_enabled? store = early_hints_store store[:debug_buffer] ||= [] store[:debug_buffer] << "<!-- Shakapacker Early Hints (CSS): SKIPPED (early_hints: #{effective_hints.inspect}) -->" end requested_packs = sources_from_manifest_entrypoints(names, type: :stylesheet) appended_packs = available_sources_from_manifest_entrypoints(@stylesheet_pack_tag_queue || [], type: :stylesheet) @stylesheet_pack_tag_loaded = true capture do # Output debug buffer first if early_hints_debug_enabled? store = early_hints_store if store[:debug_buffer] && store[:debug_buffer].any? concat store[:debug_buffer].join("\n").html_safe concat "\n" end end (requested_packs | appended_packs, :stylesheet, ) end end |