1. What is an Asset Pipeline?
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.
The Rails Asset Pipeline is managed by Propshaft. 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.
Propshaft focuses on essential asset management tasks and leaves more complex
tasks, such as JavaScript and CSS bundling and minification, to specialized
tools like jsbundling-rails and
cssbundling-rails, which can be
added separately to your application. Propshaft focuses on
fingerprinting and
emphasizes generating digest-based URLs for assets, allowing browsers to cache
them, thus minimizing the need for intricate compilation and bundling.
The Propshaft gem is enabled by default in
new applications. If, for some reason, you want to disable it during setup, you
can use the --skip-asset-pipeline option:
$ rails new app_name --skip-asset-pipeline
Before Rails 8, the asset pipeline was powered by Sprockets. You can read about the Sprockets Asset Pipeline in previous versions of the Rails Guides. You can also explore the evolution of asset management techniques to see how the Rails Asset Pipeline has evolved over time.
2. Propshaft Features
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.
2.1. Asset Load Order
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:
Manually include assets in the correct order:
In your HTML layout (usually
application.html.erbfor 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:<!-- 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>This is important if, for instance,
main.jsrelies onutilities.jsto be loaded first.Use Modules in JavaScript (ES6)
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
<script type="module">in your HTML:// main.js import { initUtilities } from "./utilities.js"; import { setupFeature } from "./feature.js"; initUtilities(); setupFeature();Then in your layout:
<script type="module" src="main.js"></script>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.
Combine Files when necessary
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
combined.jsfile that imports or copies code from other scripts. Then, just includecombined.jsin 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.Bundle your JavaScript or CSS using a bundler
If your project requires features like dependency chaining or CSS pre-processing, you may want to consider advanced asset management alongside Propshaft.
Tools like
jsbundling-railsintegrates Bun, esbuild, rollup.js, or Webpack into your Rails application, whilecssbundling-railscan be used to process stylesheets that use Tailwind CSS, Bootstrap, Bulma, PostCSS, or Dart Sass.These tools complement Propshaft by handling the complex processing, while Propshaft efficiently organizes and serves the final assets.
2.2. Asset Organization
Propshaft organizes assets within the app/assets directory, which includes
subdirectories like images, javascripts, and stylesheets. You can place
your JavaScript, CSS, image files, and other assets into these directories, and
Propshaft will manage them during the precompilation process.
You can also specify additional asset paths for Propshaft to search by modifying
config.assets.paths in your config/initializers/assets.rb file. For example:
# Add additional assets to the asset load path.
Rails.application.config.assets.paths << Emoji.images_path
Propshaft will make all assets from the configured paths available for serving.
During the precompilation process, Propshaft copies these assets into the
public/assets directory, ensuring they are ready for production use.
Assets can be referenced through their logical
paths using helpers like asset_path, image_tag,
javascript_include_tag, and other asset helper tags. After running
assets:precompile in production, these logical references are
automatically converted into their fingerprinted paths using the
.manifest.json file.
It is possible to exclude certain directories from this process, you can read more about it in the Fingerprinting section.
2.3. Fingerprinting: Versioning with digest-based URLs
In Rails, asset versioning uses fingerprinting to add unique identifiers to asset filenames.
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.
2.3.1. Asset Digesting
As mentioned in the Asset Organization section, in
Propshaft, all assets from the paths configured in config.assets.paths are
available for serving and will be copied into the public/assets directory.
When fingerprinted, an asset filename like styles.css is renamed to
styles-a1b2c3d4e5f6.css. This ensures that if styles.css is updated, the
filename changes as well, compelling the browser to download the latest version
instead of using a potentially outdated cached copy.
2.3.2. Manifest Files
In Propshaft, the .manifest.json 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 public/assets directory, the .manifest.json file
helps Rails resolve asset paths at runtime, allowing it to reference the correct
fingerprinted files.
The .manifest.json includes entries for main assets like application.js and
application.css as well as other files, such as images. Here's an example of
what the JSON might look like:
{
"application.css": "application-6d58c9e6e3b5d4a7c9a8e3.css",
"application.js": "application-2d4b9f6c5a7c8e2b8d9e6.js",
"logo.png": "logo-f3e8c9b2a6e5d4c8.png",
"favicon.ico": "favicon-d6c8e5a9f3b2c7.ico"
}
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.
2.3.3. Digested Assets in Views
You can reference digested assets in your views using standard Rails asset
helpers like asset_path, image_tag, javascript_include_tag,
stylesheet_link_tag and others.
For example, in your layout file, you can include a stylesheet like this:
<%= stylesheet_link_tag "application", media: "all" %>
If you're using the turbo-rails gem
(which is included by default in Rails), you can include the data-turbo-track
option. This causes Turbo to check if an asset has been updated and, if so,
reload it into the page:
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
You can access images in the app/assets/images directory like this:
<%= image_tag "rails.png" %>
When the asset pipeline is enabled, Propshaft will serve this file. If a file
exists at public/assets/rails.png, it will be served by the web server.
Alternatively, if you are using fingerprinted assets (e.g.,
rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png),
Propshaft will also serve these correctly. The fingerprint is automatically
applied during the precompilation process.
Images can be organized into subdirectories, and you can reference them by specifying the directory in the tag:
<%= image_tag "icons/rails.png" %>
Finally, you can reference an image in your CSS like:
background: url("/bg/pattern.svg");
Propshaft will automatically convert this to:
background: url("/assets/bg/pattern-2169cbef.svg");
If you're precompiling your assets (see the Production
section), 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 image_tag and other helpers with user-supplied data. This
ensures that the browser always fetches the correct version of the asset.
2.3.4. Digested Assets in JavaScript
In JavaScript, you need to manually trigger the asset transformation using the
RAILS_ASSET_URL macro. Here’s an example:
export default class extends Controller {
init() {
this.img = RAILS_ASSET_URL("/icons/trash.svg");
}
}
This will transform into:
export default class extends Controller {
init() {
this.img = "/assets/icons/trash-54g9cbef.svg";
}
}
This ensures that the correct, digested file is used in your JavaScript code.
If you’re using bundlers like Webpack or
esbuild, you should let the bundlers handle the
digesting process. If Propshaft detects that a file already has a digest in the
filename (e.g., script-2169cbef.js), it will skip digesting the file again to
avoid unnecessary reprocessing.
For managing assets with Import Maps, Propshaft ensures that assets referenced in the import map are appropriately handled and mapped to their digested paths during the precompilation process.
2.3.5. Bypassing the Digest Step
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
-[digest].digested.js as files that have already been digested and will
preserve their stable file names.
2.3.6. Excluding Directories from Digestion
You can exclude certain directories from the precompilation and digestion
process by adding them to config.assets.excluded_paths. This is useful if, for
example, you’re using app/assets/stylesheets as input to a compiler like Dart
Sass, and you don’t want these files to be part of the
asset load path.
config.assets.excluded_paths = [Rails.root.join("app/assets/stylesheets")]
This will prevent the specified directories from being processed by Propshaft while still allowing them to be part of the precompilation process.
3. Working with Propshaft
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.
3.1. Setup
Follow these steps for setting up Propshaft in your Rails application:
Create a new Rails application:
$ rails new app_nameOrganize your assets:
Propshaft expects your assets to be in the
app/assetsdirectory. You can organize your assets into subdirectories likeapp/assets/javascriptsfor JavaScript files,app/assets/stylesheetsfor CSS files, andapp/assets/imagesfor images.For example, you can create a new JavaScript file in
app/assets/javascripts:// app/assets/javascripts/main.js console.log("Hello, world!");and a new CSS file in
app/assets/stylesheets:/* app/assets/stylesheets/main.css */ body { background-color: red; }Link assets in your application layout
In your application layout file (usually
app/views/layouts/application.html.erb), you can include your assets using thestylesheet_link_tagandjavascript_include_taghelpers:<!-- 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>This layout includes the
main.cssstylesheet andmain.jsJavaScript file in your application.Start the Rails server:
$ bin/rails serverPreview your application:
Open your web browser and navigate to
http://localhost:3000. You should see your Rails application with the included assets.
3.2. Development
Rails and Propshaft are configured differently in development than in production, to allow rapid iteration without manual intervention.
3.2.1. No Caching
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.
3.2.2. Automatic Reloading of Assets
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.
When using JavaScript bundlers such as esbuild or Webpack alongside Propshaft, the workflow combines both tools effectively:
- 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.
- Propshaft ensures that the latest compiled assets are served to the browser whenever a request is made.
For these setups, running ./bin/dev starts both the Rails server and the asset
bundler's development server.
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.
3.2.3. Improving Performance with File Watchers
In development, Propshaft checks if any assets have been updated before each
request, using the application's file watcher (by default,
ActiveSupport::FileUpdateChecker). If you have a large number of assets, you
can improve performance by using the listen gem and configuring the following
setting in config/environments/development.rb:
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
This will reduce the overhead of checking for file updates and improve performance during development.
3.3. Production
In production, Rails serves assets with caching enabled to optimize performance, ensuring that your application can handle high traffic efficiently.
3.3.1. Asset Caching and Versioning in Production
As mentioned in the Fingerprinting section 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.
3.3.2. Precompiling Assets
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 url(asset)
function calls in CSS to url(digested-asset) instead and source mapping
comments likewise.
To manually run precompilation you can use the following command:
$ RAILS_ENV=production rails assets:precompile
After doing this, all assets in the load path will be copied (or compiled when using advanced asset management) in the precompilation step and stamped with a digest hash.
Additionally, you can set ENV["SECRET_KEY_BASE_DUMMY"] to trigger the use of a
randomly generated secret_key_base 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.
$ RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1