CINXE.COM
The Asset Pipeline — Ruby on Rails Guides
<!doctype html> <html dir="ltr" lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>The Asset Pipeline — Ruby on Rails Guides</title> <link rel="stylesheet" type="text/css" href="stylesheets/style-6fc5bf25e695f363fd1dd3d9dbb2f997.css" data-turbo-track="reload"> <link rel="stylesheet" type="text/css" href="stylesheets/print-a87ee66d50ce96bb83ac082f1249fe3e.css" media="print"> <link rel="stylesheet" type="text/css" href="stylesheets/highlight-2794201d063bd2e4dbd0f0874c2a3f6f.css" data-turbo-track="reload"> <link rel="icon" href="images/favicon.ico" sizes="any"> <link rel="apple-touch-icon" href="images/icon.png"> <link rel="canonical" href="https://guides.rubyonrails.org/asset_pipeline.html"> <script src="javascripts/@hotwired--turbo-764f59c7edbeb902a9068c0340dd274e.js" data-turbo-track="reload"></script> <script src="javascripts/clipboard-8b7aed6f069f0cf58eeae353cd2f898b.js" data-turbo-track="reload"></script> <script src="javascripts/guides-751b87e159daf790ddf7e8e88ad8465a.js" data-turbo-track="reload"></script> <meta property="og:title" content="The Asset Pipeline — Ruby on Rails Guides" /> <meta name="description" content="The Asset PipelineThis guide explains how to handle essential asset management tasks.After reading this guide, you will know: What is an asset pipeline. The main features of Propshaft, and how to set it up. How to migrate from Sprockets to Propshaft. How to use other libraries for more advanced asset management." /> <meta property="og:description" content="The Asset PipelineThis guide explains how to handle essential asset management tasks.After reading this guide, you will know: What is an asset pipeline. The main features of Propshaft, and how to set it up. How to migrate from Sprockets to Propshaft. How to use other libraries for more advanced asset management." /> <meta property="og:locale" content="en_US" /> <meta property="og:site_name" content="Ruby on Rails Guides" /> <meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" /> <meta property="og:type" content="website" /> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@100..900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Heebo:wght@100..900&family=Noto+Sans+Arabic:wght@100..900&display=swap" rel="stylesheet"> <meta name="theme-color" content="#C81418"> </head> <body dir="ltr" class="guide no-js"> <script> document.body.classList.remove('no-js') </script> <a id="main-skip-link" href="#main" class="skip-link" data-turbo="false"> Skip to main content </a> <div id="mobile-navigation-bar"> <div class="wrapper"> <strong class="more-info-label">More at <a href="https://rubyonrails.org/">rubyonrails.org:</a> </strong> <button type="button" class="js-only red-button more-info-button" id="more-info" aria-controls="more-info-links" aria-expanded="false"> More Ruby on Rails </button> <ul id="more-info-links" class="more-info-links hidden"> <li class="more-info"><a href="https://rubyonrails.org/blog">Blog</a></li> <li class="more-info"><a href="https://guides.rubyonrails.org/">Guides</a></li> <li class="more-info"><a href="https://api.rubyonrails.org/">API</a></li> <li class="more-info"><a href="https://discuss.rubyonrails.org/">Forum</a></li> <li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li> </ul> </div> </div> <header id="page-header"> <div class="wrapper clearfix"> <nav id="feature-nav"> <div class="header-logo"> <a href="index.html" title="Guides home for v8.0.1 Guides">Guides</a> <span id="version-switcher" class="js-only"> <label for="version-switcher-select">Version: <span class="visibly-hidden">pick from the list to go to that Rails version's guides</span></label> <select id="version-switcher-select" class="guides-version"> <option value="https://edgeguides.rubyonrails.org/">Edge</option> <option value="https://guides.rubyonrails.org/v8.0/asset_pipeline.html" selected>8.0</option> <option value="https://guides.rubyonrails.org/v7.2/asset_pipeline.html">7.2</option> <option value="https://guides.rubyonrails.org/v7.1/asset_pipeline.html">7.1</option> <option value="https://guides.rubyonrails.org/v7.0/asset_pipeline.html">7.0</option> <option value="https://guides.rubyonrails.org/v6.1/asset_pipeline.html">6.1</option> <option value="https://guides.rubyonrails.org/v6.0/asset_pipeline.html">6.0</option> <option value="https://guides.rubyonrails.org/v5.2/asset_pipeline.html">5.2</option> <option value="https://guides.rubyonrails.org/v5.1/asset_pipeline.html">5.1</option> <option value="https://guides.rubyonrails.org/v5.0/asset_pipeline.html">5.0</option> <option value="https://guides.rubyonrails.org/v4.2/asset_pipeline.html">4.2</option> <option value="https://guides.rubyonrails.org/v4.1/asset_pipeline.html">4.1</option> <option value="https://guides.rubyonrails.org/v4.0/asset_pipeline.html">4.0</option> <option value="https://guides.rubyonrails.org/v3.2/asset_pipeline.html">3.2</option> <option value="https://guides.rubyonrails.org/v3.1/asset_pipeline.html">3.1</option> <option value="https://guides.rubyonrails.org/v3.0/asset_pipeline.html">3.0</option> <option value="https://guides.rubyonrails.org/v2.3/asset_pipeline.html">2.3</option> </select> </span> </div> <ul class="nav"> <li><a class="nav-item" id="home_nav" href="https://rubyonrails.org/">Home</a></li> <li class="guides-index guides-index-large"> <a href="index.html" id="guides-menu-button" data-aria-controls="guides" data-aria-expanded="false" class="guides-index-item nav-item">Guides Index</a> <div id="guides" class="clearfix" style="display: none;"> <hr /> <dl class="guides-section-container"> <div class="guides-section"> <dt>Start Here</dt> <dd><a href="getting_started.html">Getting Started with Rails</a></dd> <dd><a href="install_ruby_on_rails.html">Install Ruby on Rails</a></dd> </div> <div class="guides-section"> <dt>Models</dt> <dd><a href="active_record_basics.html">Active Record Basics</a></dd> <dd><a href="active_record_migrations.html">Active Record Migrations</a></dd> <dd><a href="active_record_validations.html">Active Record Validations</a></dd> <dd><a href="active_record_callbacks.html">Active Record Callbacks</a></dd> <dd><a href="association_basics.html">Active Record Associations</a></dd> <dd><a href="active_record_querying.html">Active Record Query Interface</a></dd> <dd><a href="active_model_basics.html">Active Model Basics</a></dd> </div> <div class="guides-section"> <dt>Views</dt> <dd><a href="action_view_overview.html">Action View Overview</a></dd> <dd><a href="layouts_and_rendering.html">Layouts and Rendering in Rails</a></dd> <dd><a href="action_view_helpers.html">Action View Helpers</a></dd> <dd><a href="form_helpers.html">Action View Form Helpers</a></dd> </div> <div class="guides-section"> <dt>Controllers</dt> <dd><a href="action_controller_overview.html">Action Controller Overview</a></dd> <dd><a href="action_controller_advanced_topics.html">Action Controller Advanced Topics</a></dd> <dd><a href="routing.html">Rails Routing from the Outside In</a></dd> </div> <div class="guides-section"> <dt>Other Components</dt> <dd><a href="active_support_core_extensions.html">Active Support Core Extensions</a></dd> <dd><a href="action_mailer_basics.html">Action Mailer Basics</a></dd> <dd><a href="action_mailbox_basics.html">Action Mailbox Basics</a></dd> <dd><a href="action_text_overview.html">Action Text Overview</a></dd> <dd><a href="active_job_basics.html">Active Job Basics</a></dd> <dd><a href="active_storage_overview.html">Active Storage Overview</a></dd> <dd><a href="action_cable_overview.html">Action Cable Overview</a></dd> </div> <div class="guides-section"> <dt>Digging Deeper</dt> <dd><a href="i18n.html">Rails Internationalization (I18n) API</a></dd> <dd><a href="testing.html">Testing Rails Applications</a></dd> <dd><a href="debugging_rails_applications.html">Debugging Rails Applications</a></dd> <dd><a href="configuring.html">Configuring Rails Applications</a></dd> <dd><a href="command_line.html">The Rails Command Line</a></dd> <dd><a href="asset_pipeline.html">The Asset Pipeline</a></dd> <dd><a href="working_with_javascript_in_rails.html">Working with JavaScript in Rails</a></dd> <dd><a href="autoloading_and_reloading_constants.html">Autoloading and Reloading</a></dd> <dd><a href="api_app.html">Using Rails for API-only Applications</a></dd> </div> <div class="guides-section"> <dt>Going to Production</dt> <dd><a href="tuning_performance_for_deployment.html">Tuning Performance for Deployment</a></dd> <dd><a href="caching_with_rails.html">Caching with Rails: An Overview</a></dd> <dd><a href="security.html">Securing Rails Applications</a></dd> <dd><a href="error_reporting.html">Error Reporting in Rails Applications</a></dd> </div> <div class="guides-section"> <dt>Advanced Active Record</dt> <dd><a href="active_record_multiple_databases.html">Multiple Databases</a></dd> <dd><a href="active_record_composite_primary_keys.html">Composite Primary Keys</a></dd> </div> <div class="guides-section"> <dt>Extending Rails</dt> <dd><a href="rails_on_rack.html">Rails on Rack</a></dd> <dd><a href="generators.html">Creating and Customizing Rails Generators & Templates</a></dd> </div> <div class="guides-section"> <dt>Contributing</dt> <dd><a href="contributing_to_ruby_on_rails.html">Contributing to Ruby on Rails</a></dd> <dd><a href="api_documentation_guidelines.html">API Documentation Guidelines</a></dd> <dd><a href="ruby_on_rails_guides_guidelines.html">Guides Guidelines</a></dd> <dd><a href="development_dependencies_install.html">Installing Rails Core Development Dependencies</a></dd> </div> <div class="guides-section"> <dt>Policies</dt> <dd><a href="maintenance_policy.html">Maintenance Policy</a></dd> </div> <div class="guides-section"> <dt>Release Notes</dt> <dd><a href="upgrading_ruby_on_rails.html">Upgrading Ruby on Rails</a></dd> <dd><a href="8_0_release_notes.html">Version 8.0 - November 2024</a></dd> <dd><a href="7_2_release_notes.html">Version 7.2 - August 2024</a></dd> <dd><a href="7_1_release_notes.html">Version 7.1 - October 2023</a></dd> <dd><a href="7_0_release_notes.html">Version 7.0 - December 2021</a></dd> <dd><a href="6_1_release_notes.html">Version 6.1 - December 2020</a></dd> <dd><a href="6_0_release_notes.html">Version 6.0 - August 2019</a></dd> <dd><a href="5_2_release_notes.html">Version 5.2 - April 2018</a></dd> <dd><a href="5_1_release_notes.html">Version 5.1 - April 2017</a></dd> <dd><a href="5_0_release_notes.html">Version 5.0 - June 2016</a></dd> <dd><a href="4_2_release_notes.html">Version 4.2 - December 2014</a></dd> <dd><a href="4_1_release_notes.html">Version 4.1 - April 2014</a></dd> <dd><a href="4_0_release_notes.html">Version 4.0 - June 2013</a></dd> <dd><a href="3_2_release_notes.html">Version 3.2 - January 2012</a></dd> <dd><a href="3_1_release_notes.html">Version 3.1 - August 2011</a></dd> <dd><a href="3_0_release_notes.html">Version 3.0 - August 2010</a></dd> <dd><a href="2_3_release_notes.html">Version 2.3 - March 2009</a></dd> <dd><a href="2_2_release_notes.html">Version 2.2 - November 2008</a></dd> </div> </dl> </div> </li> <li><a class="nav-item" href="contributing_to_ruby_on_rails.html">Contribute</a></li> <li class="guides-index guides-index-small js-only"> <label for="guides-selector"> Navigate to a guide: </label> <select id="guides-selector" class="guides-index-item nav-item"> <option value="index.html">Guides Index</option> <optgroup label="Start Here"> <option value="getting_started.html">Getting Started with Rails</option> <option value="install_ruby_on_rails.html">Install Ruby on Rails</option> </optgroup> <optgroup label="Models"> <option value="active_record_basics.html">Active Record Basics</option> <option value="active_record_migrations.html">Active Record Migrations</option> <option value="active_record_validations.html">Active Record Validations</option> <option value="active_record_callbacks.html">Active Record Callbacks</option> <option value="association_basics.html">Active Record Associations</option> <option value="active_record_querying.html">Active Record Query Interface</option> <option value="active_model_basics.html">Active Model Basics</option> </optgroup> <optgroup label="Views"> <option value="action_view_overview.html">Action View Overview</option> <option value="layouts_and_rendering.html">Layouts and Rendering in Rails</option> <option value="action_view_helpers.html">Action View Helpers</option> <option value="form_helpers.html">Action View Form Helpers</option> </optgroup> <optgroup label="Controllers"> <option value="action_controller_overview.html">Action Controller Overview</option> <option value="action_controller_advanced_topics.html">Action Controller Advanced Topics</option> <option value="routing.html">Rails Routing from the Outside In</option> </optgroup> <optgroup label="Other Components"> <option value="active_support_core_extensions.html">Active Support Core Extensions</option> <option value="action_mailer_basics.html">Action Mailer Basics</option> <option value="action_mailbox_basics.html">Action Mailbox Basics</option> <option value="action_text_overview.html">Action Text Overview</option> <option value="active_job_basics.html">Active Job Basics</option> <option value="active_storage_overview.html">Active Storage Overview</option> <option value="action_cable_overview.html">Action Cable Overview</option> </optgroup> <optgroup label="Digging Deeper"> <option value="i18n.html">Rails Internationalization (I18n) API</option> <option value="testing.html">Testing Rails Applications</option> <option value="debugging_rails_applications.html">Debugging Rails Applications</option> <option value="configuring.html">Configuring Rails Applications</option> <option value="command_line.html">The Rails Command Line</option> <option value="asset_pipeline.html">The Asset Pipeline</option> <option value="working_with_javascript_in_rails.html">Working with JavaScript in Rails</option> <option value="autoloading_and_reloading_constants.html">Autoloading and Reloading</option> <option value="api_app.html">Using Rails for API-only Applications</option> </optgroup> <optgroup label="Going to Production"> <option value="tuning_performance_for_deployment.html">Tuning Performance for Deployment</option> <option value="caching_with_rails.html">Caching with Rails: An Overview</option> <option value="security.html">Securing Rails Applications</option> <option value="error_reporting.html">Error Reporting in Rails Applications</option> </optgroup> <optgroup label="Advanced Active Record"> <option value="active_record_multiple_databases.html">Multiple Databases</option> <option value="active_record_composite_primary_keys.html">Composite Primary Keys</option> </optgroup> <optgroup label="Extending Rails"> <option value="rails_on_rack.html">Rails on Rack</option> <option value="generators.html">Creating and Customizing Rails Generators & Templates</option> </optgroup> <optgroup label="Contributing"> <option value="contributing_to_ruby_on_rails.html">Contributing to Ruby on Rails</option> <option value="api_documentation_guidelines.html">API Documentation Guidelines</option> <option value="ruby_on_rails_guides_guidelines.html">Guides Guidelines</option> <option value="development_dependencies_install.html">Installing Rails Core Development Dependencies</option> </optgroup> <optgroup label="Policies"> <option value="maintenance_policy.html">Maintenance Policy</option> </optgroup> <optgroup label="Release Notes"> <option value="upgrading_ruby_on_rails.html">Upgrading Ruby on Rails</option> <option value="8_0_release_notes.html">Version 8.0 - November 2024</option> <option value="7_2_release_notes.html">Version 7.2 - August 2024</option> <option value="7_1_release_notes.html">Version 7.1 - October 2023</option> <option value="7_0_release_notes.html">Version 7.0 - December 2021</option> <option value="6_1_release_notes.html">Version 6.1 - December 2020</option> <option value="6_0_release_notes.html">Version 6.0 - August 2019</option> <option value="5_2_release_notes.html">Version 5.2 - April 2018</option> <option value="5_1_release_notes.html">Version 5.1 - April 2017</option> <option value="5_0_release_notes.html">Version 5.0 - June 2016</option> <option value="4_2_release_notes.html">Version 4.2 - December 2014</option> <option value="4_1_release_notes.html">Version 4.1 - April 2014</option> <option value="4_0_release_notes.html">Version 4.0 - June 2013</option> <option value="3_2_release_notes.html">Version 3.2 - January 2012</option> <option value="3_1_release_notes.html">Version 3.1 - August 2011</option> <option value="3_0_release_notes.html">Version 3.0 - August 2010</option> <option value="2_3_release_notes.html">Version 2.3 - March 2009</option> <option value="2_2_release_notes.html">Version 2.2 - November 2008</option> </optgroup> </select> </li> </ul> </nav> </div> </header> <hr class="hide" /> <main id="main"> <article> <header id="feature"> <div class="wrapper"> <h1>The Asset Pipeline</h1><p>This guide explains how to handle essential asset management tasks.</p><p>After reading this guide, you will know:</p> <ul> <li>What is an asset pipeline.</li> <li>The main features of Propshaft, and how to set it up.</li> <li>How to migrate from Sprockets to Propshaft.</li> <li>How to use other libraries for more advanced asset management.</li> </ul> <nav id="column-side" aria-label="Chapter" class="guide-index" data-turbo="false"> <a id="chapter-nav-skip-link" href="#article-body" class="skip-link"> Skip to article body </a> <h2 class="chapter"> <picture aria-hidden="true"> <!-- Using the `source` HTML tag to set the dark theme image --> <source srcset="images/icon_book-close-bookmark-1-wht.svg" media="(prefers-color-scheme: dark)" /> <img src="images/icon_book-close-bookmark-1.svg" alt="Chapter Icon" /> </picture> Chapters </h2> <ol class="chapters"> <li><a href="#what-is-an-asset-pipeline-questionmark">What is an Asset Pipeline?</a></li> <li><a href="#propshaft-features">Propshaft Features</a> <ul> <li><a href="#asset-load-order">Asset Load Order</a></li> <li><a href="#asset-organization">Asset Organization</a></li> <li><a href="#fingerprinting-versioning-with-digest-based-urls">Fingerprinting: Versioning with digest-based URLs</a></li> </ul></li> <li><a href="#working-with-propshaft">Working with Propshaft</a> <ul> <li><a href="#setup">Setup</a></li> <li><a href="#development">Development</a></li> <li><a href="#production">Production</a></li> </ul></li> <li><a href="#sprockets-to-propshaft">Sprockets to Propshaft</a> <ul> <li><a href="#evolution-of-asset-management-techniques">Evolution of Asset Management Techniques</a></li> <li><a href="#sprockets-vs-propshaft">Sprockets vs. Propshaft</a></li> <li><a href="#migration-steps">Migration Steps</a></li> </ul></li> <li><a href="#advanced-asset-management">Advanced Asset Management</a> <ul> <li><a href="#jsbundling-rails"><code>jsbundling-rails</code></a></li> <li><a href="#cssbundling-rails"><code>cssbundling-rails</code></a></li> <li><a href="#tailwindcss-rails"><code>tailwindcss-rails</code></a></li> <li><a href="#importmap-rails"><code>importmap-rails</code></a></li> </ul></li> </ol> </nav> </div> </header> <div class="wrapper"> <div id="column-main"> <section id="article-body"> <h2 id="what-is-an-asset-pipeline-questionmark"><a class="anchorlink" href="#what-is-an-asset-pipeline-questionmark" data-turbo="false"><span>1</span> What is an Asset Pipeline?</a></h2><p>The Rails Asset Pipeline is a library designed for organizing, caching, and serving static assets, such as JavaScript, CSS, and image files. It streamlines and optimizes the management of these assets to enhance the performance and maintainability of the application.</p><p>The Rails Asset Pipeline is managed by <a href="https://github.com/rails/propshaft"><strong>Propshaft</strong></a>. Propshaft is built for an era where transpilation, bundling and compression are less critical for basic applications, thanks to better browser support, faster networks and HTTP/2 capabilities.</p><p>Propshaft focuses on essential asset management tasks and leaves more complex tasks, such as JavaScript and CSS bundling and minification, to specialized tools like <a href="https://github.com/rails/jsbundling-rails"><code>js-bundling-rails</code></a> and <a href="https://github.com/rails/cssbundling-rails"><code>css-bundling-rails</code></a>, which can be added separately to your application. Propshaft focuses on <a href="#fingerprinting-versioning-with-digest-based-urls">fingerprinting</a> and emphasizes generating digest-based URLs for assets, allowing browsers to cache them, thus minimizing the need for intricate compilation and bundling.</p><p>The <a href="https://github.com/rails/propshaft">Propshaft</a> gem is enabled by default in new applications. If, for some reason, you want to disable it during setup, you can use the <code>--skip-asset-pipeline</code> option:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">rails </span>new app_name <span class="nt">--skip-asset-pipeline</span> </code></pre> <button class="clipboard-button" data-clipboard-text="rails new app_name --skip-asset-pipeline ">Copy</button> </div> <div class="interstitial note"><p>Before Rails 8, the asset pipeline was powered by <a href="https://github.com/rails/sprockets">Sprockets</a>. You can read about the <a href="https://guides.rubyonrails.org/v7.2/asset_pipeline.html">Sprockets Asset Pipeline</a> in previous versions of the Rails Guides. You can also explore the <a href="#evolution-of-asset-management-techniques">evolution of asset management techniques</a> to see how the Rails Asset Pipeline has evolved over time.</p></div><h2 id="propshaft-features"><a class="anchorlink" href="#propshaft-features" data-turbo="false"><span>2</span> Propshaft Features</a></h2><p>Propshaft expects that your assets are already in a browser-ready format—like plain CSS, JavaScript, or preprocessed images (like JPEGs or PNGs). Its job is to organize, version, and serve those assets efficiently. In this section, we’ll cover the main features of Propshaft and how they work.</p><h3 id="asset-load-order"><a class="anchorlink" href="#asset-load-order" data-turbo="false"><span>2.1</span> Asset Load Order</a></h3><p>With Propshaft, you can control the loading order of dependent files by specifying each file explicitly and organizing them manually or ensuring they are included in the correct sequence within your HTML or layout files. This ensures that dependencies are managed and loaded without relying on automated dependency management tools. Below are some strategies for managing dependencies:</p> <ol> <li><p>Manually include assets in the correct order:</p><p>In your HTML layout (usually <code>application.html.erb</code> for Rails apps) you can specify the exact order for loading CSS and JavaScript files by including each file individually in a specific order. For example:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="c"><!-- application.html.erb --></span> <span class="nt"><head></span> <span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"reset"</span> <span class="cp">%></span> <span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"base"</span> <span class="cp">%></span> <span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"main"</span> <span class="cp">%></span> <span class="nt"></head></span> <span class="nt"><body></span> <span class="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s2">"utilities"</span> <span class="cp">%></span> <span class="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s2">"main"</span> <span class="cp">%></span> <span class="nt"></body></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<!-- application.html.erb --> <head> <%= stylesheet_link_tag "reset" %> <%= stylesheet_link_tag "base" %> <%= stylesheet_link_tag "main" %> </head> <body> <%= javascript_include_tag "utilities" %> <%= javascript_include_tag "main" %> </body> ">Copy</button> </div> <p>This is important if, for instance, <code>main.js</code> relies on <code>utilities.js</code> to be loaded first.</p></li> <li><p>Use Modules in JavaScript (ES6)</p><p>If you have dependencies within JavaScript files, ES6 modules can help. By using import statements, you can explicitly control dependencies within JavaScript code. Just make sure your JavaScript files are set up as modules using <code><script type="module"></code> in your HTML:</p><div class="interstitial code"> <pre><code class="highlight plaintext">// main.js import { initUtilities } from "./utilities.js"; import { setupFeature } from "./feature.js"; initUtilities(); setupFeature(); </code></pre> <button class="clipboard-button" data-clipboard-text="// main.js import { initUtilities } from "./utilities.js"; import { setupFeature } from "./feature.js"; initUtilities(); setupFeature(); ">Copy</button> </div> <p>Then in your layout:</p><div class="interstitial code"> <pre><code class="highlight plaintext"><script type="module" src="main.js"></script> </code></pre> <button class="clipboard-button" data-clipboard-text="<script type="module" src="main.js"></script> ">Copy</button> </div> <p>This way, you can manage dependencies within JavaScript files without relying on Propshaft to understand them. By importing modules, you can control the order in which files are loaded and ensure dependencies are met.</p></li> <li><p>Combine Files when necessary</p><p>If you have several JavaScript or CSS files that must always load together, you can combine them into a single file. For example, you could create a <code>combined.js</code> file that imports or copies code from other scripts. Then, just include <code>combined.js</code> in your layout to avoid dealing with individual file ordering. This can be useful for files that always load together, like a set of utility functions or a group of styles for a specific component. While this approach can work for small projects or simple use cases, it can become tedious and error-prone for larger applications.</p></li> <li><p>Bundle your JavaScript or CSS using a bundler</p><p>If your project requires features like dependency chaining or CSS pre-processing, you may want to consider <a href="#advanced-asset-management">advanced asset management</a> alongside Propshaft.</p><p>Tools like <a href="https://github.com/rails/jsbundling-rails"><code>js-bundling-rails</code></a> integrates <a href="https://bun.sh/">Bun</a>, <a href="https://esbuild.github.io/">esbuild</a>, <a href="https://rollupjs.org/">rollup.js</a>, or <a href="https://webpack.js.org/">Webpack</a> into your Rails application, while <a href="https://github.com/rails/cssbundling-rails"><code>css-bundling-rails</code></a> can be used to process stylesheets that use <a href="https://tailwindcss.com/">Tailwind CSS</a>, <a href="https://getbootstrap.com/">Bootstrap</a>, <a href="https://bulma.io/">Bulma</a>, <a href="https://postcss.org/">PostCSS</a>, or <a href="https://sass-lang.com/">Dart Sass</a>.</p><p>These tools complement Propshaft by handling the complex processing, while Propshaft efficiently organizes and serves the final assets.</p></li> </ol> <h3 id="asset-organization"><a class="anchorlink" href="#asset-organization" data-turbo="false"><span>2.2</span> Asset Organization</a></h3><p>Propshaft organizes assets within the <code>app/assets</code> directory, which includes subdirectories like <code>images</code>, <code>javascripts</code>, and <code>stylesheets</code>. You can place your JavaScript, CSS, image files, and other assets into these directories, and Propshaft will manage them during the precompilation process.</p><p>You can also specify additional asset paths for Propshaft to search by modifying <code>config.assets.paths</code> in your <code>config/initializers/assets.rb</code> file. For example:</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="c1"># Add additional assets to the asset load path.</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">assets</span><span class="p">.</span><span class="nf">paths</span> <span class="o"><<</span> <span class="no">Emoji</span><span class="p">.</span><span class="nf">images_path</span> </code></pre> <button class="clipboard-button" data-clipboard-text="# Add additional assets to the asset load path. Rails.application.config.assets.paths << Emoji.images_path ">Copy</button> </div> <p>Propshaft will make all assets from the configured paths available for serving. During the precompilation process, Propshaft copies these assets into the <code>public/assets</code> directory, ensuring they are ready for production use.</p><p>Assets can be <a href="#digested-assets-in-views">referenced through their logical paths</a> using helpers like <code>asset_path</code>, <code>image_tag</code>, <code>javascript_include_tag</code>, and other asset helper tags. After running <a href="#production">assets:precompile in production</a>, these logical references are automatically converted into their fingerprinted paths using the <a href="#manifest-files"><code>.manifest.json</code> file</a>.</p><p>Its possible to exclude certain directories from this process, you can read more about it in the <a href="#fingerprinting-versioning-with-digest-based-urls">Fingerprinting section</a>.</p><h3 id="fingerprinting-versioning-with-digest-based-urls"><a class="anchorlink" href="#fingerprinting-versioning-with-digest-based-urls" data-turbo="false"><span>2.3</span> Fingerprinting: Versioning with digest-based URLs</a></h3><p>In Rails, asset versioning uses fingerprinting to add unique identifiers to asset filenames.</p><p>Fingerprinting is a technique that makes the name of a file dependent on its content. A digest of the file's content is generated and appended to the filename. This ensures that when the file content changes, its digest—and consequently its filename—also changes. This mechanism is crucial for caching assets effectively, as the browser will always load the updated version of an asset when its content changes, thereby improving performance. For static or infrequently changed content, this provides an easy way to tell whether two versions of a file are identical, even across different servers or deployment dates.</p><h4 id="asset-digesting"><a class="anchorlink" href="#asset-digesting" data-turbo="false"><span>2.3.1</span> Asset Digesting</a></h4><p>As mentioned in the <a href="#asset-organization">Asset Organization section</a>, in Propshaft, all assets from the paths configured in <code>config.assets.paths</code> are available for serving and will be copied into the <code>public/assets</code> directory.</p><p>When fingerprinted, an asset filename like <code>styles.css</code> is renamed to <code>styles-a1b2c3d4e5f6.css</code>. This ensures that if <code>styles.css</code> is updated, the filename changes as well, compelling the browser to download the latest version instead of using a potentially outdated cached copy.</p><h4 id="manifest-files"><a class="anchorlink" href="#manifest-files" data-turbo="false"><span>2.3.2</span> Manifest Files</a></h4><p>In Propshaft, the <code>.manifest.json</code> file is automatically generated during the asset precompilation process. This file maps original asset filenames to their fingerprinted versions, ensuring proper cache invalidation and efficient asset management. Located in the <code>public/assets</code> directory, the <code>.manifest.json</code> file helps Rails resolve asset paths at runtime, allowing it to reference the correct fingerprinted files.</p><p>The <code>.manifest.json</code> includes entries for main assets like <code>application.js</code> and <code>application.css</code> as well as other files, such as images. Here's an example of what the JSON might look like:</p><div class="interstitial code"> <pre><code class="highlight json"><span class="p">{</span> <span class="w"> </span><span class="nl">"application.css"</span><span class="p">:</span><span class="w"> </span><span class="s2">"application-6d58c9e6e3b5d4a7c9a8e3.css"</span><span class="p">,</span> <span class="w"> </span><span class="nl">"application.js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"application-2d4b9f6c5a7c8e2b8d9e6.js"</span><span class="p">,</span> <span class="w"> </span><span class="nl">"logo.png"</span><span class="p">:</span><span class="w"> </span><span class="s2">"logo-f3e8c9b2a6e5d4c8.png"</span><span class="p">,</span> <span class="w"> </span><span class="nl">"favicon.ico"</span><span class="p">:</span><span class="w"> </span><span class="s2">"favicon-d6c8e5a9f3b2c7.ico"</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="{ "application.css": "application-6d58c9e6e3b5d4a7c9a8e3.css", "application.js": "application-2d4b9f6c5a7c8e2b8d9e6.js", "logo.png": "logo-f3e8c9b2a6e5d4c8.png", "favicon.ico": "favicon-d6c8e5a9f3b2c7.ico" } ">Copy</button> </div> <p>When a filename is unique and based on its content, HTTP headers can be set to encourage caches everywhere (whether at CDNs, at ISPs, in networking equipment, or in web browsers) to keep their own copy of the content. When the content is updated, the fingerprint will change. This will cause the remote clients to request a new copy of the content. This is generally known as cache busting.</p><h4 id="digested-assets-in-views"><a class="anchorlink" href="#digested-assets-in-views" data-turbo="false"><span>2.3.3</span> Digested Assets in Views</a></h4><p>You can reference digested assets in your views using standard Rails asset helpers like <code>asset_path</code>, <code>image_tag</code>, <code>javascript_include_tag</code>, <code>stylesheet_link_tag</code> and others.</p><p>For example, in your layout file, you can include a stylesheet like this:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"application"</span><span class="p">,</span> <span class="ss">media: </span><span class="s2">"all"</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= stylesheet_link_tag "application", media: "all" %> ">Copy</button> </div> <p>If you're using the <a href="https://github.com/hotwired/turbo-rails"><code>turbo-rails</code></a> gem (which is included by default in Rails), you can include the <code>data-turbo-track</code> option. This causes Turbo to check if an asset has been updated and, if so, reload it into the page:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"application"</span><span class="p">,</span> <span class="s2">"data-turbo-track"</span><span class="p">:</span> <span class="s2">"reload"</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> ">Copy</button> </div> <p>You can access images in the <code>app/assets/images</code> directory like this:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">image_tag</span> <span class="s2">"rails.png"</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= image_tag "rails.png" %> ">Copy</button> </div> <p>When the asset pipeline is enabled, Propshaft will serve this file. If a file exists at <code>public/assets/rails.png</code>, it will be served by the web server.</p><p>Alternatively, if you are using fingerprinted assets (e.g., <code>rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png</code>), Propshaft will also serve these correctly. The fingerprint is automatically applied during the precompilation process.</p><p>Images can be organized into subdirectories, and you can reference them by specifying the directory in the tag:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">image_tag</span> <span class="s2">"icons/rails.png"</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= image_tag "icons/rails.png" %> ">Copy</button> </div> <p>Finally, you can reference an image in your CSS like:</p><div class="interstitial code"> <pre><code class="highlight css"><span class="nt">background</span><span class="o">:</span> <span class="nt">url</span><span class="o">(</span><span class="s1">"/bg/pattern.svg"</span><span class="o">);</span> </code></pre> <button class="clipboard-button" data-clipboard-text="background: url("/bg/pattern.svg"); ">Copy</button> </div> <p>Propshaft will automatically convert this to:</p><div class="interstitial code"> <pre><code class="highlight css"><span class="nt">background</span><span class="o">:</span> <span class="nt">url</span><span class="o">(</span><span class="s1">"/assets/bg/pattern-2169cbef.svg"</span><span class="o">);</span> </code></pre> <button class="clipboard-button" data-clipboard-text="background: url("/assets/bg/pattern-2169cbef.svg"); ">Copy</button> </div> <div class="interstitial warning"><p>If you're precompiling your assets (see <a href="#production">the Production section</a>), linking to an asset that doesn't exist will raise an exception in the calling page. This includes linking to a blank string. Be careful when using <code>image_tag</code> and other helpers with user-supplied data. This ensures that the browser always fetches the correct version of the asset.</p></div><h4 id="digested-assets-in-javascript"><a class="anchorlink" href="#digested-assets-in-javascript" data-turbo="false"><span>2.3.4</span> Digested Assets in JavaScript</a></h4><p>In JavaScript, you need to manually trigger the asset transformation using the <code>RAILS_ASSET_URL</code> macro. Here’s an example:</p><div class="interstitial code"> <pre><code class="highlight javascript"><span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nc">extends</span> <span class="nx">Controller</span> <span class="p">{</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">img</span> <span class="o">=</span> <span class="nc">RAILS_ASSET_URL</span><span class="p">(</span><span class="dl">"</span><span class="s2">/icons/trash.svg</span><span class="dl">"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="export default class extends Controller { init() { this.img = RAILS_ASSET_URL("/icons/trash.svg"); } } ">Copy</button> </div> <p>This will transform into:</p><div class="interstitial code"> <pre><code class="highlight javascript"><span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nc">extends</span> <span class="nx">Controller</span> <span class="p">{</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">img</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">/assets/icons/trash-54g9cbef.svg</span><span class="dl">"</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="export default class extends Controller { init() { this.img = "/assets/icons/trash-54g9cbef.svg"; } } ">Copy</button> </div> <p>This ensures that the correct, digested file is used in your JavaScript code.</p><p>If you’re using bundlers like <a href="https://webpack.js.org/">Webpack</a> or <a href="https://esbuild.github.io/">esbuild</a>, you should let the bundlers handle the digesting process. If Propshaft detects that a file already has a digest in the filename (e.g., <code>script-2169cbef.js</code>), it will skip digesting the file again to avoid unnecessary reprocessing.</p><p>For managing assets with <a href="#importmap-rails">Import Maps</a>, Propshaft ensures that assets referenced in the import map are appropriately handled and mapped to their digested paths during the precompilation process.</p><h4 id="bypassing-the-digest-step"><a class="anchorlink" href="#bypassing-the-digest-step" data-turbo="false"><span>2.3.5</span> Bypassing the Digest Step</a></h4><p>If you need to reference files that refer to each other—like a JavaScript file and its source map—and want to avoid the digesting process, you can pre-digest these files manually. Propshaft recognizes files with the pattern <code>-[digest].digested.js</code> as files that have already been digested and will preserve their stable file names.</p><h4 id="excluding-directories-from-digestion"><a class="anchorlink" href="#excluding-directories-from-digestion" data-turbo="false"><span>2.3.6</span> Excluding Directories from Digestion</a></h4><p>You can exclude certain directories from the precompilation and digestion process by adding them to <code>config.assets.excluded_paths</code>. This is useful if, for example, you’re using <code>app/assets/stylesheets</code> as input to a compiler like <a href="https://sass-lang.com/">Dart Sass</a>, and you don’t want these files to be part of the asset load path.</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">assets</span><span class="p">.</span><span class="nf">excluded_paths</span> <span class="o">=</span> <span class="p">[</span><span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"app/assets/stylesheets"</span><span class="p">)]</span> </code></pre> <button class="clipboard-button" data-clipboard-text="config.assets.excluded_paths = [Rails.root.join("app/assets/stylesheets")] ">Copy</button> </div> <p>This will prevent the specified directories from being processed by Propshaft while still allowing them to be part of the precompilation process.</p><h2 id="working-with-propshaft"><a class="anchorlink" href="#working-with-propshaft" data-turbo="false"><span>3</span> Working with Propshaft</a></h2><p>From Rails 8 onwards, Propshaft is included by default. To use Propshaft, you need to configure it properly and organize your assets in a way that Rails can serve them efficiently.</p><h3 id="setup"><a class="anchorlink" href="#setup" data-turbo="false"><span>3.1</span> Setup</a></h3><p>Follow these steps for setup Propshaft in your Rails application:</p> <ol> <li><p>Create a new Rails application:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">rails </span>new app_name </code></pre> <button class="clipboard-button" data-clipboard-text="rails new app_name ">Copy</button> </div></li> <li><p>Organize your assets:</p><p>Propshaft expects your assets to be in the <code>app/assets</code> directory. You can organize your assets into subdirectories like <code>app/assets/javascripts</code> for JavaScript files, <code>app/assets/stylesheets</code> for CSS files, and <code>app/assets/images</code> for images.</p><p>For example, you can create a new JavaScript file in <code>app/assets/javascripts</code>:</p><div class="interstitial code"> <pre><code class="highlight javascript"><span class="c1">// app/assets/javascripts/main.js</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello, world!</span><span class="dl">"</span><span class="p">);</span> </code></pre> <button class="clipboard-button" data-clipboard-text="// app/assets/javascripts/main.js console.log("Hello, world!"); ">Copy</button> </div> <p>and a new CSS file in <code>app/assets/stylesheets</code>:</p><div class="interstitial code"> <pre><code class="highlight css"><span class="c">/* app/assets/stylesheets/main.css */</span> <span class="nt">body</span> <span class="p">{</span> <span class="nl">background-color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="/* app/assets/stylesheets/main.css */ body { background-color: red; } ">Copy</button> </div></li> <li><p>Link assets in your application layout</p><p>In your application layout file (usually <code>app/views/layouts/application.html.erb</code>), you can include your assets using the <code>stylesheet_link_tag</code> and <code>javascript_include_tag</code> helpers:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="c"><!-- app/views/layouts/application.html.erb --></span> <span class="cp"><!DOCTYPE html></span> <span class="nt"><html></span> <span class="nt"><head></span> <span class="nt"><title></span>MyApp<span class="nt"></title></span> <span class="cp"><%=</span> <span class="n">stylesheet_link_tag</span> <span class="s2">"main"</span> <span class="cp">%></span> <span class="nt"></head></span> <span class="nt"><body></span> <span class="cp"><%=</span> <span class="k">yield</span> <span class="cp">%></span> <span class="cp"><%=</span> <span class="n">javascript_include_tag</span> <span class="s2">"main"</span> <span class="cp">%></span> <span class="nt"></body></span> <span class="nt"></html></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<!-- app/views/layouts/application.html.erb --> <!DOCTYPE html> <html> <head> <title>MyApp</title> <%= stylesheet_link_tag "main" %> </head> <body> <%= yield %> <%= javascript_include_tag "main" %> </body> </html> ">Copy</button> </div> <p>This layout includes the <code>main.css</code> stylesheet and <code>main.js</code> JavaScript file in your application.</p></li> <li><p>Start the Rails server:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>server </code></pre> <button class="clipboard-button" data-clipboard-text="bin/rails server ">Copy</button> </div></li> <li><p>Preview your application:</p><p>Open your web browser and navigate to <code>http://localhost:3000</code>. You should see your Rails application with the included assets.</p></li> </ol> <h3 id="development"><a class="anchorlink" href="#development" data-turbo="false"><span>3.2</span> Development</a></h3><p>Rails and Propshaft are configured differently in development than in production, to allow rapid iteration without manual intervention.</p><h4 id="no-caching"><a class="anchorlink" href="#no-caching" data-turbo="false"><span>3.2.1</span> No Caching</a></h4><p>In development, Rails is configured to bypass asset caching. This means that when you modify assets (e.g., CSS, JavaScript), Rails will serve the most up-to-date version directly from the file system. There's no need to worry about versioning or file renaming because caching is skipped entirely. Browsers will automatically pull in the latest version each time you reload the page.</p><h4 id="automatic-reloading-of-assets"><a class="anchorlink" href="#automatic-reloading-of-assets" data-turbo="false"><span>3.2.2</span> Automatic Reloading of Assets</a></h4><p>When using Propshaft on its own, it automatically checks for updates to assets like JavaScript, CSS, or images with every request. This means you can edit these files, reload the browser, and instantly see the changes without needing to restart the Rails server.</p><p>When using JavaScript bundlers such as <a href="https://esbuild.github.io/">esbuild</a> or <a href="https://webpack.js.org/">Webpack</a> alongside Propshaft, the workflow combines both tools effectively:</p> <ul> <li>The bundler watches for changes in your JavaScript and CSS files, compiles them into the appropriate build directory, and keeps the files up to date.</li> <li>Propshaft ensures that the latest compiled assets are served to the browser whenever a request is made.</li> </ul> <p>For these setups, running <code>./bin/dev</code> starts both the Rails server and the asset bundler's development server.</p><p>In either case, Propshaft ensures that changes to your assets are reflected as soon as the browser page is reloaded, without requiring a server restart.</p><h4 id="improving-performance-with-file-watchers"><a class="anchorlink" href="#improving-performance-with-file-watchers" data-turbo="false"><span>3.2.3</span> Improving Performance with File Watchers</a></h4><p>In development, Propshaft checks if any assets have been updated before each request, using the application's file watcher (by default, <code>ActiveSupport::FileUpdateChecker</code>). If you have a large number of assets, you can improve performance by using the <code>listen</code> gem and configuring the following setting in <code>config/environments/development.rb</code>:</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">file_watcher</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">EventedFileUpdateChecker</span> </code></pre> <button class="clipboard-button" data-clipboard-text="config.file_watcher = ActiveSupport::EventedFileUpdateChecker ">Copy</button> </div> <p>This will reduce the overhead of checking for file updates and improve performance during development.</p><h3 id="production"><a class="anchorlink" href="#production" data-turbo="false"><span>3.3</span> Production</a></h3><p>In production, Rails serves assets with caching enabled to optimize performance, ensuring that your application can handle high traffic efficiently.</p><h4 id="asset-caching-and-versioning-in-production"><a class="anchorlink" href="#asset-caching-and-versioning-in-production" data-turbo="false"><span>3.3.1</span> Asset Caching and Versioning in Production</a></h4><p>As mentioned in the <a href="#fingerprinting-versioning-with-digest-based-urls">Fingerprinting section</a> when the file content changes, its digest also changes, thus the browser uses the updated version of the file. Whereas, if the content remains the same, the browser will use the cached version.</p><h4 id="precompiling-assets"><a class="anchorlink" href="#precompiling-assets" data-turbo="false"><span>3.3.2</span> Precompiling Assets</a></h4><p>In production, precompilation is typically run during deployment to ensure that the latest versions of the assets are served. Propshaft was explicitly not designed to provide full transpiler capabilities. However, it does offer an input -> output compiler setup that by default is used to translate <code>url(asset)</code> function calls in CSS to <code>url(digested-asset)</code> instead and source mapping comments likewise.</p><p>To manually run precompilation you can use the following command:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nv">RAILS_ENV</span><span class="o">=</span>production <span class="nb">rails </span>assets:precompile </code></pre> <button class="clipboard-button" data-clipboard-text="RAILS_ENV=production rails assets:precompile ">Copy</button> </div> <p>After doing this, all assets in the load path will be copied (or compiled when using <a href="#advanced-asset-management">advanced asset management</a>) in the precompilation step and stamped with a digest hash.</p><p>Additionally, you can set <code>ENV["SECRET_KEY_BASE_DUMMY"]</code> to trigger the use of a randomly generated <code>secret_key_base</code> that’s stored in a temporary file. This is useful when precompiling assets for production as part of a build step that otherwise does not need access to the production secrets.</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nv">RAILS_ENV</span><span class="o">=</span>production <span class="nv">SECRET_KEY_BASE_DUMMY</span><span class="o">=</span>1 <span class="nb">rails </span>assets:precompile </code></pre> <button class="clipboard-button" data-clipboard-text="RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 rails assets:precompile ">Copy</button> </div> <p>By default, assets are served from the <code>/assets</code> directory.</p><div class="interstitial warning"><p>Running the precompile command in development generates a marker file named <code>.manifest.json</code>, which tells the application that it can serve the compiled assets. As a result, any changes you make to your source assets won't be reflected in the browser until the precompiled assets are updated. If your assets stop updating in development mode, the solution is to remove the <code>.manifest.json</code> file located in <code>public/assets/</code>. You can use the <code>rails assets:clobber</code> command to delete all your precompiled assets and the <code>.manifest.json</code> file. This will force Rails to recompile the assets on the fly, reflecting the latest changes.</p></div><div class="interstitial note"><p>Always ensure that the expected compiled filenames end with <code>.js</code> or <code>.css</code>.</p></div><h5 id="far-future-expires-header"><a class="anchorlink" href="#far-future-expires-header" data-turbo="false"><span>3.3.2.1</span> Far-future Expires Header</a></h5><p>Precompiled assets exist on the file system and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add those headers.</p><p>For Apache:</p><div class="interstitial code"> <pre><code class="highlight apache"><span class="c"># The Expires* directives requires the Apache module</span> <span class="c"># `mod_expires` to be enabled.</span> <span class="p"><</span><span class="nl">Location</span><span class="sr"> /assets/</span><span class="p">></span> <span class="c"># Use of ETag is discouraged when Last-Modified is present</span> <span class="nc">Header</span> <span class="ss">unset</span> ETag <span class="nc">FileETag</span> <span class="ss">None</span> <span class="c"># RFC says only cache for 1 year</span> <span class="nc">ExpiresActive</span> <span class="ss">On</span> <span class="nc">ExpiresDefault</span> "access plus 1 year" <span class="p"></</span><span class="nl">Location</span><span class="p">></span> </code></pre> <button class="clipboard-button" data-clipboard-text="# The Expires* directives requires the Apache module # `mod_expires` to be enabled. <Location /assets/> # Use of ETag is discouraged when Last-Modified is present Header unset ETag FileETag None # RFC says only cache for 1 year ExpiresActive On ExpiresDefault "access plus 1 year" </Location> ">Copy</button> </div> <p>For NGINX:</p><div class="interstitial code"> <pre><code class="highlight nginx"><span class="k">location</span> <span class="p">~</span> <span class="sr">^/assets/</span> <span class="p">{</span> <span class="kn">expires</span> <span class="s">1y</span><span class="p">;</span> <span class="kn">add_header</span> <span class="s">Cache-Control</span> <span class="s">public</span><span class="p">;</span> <span class="kn">add_header</span> <span class="s">ETag</span> <span class="s">""</span><span class="p">;</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="location ~ ^/assets/ { expires 1y; add_header Cache-Control public; add_header ETag ""; } ">Copy</button> </div> <h4 id="cdns"><a class="anchorlink" href="#cdns" data-turbo="false"><span>3.3.3</span> CDNs</a></h4><p>CDN stands for <a href="https://en.wikipedia.org/wiki/Content_delivery_network">Content Delivery Network</a>, they are primarily designed to cache assets all over the world so that when a browser requests the asset, a cached copy will be geographically close to that browser. If you are serving assets directly from your Rails server in production, the best practice is to use a CDN in front of your application.</p><p>A common pattern for using a CDN is to set your production application as the "origin" server. This means when a browser requests an asset from the CDN and there is a cache miss, it will instead source the file from your server and then cache it. For example if you are running a Rails application on <code>example.com</code> and have a CDN configured at <code>mycdnsubdomain.fictional-cdn.com</code>, then when a request is made to <code>mycdnsubdomain.fictional-cdn.com/assets/smile.png</code>, the CDN will query your server once at <code>example.com/assets/smile.png</code> and cache the request. The next request to the CDN that comes in to the same URL will hit the cached copy. When the CDN can serve an asset directly the request never touches your Rails server. Since the assets from a CDN are geographically closer to the browser, the request is faster, and since your server doesn't need to spend time serving assets, it can focus on serving application code.</p><h5 id="set-up-a-cdn-to-serve-static-assets"><a class="anchorlink" href="#set-up-a-cdn-to-serve-static-assets" data-turbo="false"><span>3.3.3.1</span> Set up a CDN to Serve Static Assets</a></h5><p>To set up CDN, your application needs to be running in production on the internet at a publicly available URL, for example <code>example.com</code>. Next you'll need to sign up for a CDN service from a cloud hosting provider. When you do this you need to configure the "origin" of the CDN to point back at your website <code>example.com</code>. Check your provider for documentation on configuring the origin server.</p><p>The CDN you provisioned should give you a custom subdomain for your application such as <code>mycdnsubdomain.fictional-cdn.com</code> (note fictional-cdn.com is not a valid CDN provider at the time of this writing). Now that you have configured your CDN server, you need to tell browsers to use your CDN to grab assets instead of your Rails server directly. You can do this by configuring Rails to set your CDN as the asset host instead of using a relative path. To set your asset host in Rails, you need to set <a href="configuring.html#config-asset-host"><code>config.asset_host</code></a> in <code>config/environments/production.rb</code>:</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">asset_host</span> <span class="o">=</span> <span class="s2">"mycdnsubdomain.fictional-cdn.com"</span> </code></pre> <button class="clipboard-button" data-clipboard-text="config.asset_host = "mycdnsubdomain.fictional-cdn.com" ">Copy</button> </div> <div class="interstitial note"><p>You only need to provide the "host", this is the subdomain and root domain, you do not need to specify a protocol or "scheme" such as <code>http://</code> or <code>https://</code>. When a web page is requested, the protocol in the link to your asset that is generated will match how the webpage is accessed by default.</p></div><p>You can also set this value through an <a href="https://en.wikipedia.org/wiki/Environment_variable">environment variable</a> to make running a staging copy of your site easier:</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">asset_host</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"CDN_HOST"</span><span class="p">]</span> </code></pre> <button class="clipboard-button" data-clipboard-text="config.asset_host = ENV["CDN_HOST"] ">Copy</button> </div> <div class="interstitial note"><p>You would need to set <code>CDN_HOST</code> on your server to <code>mycdnsubdomain .fictional-cdn.com</code> for this to work.</p></div><p>Once you have configured your server and your CDN, asset paths from helpers such as:</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">asset_path</span><span class="p">(</span><span class="s1">'smile.png'</span><span class="p">)</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= asset_path('smile.png') %> ">Copy</button> </div> <p>Will be rendered as full CDN URLs like <code>http://mycdnsubdomain.fictional-cdn.com/assets/smile.png</code> (digest omitted for readability).</p><p>If the CDN has a copy of <code>smile.png</code>, it will serve it to the browser, and the origin server won't even know it was requested. If the CDN does not have a copy, it will try to find it at the "origin" <code>example.com/assets/smile.png</code>, and then store it for future use.</p><p>If you want to serve only some assets from your CDN, you can use custom <code>:host</code> option your asset helper, which overwrites value set in <a href="configuring.html#config-action-controller-asset-host"><code>config.action_controller.asset_host</code></a>.</p><div class="interstitial code"> <pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">asset_path</span> <span class="s1">'image.png'</span><span class="p">,</span> <span class="ss">host: </span><span class="s1">'mycdnsubdomain.fictional-cdn.com'</span> <span class="cp">%></span> </code></pre> <button class="clipboard-button" data-clipboard-text="<%= asset_path 'image.png', host: 'mycdnsubdomain.fictional-cdn.com' %> ">Copy</button> </div> <h5 id="customize-cdn-caching-behavior"><a class="anchorlink" href="#customize-cdn-caching-behavior" data-turbo="false"><span>3.3.3.2</span> Customize CDN Caching Behavior</a></h5><p>A CDN works by caching content. If the CDN has stale or bad content, then it is hurting rather than helping your application. The purpose of this section is to describe general caching behavior of most CDNs. Your specific provider may behave slightly differently.</p><p><strong>CDN Request Caching</strong></p><p>While a CDN is described as being good for caching assets, it actually caches the entire request. This includes the body of the asset as well as any headers. The most important one being <code>Cache-Control</code>, which tells the CDN (and web browsers) how to cache contents. This means that if someone requests an asset that does not exist, such as <code>/assets/i-dont-exist.png</code>, and your Rails application returns a 404, then your CDN will likely cache the 404 page if a valid <code>Cache-Control</code> header is present.</p><p><strong>CDN Header Debugging</strong></p><p>One way to check the headers are cached properly in your CDN is by using <a href="https://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com">curl</a>. You can request the headers from both your server and your CDN to verify they are the same:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span>curl <span class="nt">-I</span> http://www.example/assets/application- <span class="go">d0e099e021c95eb0de3615fd1d8c4d83.css</span> <span class="go">HTTP/1.1 200 OK</span> <span class="go">Server: Cowboy</span> <span class="go">Date: Sun, 24 Aug 2014 20:27:50 GMT</span> <span class="go">Connection: keep-alive</span> <span class="go">Last-Modified: Thu, 08 May 2014 01:24:14 GMT</span> <span class="go">Content-Type: text/css</span> <span class="go">Cache-Control: public, max-age=2592000</span> <span class="go">Content-Length: 126560</span> <span class="go">Via: 1.1 vegur</span> </code></pre> <button class="clipboard-button" data-clipboard-text="curl -I http://www.example/assets/application- ">Copy</button> </div> <p>Versus the CDN copy:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span>curl <span class="nt">-I</span> http://mycdnsubdomain.fictional-cdn.com/application- <span class="go">d0e099e021c95eb0de3615fd1d8c4d83.css</span> <span class="go">HTTP/1.1 200 OK Server: Cowboy Last-</span> <span class="go">Modified: Thu, 08 May 2014 01:24:14 GMT Content-Type: text/css</span> <span class="go">Cache-Control:</span> <span class="go">public, max-age=2592000</span> <span class="go">Via: 1.1 vegur</span> <span class="go">Content-Length: 126560</span> <span class="go">Accept-Ranges:</span> <span class="go">bytes</span> <span class="go">Date: Sun, 24 Aug 2014 20:28:45 GMT</span> <span class="go">Via: 1.1 varnish</span> <span class="go">Age: 885814</span> <span class="go">Connection: keep-alive</span> <span class="go">X-Served-By: cache-dfw1828-DFW</span> <span class="go">X-Cache: HIT</span> <span class="go">X-Cache-Hits:</span> <span class="go">68</span> <span class="go">X-Timer: S1408912125.211638212,VS0,VE0</span> </code></pre> <button class="clipboard-button" data-clipboard-text="curl -I http://mycdnsubdomain.fictional-cdn.com/application- ">Copy</button> </div> <p>Check your CDN documentation for any additional information they may provide such as <code>X-Cache</code> or for any additional headers they may add.</p><p><strong>CDNs and the Cache-Control Header</strong></p><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control"><code>Cache-Control</code></a> header describes how a request can be cached. When no CDN is used, a browser will use this information to cache contents. This is very helpful for assets that are not modified so that a browser does not need to re-download a website's CSS or JavaScript on every request. Generally we want our Rails server to tell our CDN (and browser) that the asset is "public". That means any cache can store the request. Also we commonly want to set <code>max-age</code> which is how long the cache will store the object before invalidating the cache. The <code>max-age</code> value is set to seconds with a maximum possible value of <code>31536000</code>, which is one year. You can do this in your Rails application by setting</p><div class="interstitial code"> <pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">public_file_server</span><span class="p">.</span><span class="nf">headers</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"Cache-Control"</span> <span class="o">=></span> <span class="s2">"public, max-age=31536000"</span> <span class="p">}</span> </code></pre> <button class="clipboard-button" data-clipboard-text="config.public_file_server.headers = { "Cache-Control" => "public, max-age=31536000" } ">Copy</button> </div> <p>Now when your application serves an asset in production, the CDN will store the asset for up to a year. Since most CDNs also cache headers of the request, this <code>Cache-Control</code> will be passed along to all future browsers seeking this asset. The browser then knows that it can store this asset for a very long time before needing to re-request it.</p><p><strong>CDNs and URL-based Cache Invalidation</strong></p><p>Most CDNs will cache contents of an asset based on the complete URL. This means that a request to</p><div class="interstitial code"> <pre><code class="highlight plaintext">http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png </code></pre> <button class="clipboard-button" data-clipboard-text="http://mycdnsubdomain.fictional-cdn.com/assets/smile-123.png ">Copy</button> </div> <p>Will be a completely different cache from</p><div class="interstitial code"> <pre><code class="highlight plaintext">http://mycdnsubdomain.fictional-cdn.com/assets/smile.png </code></pre> <button class="clipboard-button" data-clipboard-text="http://mycdnsubdomain.fictional-cdn.com/assets/smile.png ">Copy</button> </div> <p>If you want to set far future <code>max-age</code> in your <code>Cache-Control</code> (and you do), then make sure when you change your assets that your cache is invalidated. For example when changing the smiley face in an image from yellow to blue, you want all visitors of your site to get the new blue face. When using a CDN with the Rails asset pipeline <code>config.assets.digest</code> is set to true by default so that each asset will have a different file name when it is changed. This way you don't have to ever manually invalidate any items in your cache. By using a different unique asset name instead, your users get the latest asset.</p><h2 id="sprockets-to-propshaft"><a class="anchorlink" href="#sprockets-to-propshaft" data-turbo="false"><span>4</span> Sprockets to Propshaft</a></h2><h3 id="evolution-of-asset-management-techniques"><a class="anchorlink" href="#evolution-of-asset-management-techniques" data-turbo="false"><span>4.1</span> Evolution of Asset Management Techniques</a></h3><p>Within the last few years, the evolution of the web has led to significant changes that have influenced how assets are managed in web applications. These include:</p> <ol> <li><strong>Browser Support</strong>: Modern browsers have improved support for new features and syntax, reducing the need for transpilation and polyfills.</li> <li><strong>HTTP/2</strong>: The introduction of HTTP/2 has made it easier to serve multiple files in parallel, reducing the need for bundling.</li> <li><strong>ES6+</strong>: Modern JavaScript syntax (ES6+) is supported by most modern browsers, reducing the need for transpilation.</li> </ol> <p>Therefore, the asset pipeline powered by Propshaft, no longer includes transpilation, bundling, or compression by default. However, fingerprinting still remains an integral part. You can read more about the evolution of asset management techniques and how they directed the change from Sprockets to Propshaft below.</p><h4 id="transpilation-❌"><a class="anchorlink" href="#transpilation-❌" data-turbo="false"><span>4.1.1</span> Transpilation ❌</a></h4><p>Transpilation involves converting code from one language or format to another.</p><p>For example, converting TypeScript to JavaScript.</p><div class="interstitial code"> <pre><code class="highlight typescript"><span class="kd">const</span> <span class="nx">greet</span> <span class="o">=</span> <span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">);</span> <span class="p">};</span> </code></pre> <button class="clipboard-button" data-clipboard-text="const greet = (name: string): void => { console.log(`Hello, ${name}!`); }; ">Copy</button> </div> <p>After transpilation, this code becomes:</p><div class="interstitial code"> <pre><code class="highlight javascript"><span class="kd">const</span> <span class="nx">greet</span> <span class="o">=</span> <span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">);</span> <span class="p">};</span> </code></pre> <button class="clipboard-button" data-clipboard-text="const greet = (name) => { console.log(`Hello, ${name}!`); }; ">Copy</button> </div> <p>In the past, pre-processors like <a href="https://sass-lang.com/">Sass</a> and <a href="https://lesscss.org/">Less</a> were essential for CSS features such as variables and nesting. Today, modern CSS supports these natively, reducing the need for transpilation.</p><h4 id="bundling-❌"><a class="anchorlink" href="#bundling-❌" data-turbo="false"><span>4.1.2</span> Bundling ❌</a></h4><p>Bundling combines multiple files into one to reduce the number of HTTP requests a browser needs to make to render a page.</p><p>For example, if your application has three JavaScript files:</p> <ul> <li>menu.js</li> <li>cart.js</li> <li>checkout.js</li> </ul> <p>Bundling will merge these into a single application.js file.</p><div class="interstitial code"> <pre><code class="highlight javascript"><span class="c1">// app/javascript/application.js</span> <span class="c1">// Contents of menu.js, cart.js, and checkout.js are combined here</span> </code></pre> <button class="clipboard-button" data-clipboard-text="// app/javascript/application.js // Contents of menu.js, cart.js, and checkout.js are combined here ">Copy</button> </div> <p>This was crucial with HTTP/1.1, which limited 6-8 simultaneous connections per domain. With HTTP/2, browsers fetch multiple files in parallel, making bundling less critical for modern applications.</p><h4 id="compression-❌"><a class="anchorlink" href="#compression-❌" data-turbo="false"><span>4.1.3</span> Compression ❌</a></h4><p>Compression encodes files in a more efficient format to reduce their size further when delivered to users. A common technique is <a href="https://en.wikipedia.org/wiki/Gzip">Gzip compression</a>.</p><p>For example, a CSS file that's 200KB may compress to just 50KB when Gzipped. Browsers automatically decompress such files upon receipt, saving bandwidth and improving speed.</p><p>However, with CDNs automatically compressing assets, the need for manual compression has decreased.</p><h3 id="sprockets-vs-propshaft"><a class="anchorlink" href="#sprockets-vs-propshaft" data-turbo="false"><span>4.2</span> Sprockets vs. Propshaft</a></h3><h4 id="load-order"><a class="anchorlink" href="#load-order" data-turbo="false"><span>4.2.1</span> Load Order</a></h4><p>In Sprockets, you could link files together to ensure they loaded in the correct order. For example, a main JavaScript file that depended on other files would automatically have its dependencies managed by Sprockets, ensuring everything loaded in the right sequence. Propshaft, on the other hand, does not automatically handle these dependencies, and instead <a href="#asset-load-order">lets you manage the asset load order manually</a>.</p><h4 id="versioning"><a class="anchorlink" href="#versioning" data-turbo="false"><span>4.2.2</span> Versioning</a></h4><p>Sprockets simplifies asset fingerprinting by appending a hash to filenames whenever assets are updated, ensuring proper cache invalidation. With Propshaft, you’ll need to handle certain aspects manually. For example, while asset fingerprinting works, you might need to use a bundler or trigger transformations manually for JavaScript files to ensure filenames are updated correctly. Read more about <a href="#fingerprinting-versioning-with-digest-based-urls">fingerprinting in Propshaft</a>.</p><h4 id="precompilation"><a class="anchorlink" href="#precompilation" data-turbo="false"><span>4.2.3</span> Precompilation</a></h4><p>Sprockets processed assets that were explicitly included in a bundle. In contrast, Propshaft automatically processes all assets located in the specified paths, including images, stylesheets, JavaScript files, and more, without requiring explicit bundling. Read more about <a href="#asset-digesting">asset digesting</a>.</p><h3 id="migration-steps"><a class="anchorlink" href="#migration-steps" data-turbo="false"><span>4.3</span> Migration Steps</a></h3><p>Propshaft is intentionally simpler than <a href="https://github.com/rails/sprockets-rails">Sprockets</a>, which may make migrating from Sprockets a fair amount of work. This is especially true if you rely on Sprockets for tasks like transpiling <a href="https://www.typescriptlang.org/">TypeScript</a> or <a href="https://sass-lang.com/">Sass</a>, or if you're using gems that provide this functionality. In such cases, you'll either need to stop transpiling or switch to a Node.js-based transpiler, such as those provided by <a href="https://github.com/rails/jsbundling-rails"><code>jsbundling-rails</code></a> or <a href="https://github.com/rails/cssbundling-rails"><code>cssbundling-rails</code></a>. Read more about these in the <a href="#advanced-asset-management">Advanced Asset Management section</a>.</p><p>However, if you're already using a Node-based setup to bundle JavaScript and CSS, Propshaft should integrate smoothly into your workflow. Since you won’t need an additional tool for bundling or transpiling, Propshaft will primarily handle asset digesting and serving.</p><p>Some key steps in the migration include:</p> <ol> <li><p>Remove some gems using the following:</p><div class="interstitial code"> <pre><code class="highlight console"><span class="go">bundle remove sprockets</span> <span class="go">bundle remove sprockets-rails</span> <span class="go">bundle remove sass-rails</span> </code></pre> <button class="clipboard-button" data-clipboard-text="">Copy</button> </div></li> <li><p>Delete the <code>config/assets.rb</code> and <code>assets/config/manifest.js</code> files from your project.</p></li> <li><p>If you've already upgraded to Rails 8, then Propshaft is already included in your application. Otherwise, install it using <code>bundle add propshaft</code>.</p></li> <li><p>Remove the <code>config.assets.paths << Rails.root.join('app', 'assets')</code> line from your <code>application.rb</code> file.</p></li> <li><p>Migrate asset helpers by replacing all instances of asset helpers (e.g., <code>image_url</code>) with standard URLs because Propshaft utilizes relative paths. For example, <code>image_url("logo.png")</code> will become <code>url("/logo.png")</code>.</p></li> <li><p>If you're relying on Sprockets for transpiling, you'll need to switch to a Node-based transpiler like Webpack, esbuild, or Vite. You can use the <code>jsbundling-rails</code> and <code>cssbundling-rails</code> gems to integrate these tools into your Rails application.</p></li> </ol> <p>For more information, you can read the <a href="https://github.com/rails/propshaft/blob/main/UPGRADING.md">detailed guide on how to migrate from Sprockets to Propshaft</a>.</p><h2 id="advanced-asset-management"><a class="anchorlink" href="#advanced-asset-management" data-turbo="false"><span>5</span> Advanced Asset Management</a></h2><p>Over the years, there have been multiple default approaches for handling assets, and as the web evolved, we began to see more JavaScript-heavy applications. In The Rails Doctrine we believe that <a href="https://rubyonrails.org/doctrine#omakase">The Menu Is Omakase</a>, so Propshaft focuses on delivering a production-ready setup with modern browsers by default.</p><p>There is no one-size-fits-all solution for the various JavaScript and CSS frameworks and extensions available. However, there are other bundling libraries in the Rails ecosystem that should empower you in cases where the default setup isn't enough.</p><h3 id="jsbundling-rails"><a class="anchorlink" href="#jsbundling-rails" data-turbo="false"><span>5.1</span> <code>jsbundling-rails</code></a></h3><p><a href="https://github.com/rails/jsbundling-rails"><code>jsbundling-rails</code></a> is a gem that integrates modern JavaScript bundlers into your Rails application. It allows you to manage and bundle JavaScript assets with tools like <a href="https://bun.sh">Bun</a>, <a href="https://esbuild.github.io/">esbuild</a>, <a href="https://rollupjs.org/">rollup.js</a>, or <a href="https://webpack.js.org/">Webpack</a>, offering a runtime-dependent approach for developers seeking flexibility and performance.</p><h4 id="how-jsbundling-rails-works"><a class="anchorlink" href="#how-jsbundling-rails-works" data-turbo="false"><span>5.1.1</span> How <code>jsbundling-rails</code> Works</a></h4> <ol> <li>After installation, it sets up your Rails app to use your chosen JavaScript bundler.</li> <li>It creates a <code>build</code> script in your <code>package.json</code> file to compile your JavaScript assets.</li> <li>During development, the <code>build:watch</code> script ensures live updates to your assets as you make changes.</li> <li>In production, the gem ensures that JavaScript is built and included during the precompilation step, reducing manual intervention. It hooks into Rails' <code>assets:precompile</code> task to build JavaScript for all entry points during deployment. This integration ensures that your JavaScript is production-ready with minimal configuration.</li> </ol> <p>The gem automatically handles entry-point discovery - identifying the primary JavaScript files to bundle by following Rails conventions, typically looking in directories like <code>app/javascript/</code> and configuration. By adhering to Rails conventions, <code>jsbundling-rails</code> simplifies the process of integrating complex JavaScript workflows into Rails projects.</p><h4 id="jsbundling-rails-when-should-you-use-it-questionmark"><a class="anchorlink" href="#jsbundling-rails-when-should-you-use-it-questionmark" data-turbo="false"><span>5.1.2</span> When Should You Use It?</a></h4><p><code>jsbundling-rails</code> is ideal for Rails applications that:</p> <ul> <li>Require modern JavaScript features like ES6+, TypeScript, or JSX.</li> <li>Need to leverage bundler-specific optimizations like tree-shaking, code splitting, or minification.</li> <li>Use <code>Propshaft</code> for asset management and need a reliable way to integrate precompiled JavaScript with the broader Rails asset pipeline.</li> <li>Utilize libraries or frameworks that depend on a build step. For example, projects requiring transpilation—such as those using <a href="https://babeljs.io/">Babel</a>, <a href="https://www.typescriptlang.org/">TypeScript</a>, or React JSX—benefit greatly from <code>jsbundling-rails</code>. These tools rely on a build step, which the gem seamlessly supports.</li> </ul> <p>By integrating with Rails tools like <code>Propshaft</code> and simplifying JavaScript workflows, <code>jsbundling-rails</code> allows you to build rich, dynamic front-ends while staying productive and adhering to Rails conventions.</p><h3 id="cssbundling-rails"><a class="anchorlink" href="#cssbundling-rails" data-turbo="false"><span>5.2</span> <code>cssbundling-rails</code></a></h3><p><a href="https://github.com/rails/cssbundling-rails"><code>cssbundling-rails</code></a> integrates modern CSS frameworks and tools into your Rails application. It allows you to bundle and process your stylesheets. Once processed, the resulting CSS is delivered via the Rails asset pipeline.</p><h4 id="how-cssbundling-rails-works"><a class="anchorlink" href="#how-cssbundling-rails-works" data-turbo="false"><span>5.2.1</span> How <code>cssbundling-rails</code> Works</a></h4> <ol> <li>After installation, it sets up your Rails app to use your chosen CSS framework or processor.</li> <li>It creates a <code>build:css</code> script in your <code>package.json</code> file to compile your stylesheets.</li> <li>During development, a <code>build:css --watch</code> task ensures live updates to your CSS as you make changes, providing a smooth and responsive workflow.</li> <li>In production, the gem ensures your stylesheets are compiled and ready for deployment. During the <code>assets:precompile</code> step, it installs all <code>package.json</code> dependencies via <code>bun</code>, <code>yarn</code>, <code>pnpm</code> or <code>npm</code> and runs the <code>build:css</code> task. to process your stylesheet entry points. The resulting CSS output is then digested by the asset pipeline and copied into the <code>public/assets</code> directory, just like other asset pipeline files.</li> </ol> <p>This integration simplifies the process of preparing production-ready styles while ensuring all your CSS is managed and processed efficiently.</p><h4 id="cssbundling-rails-when-should-you-use-it-questionmark"><a class="anchorlink" href="#cssbundling-rails-when-should-you-use-it-questionmark" data-turbo="false"><span>5.2.2</span> When Should You Use It?</a></h4><p><code>cssbundling-rails</code> is ideal for Rails applications that:</p> <ul> <li>Use CSS frameworks like <a href="https://tailwindcss.com/">Tailwind CSS</a>, <a href="https://getbootstrap.com/">Bootstrap</a>, or <a href="https://bulma.io/">Bulma</a> that require processing during development or deployment.</li> <li>Need advanced CSS capabilities such as custom preprocessing with <a href="https://postcss.org/">PostCSS</a> or <a href="https://sass-lang.com/">Dart Sass</a> plugins.</li> <li>Require seamless integration of processed CSS into the Rails asset pipeline.</li> <li>Benefit from live updates to stylesheets during development with minimal manual intervention.</li> </ul> <p><strong>NOTE</strong>: Unlike <a href="https://github.com/rails/dartsass-rails"><code>dartsass-rails</code></a> or <a href="https://github.com/rails/tailwindcss-rails"><code>tailwindcss-rails</code></a>, which use standalone versions of <a href="https://sass-lang.com/">Dart Sass</a> and <a href="https://tailwindcss.com/">Tailwind CSS</a>, <code>cssbundling-rails</code> introduces a Node.js dependency. This makes it a good choice for applications already relying on Node for JavaScript processing with gems like <code>jsbundling-rails</code>. However, if you're using <a href="https://github.com/rails/importmap-rails"><code>importmap-rails</code></a> for JavaScript and prefer to avoid Node.js, standalone alternatives like <a href="https://github.com/rails/dartsass-rails"><code>dartsass-rails</code></a> or <a href="https://github.com/rails/tailwindcss-rails"><code>tailwindcss-rails</code></a> offer a simpler setup.</p><p>By integrating modern CSS workflows, automating production builds, and leveraging the Rails asset pipeline, <code>cssbundling-rails</code> enables developers to efficiently manage and deliver dynamic styles.</p><h3 id="tailwindcss-rails"><a class="anchorlink" href="#tailwindcss-rails" data-turbo="false"><span>5.3</span> <code>tailwindcss-rails</code></a></h3><p><a href="https://github.com/rails/tailwindcss-rails"><code>tailwindcss-rails</code></a> is a wrapper gem that integrates <a href="https://tailwindcss.com/">Tailwind CSS</a> into your Rails application. By bundling Tailwind CSS with a <a href="https://tailwindcss.com/blog/standalone-cli">standalone executable</a>, it eliminates the need for Node.js or additional JavaScript dependencies. This makes it a lightweight and efficient solution for styling Rails applications.</p><h4 id="how-tailwindcss-rails-works"><a class="anchorlink" href="#how-tailwindcss-rails-works" data-turbo="false"><span>5.3.1</span> How <code>tailwindcss-rails</code> Works</a></h4> <ol> <li>When installed, by providing <code>--css tailwind</code> to the <code>rails new</code> command, the gem generates a <code>tailwind.config.js</code> file for customizing your Tailwind setup and a <code>stylesheets/application.tailwind.css</code> file for managing your CSS entry points.</li> <li>Instead of relying on Node.js, the gem uses a precompiled Tailwind CSS binary. This standalone approach allows you to process and compile CSS without adding a JavaScript runtime to your project.</li> <li>During development, changes to your Tailwind configuration or CSS files are automatically detected and processed. The gem rebuilds your stylesheets and provides a <code>watch</code> process to automatically generate Tailwind output in development.</li> <li>In production, the gem hooks into the <code>assets:precompile</code> task. It processes your Tailwind CSS files and generates optimized, production-ready stylesheets, which are then included in the asset pipeline. The output is fingerprinted and cached for efficient delivery.</li> </ol> <h4 id="tailwindcss-rails-when-should-you-use-it-questionmark"><a class="anchorlink" href="#tailwindcss-rails-when-should-you-use-it-questionmark" data-turbo="false"><span>5.3.2</span> When Should You Use It?</a></h4><p><code>tailwindcss-rails</code> is ideal for Rails applications that:</p> <ul> <li>Want to use <a href="https://tailwindcss.com/">Tailwind CSS</a> without introducing a Node.js dependency or JavaScript build tools.</li> <li>Require a minimal setup for managing utility-first CSS frameworks.</li> <li>Need to take advantage of Tailwind's powerful features like custom themes, variants, and plugins without complex configuration.</li> </ul> <p>The gem works seamlessly with Rails' asset pipeline tools, like Propshaft, ensuring that your CSS is preprocessed, digested, and efficiently served in production environments.</p><h3 id="importmap-rails"><a class="anchorlink" href="#importmap-rails" data-turbo="false"><span>5.4</span> <code>importmap-rails</code></a></h3><p><a href="https://github.com/rails/importmap-rails"><code>importmap-rails</code></a> enables a Node.js-free approach to managing JavaScript in Rails applications. It leverages modern browser support for <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">ES Modules</a> to load JavaScript directly in the browser without requiring bundling or transpilation. This approach aligns with Rails' commitment to simplicity and convention over configuration.</p><h4 id="how-importmap-rails-works"><a class="anchorlink" href="#how-importmap-rails-works" data-turbo="false"><span>5.4.1</span> How <code>importmap-rails</code> Works</a></h4> <ul> <li>After installation, <code>importmap-rails</code> configures your Rails app to use <code><script type="module"></code> tags to load JavaScript modules directly in the browser.</li> <li>JavaScript dependencies are managed using the <code>bin/importmap</code> command, which pins modules to URLs, typically hosted on CDNs like <a href="https://www.jsdelivr.com/">jsDelivr</a> that host pre-bundled, browser-ready versions of libraries. This eliminates the need for <code>node_modules</code> or a package manager.</li> <li>During development, there’s no bundling step, so updates to your JavaScript are instantly available, streamlining the workflow.</li> <li>In production, the gem integrates with Propshaft to serve JavaScript files as part of the asset pipeline. Propshaft ensures files are digested, cached, and production-ready. Dependencies are versioned, fingerprinted, and efficiently delivered without manual intervention.</li> </ul> <p><strong>NOTE</strong>: While Propshaft ensures proper asset handling, it does not handle JavaScript processing or transformations — <code>importmap-rails</code> assumes your JavaScript is already in a browser-compatible format. This is why it works best for projects that don't require transpiling or bundling.</p><p>By eliminating the need for a build step and Node.js, <code>importmap-rails</code> simplifies JavaScript management.</p><h4 id="importmap-rails-when-should-you-use-it-questionmark"><a class="anchorlink" href="#importmap-rails-when-should-you-use-it-questionmark" data-turbo="false"><span>5.4.2</span> When Should You Use It?</a></h4><p><code>importmap-rails</code> is ideal for Rails applications that:</p> <ul> <li>Do not require complex JavaScript features like transpiling or bundling.</li> <li>Use modern JavaScript without relying on tools like <a href="https://babeljs.io/">Babel</a>.</li> </ul> </section> <hr> <footer aria-labelledby="heading-feedback" role="region"> <h2 id="heading-feedback">Feedback</h2> <p> You're encouraged to help improve the quality of this guide. </p> <p> Please contribute if you see any typos or factual errors. To get started, you can read our <a href="https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">documentation contributions</a> section. </p> <p> You may also find incomplete content or stuff that is not up to date. Please do add any missing documentation for main. Make sure to check <a href="https://edgeguides.rubyonrails.org">Edge Guides</a> first to verify if the issues are already fixed or not on the main branch. Check the <a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails Guides Guidelines</a> for style and conventions. </p> <p> If for whatever reason you spot something to fix but cannot patch it yourself, please <a href="https://github.com/rails/rails/issues">open an issue</a>. </p> <p>And last but not least, any kind of discussion regarding Ruby on Rails documentation is very welcome on the <a href="https://discuss.rubyonrails.org/c/rubyonrails-docs">official Ruby on Rails Forum</a>. </p> </footer> </div> </article> </main> <hr class="hide" /> <footer id="complementary"> <div class="wrapper"> <p>This work is licensed under a <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International</a> License</p> <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> </div> </footer> <a href="#main-skip-link" class="back-to-top" data-turbo="false"><span class="visibly-hidden">Back to top</span></a> </body> </html>