Module: Controllers::Webpackable

Extended by:
ActiveSupport::Concern
Included in:
ApplicationController
Defined in:
app/concerns/controllers/webpackable.rb

Overview

Webpackable - Rails concern for webpack asset management

This module provides helpers for including webpack assets (JS/CSS) in Rails views.
It uses WebpackManifestLoader to get fresh asset URLs from the webpack manifest,
avoiding stale cache issues that can occur with traditional Rails caching.

Key Features:

  • Fresh manifest loading (no stale cache issues)
  • Development webpack dev server support
  • Font preloading for custom typography
  • Error handling and fallbacks

Usage in Controllers:
include Controllers::Webpackable

Usage in Views:
<%= webpack_js_include 'www' %>
<%= webpack_css_include 'crm' %>
<%= preload_webpack_fonts %>
<% if wpd_is_running? %>...<% end %>

Dependencies:

  • WebpackManifestLoader (config/initializers/webpack_manifest.rb)
  • Webpack assets built to public/javascripts/webpack/

Instance Method Summary collapse

Instance Method Details

#preload_webpack_fontsvoid

This method returns an undefined value.

Preloads custom typography fonts for better performance

Preloads Sofia Pro fonts (light, regular, semibold) and Orpheus Pro fonts
(normal, medium, bold) to prevent font loading delays and improve perceived
performance. Font Awesome 7 Pro+ uses SVG+JS so no font preloading is needed for icons.

Example:
<%= preload_webpack_fonts %>



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/concerns/controllers/webpackable.rb', line 136

def preload_webpack_fonts
  # Font Awesome 7 Pro+ now uses SVG+JS - no font preloading needed
  # Only preload custom typography fonts (Sofia Pro, Orpheus Pro, Inter, Playfair Display)
  # Skip preloading in development to avoid warnings
  return if Rails.env.development? && wpd_is_running?

  # Only preload the two fonts needed above the fold. The remaining fonts
  # (semibold, Orpheus Pro, Inter, Playfair Display) load via @font-face
  # with font-display:swap and no longer compete with the LCP image for
  # bandwidth on the critical path (~200 KB freed from the Link header).
  custom_fonts = %w[
    sofiapro-light-webfont sofiapro-regular-webfont
  ]

  # Preload custom typography fonts
  custom_fonts.each do |font|
    url = Array(get_hashed_name_from_manifest(font, 'woff2')).first
    next if url.blank?

    helpers.preload_link_tag(
      ensure_absolute_wpd_url(url, wpd_is_running? ? dev_server_origin : nil),
      as: :font,
      type: 'font/woff2',
      crossorigin: :anonymous,
      nopush: false,
      skip_pipeline: true
    )
  rescue StandardError => e
    Rails.logger.warn "Custom font preload failed for #{font}: #{e.message}"
  end
end

#webpack_css_include(webpack_file, track: false, exclude: []) ⇒ String

Generates link tags for webpack CSS bundles

Example:
<%= webpack_css_include 'crm', track: true %> # layout main bundle
<%= webpack_css_include 'rpStyles' %> # per-page bundle

Parameters:

  • webpack_file (String)

    The webpack entrypoint name (e.g., 'www', 'crm')

  • track (Boolean) (defaults to: false)

    Whether to emit data-turbo-track="reload". Default
    is false; see #webpack_js_include for rationale.

  • exclude (Array<String>) (defaults to: [])

    Substrings of chunk URLs to skip — used by
    the WWW/CRM layouts to drop vendor-fa-glyphs from the render-blocking
    include because that chunk loads via a separate <link rel=preload>.
    Without this, the entry's CSS dep graph would emit render-blocking
    <link> tags for the glyph chunk in addition to our deferred preload.

Returns:

  • (String)

    HTML link tags for the webpack entrypoint CSS



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/concerns/controllers/webpackable.rb', line 91

def webpack_css_include(webpack_file, track: false, exclude: [])
  names = get_hashed_name_from_manifest(webpack_file, 'css')
  origin = wpd_is_running? ? dev_server_origin : nil
  tags = names.filter_map do |url|
    next if exclude.any? { |e| url.include?(e) }
    full_url = ensure_absolute_wpd_url(url, origin)
    helpers.tag.link(rel: 'stylesheet', href: full_url, media: 'all', **turbo_track_attr(track))
  end
  safe_join(tags, "\n")
rescue StandardError => e
  Rails.logger.error "webpack_css_include error: #{e.message}"
  ''
end

#webpack_css_url(webpack_file, exclude: []) ⇒ Array<String>

Returns the URL(s) for a webpack CSS bundle

Example:
webpack_css_url('www') # => ["/javascripts/webpack/www-abc123.css"]

Parameters:

  • webpack_file (String)

    The webpack entrypoint name (e.g., 'www', 'crm')

Returns:

  • (Array<String>)

    Array of CSS URLs for the webpack entrypoint



113
114
115
116
117
118
119
120
121
122
123
# File 'app/concerns/controllers/webpackable.rb', line 113

def webpack_css_url(webpack_file, exclude: [])
  names = get_hashed_name_from_manifest(webpack_file, 'css')
  origin = wpd_is_running? ? dev_server_origin : nil
  names.filter_map do |url|
    next if exclude.any? { |e| url.include?(e) }
    ensure_absolute_wpd_url(url, origin)
  end
rescue StandardError => e
  Rails.logger.error "webpack_css_url error: #{e.message}"
  []
end

#webpack_js_include(webpack_file, async: false, defer: false, track: false) ⇒ String

Generates script tags for webpack JavaScript bundles

Example:
<%= webpack_js_include 'crm', track: true %> # layout main bundle
<%= webpack_js_include 'reports' %> # per-page bundle

Parameters:

  • webpack_file (String)

    The webpack entrypoint name (e.g., 'www', 'crm')

  • async (Boolean) (defaults to: false)

    Whether to load scripts asynchronously

  • defer (Boolean) (defaults to: false)

    Whether to defer script execution

  • track (Boolean) (defaults to: false)

    Whether to emit data-turbo-track="reload". Default
    is false so that per-page bundles (e.g., 'rpStyles', 'reports') do not
    force a full page reload when navigating between pages whose tracked
    asset sets differ. Pass track: true for layout-level main bundles
    ('crm', 'www') so a deploy that swaps their hashed URL still forces a
    reload and users pick up new code. See _crm_header_stack.html.erb and
    _cms_header.html.erb for the layout-level call sites.

Returns:

  • (String)

    HTML script tags for the webpack entrypoint



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'app/concerns/controllers/webpackable.rb', line 61

def webpack_js_include(webpack_file, async: false, defer: false, track: false)
  names = get_hashed_name_from_manifest(webpack_file, 'js')
  # Prefix dev-server origin in development when WDS is running
  origin = wpd_is_running? ? dev_server_origin : nil
  tags = names.map do |url|
    full_url = ensure_absolute_wpd_url(url, origin)
    helpers.javascript_include_tag(full_url, async: async, defer: defer, **turbo_track_attr(track))
  end
  safe_join(tags, "\n")
rescue StandardError => e
  Rails.logger.error "webpack_js_include error: #{e.message}"
  ''
end

#wpd_is_running?Boolean

Checks if webpack dev server is running in development

Example:
<% if wpd_is_running? %>

<% end %>

Returns:

  • (Boolean)

    True if webpack dev server is accessible



177
178
179
# File 'app/concerns/controllers/webpackable.rb', line 177

def wpd_is_running?
  webpack_dev_server_running?
end