The go-to resource for upgrading Ruby, Rails, and your dependencies.

Migrating from Webpacker to Vite on Rails


In the mid-15th century, Johannes Gutenberg introduced the mechanical movable type printing press to Europe. Setting the type for a single page was a laborious process — each letter had to be placed by hand — but once the page was set, printing many copies was fast. However, if a typo was discovered after the press started rolling, the operator had to stop the machine, locate the exact metal character in the frame, replace it, and then resume the entire printing process.

This is, as it so happens, a great description of how traditional JavaScript bundlers like Webpack operate during development.

For years, Rails developers have relied on Webpacker to bridge the gap between the Ruby ecosystem and modern JavaScript. Webpacker encapsulated complex Webpack configurations, which was incredibly useful. However, as applications grow, Webpack processes the entire dependency graph during compilation. In a mature Rails application, this means that every time we save a file, we wait for the entire asset pipeline to recompile before we can see our changes in the browser.

Vite, though, takes a fundamentally different approach. Instead of bundling the entire application during development, Vite serves source code over native ES modules (ESM). It leverages ESBuild — a bundler written in Go — to pre-bundle dependencies. When a file is modified, Vite only needs to invalidate that specific module rather than rebuilding the entire bundle. This targeted approach enables Hot Module Replacement (HMR) and often reduces compile times from thirty seconds or more down to under a second.

In this article, we will migrate a Ruby on Rails application from Webpacker to Vite. We will cover migrating standard JavaScript and Sassy CSS (SCSS) assets. Of course, we won’t cover every possible edge case — migrating complex React or Vue setups with highly customized Webpack loaders is beyond the scope of this guide — but we will establish a solid foundation.

Prerequisites and Safety

Before we get into that, though, let’s discuss prerequisites and safety. We recommend verifying your environment meets the following requirements:

  • Ruby 2.7 or higher
  • Rails 6.0 or higher
  • Node.js 14 or higher
  • A working installation of either Yarn or npm

Additionally, before you use any of the commands in this guide, it’s wise to ensure the latest “known good” version of your application is committed to source control. Migrating asset pipelines touches many files, and having a clean working directory allows us to revert if something goes awry.

Comparing Frontend Options

When moving away from Webpacker, there are a few major approaches we could take in the Rails ecosystem.

The first is jsbundling-rails and cssbundling-rails, which are the current Rails defaults. These gems wrap tools like esbuild or Webpack, compiling assets into the standard asset pipeline.

The second is importmap-rails, which avoids Node.js entirely by relying on browser-native ES modules.

The third option is Vite, using the vite_rails gem.

Generally speaking, importmap-rails is excellent for simpler applications with minimal JavaScript dependencies. The jsbundling-rails approach, though, makes sense if you want to stick closely to the Rails defaults. Vite, however, offers the fastest development experience with its native Hot Module Replacement (HMR). Since we are focusing on reducing compile times and improving the developer experience, Vite is the approach we will be discussing here.

Executing the Migration

Migrating from Webpacker to Vite requires a systematic approach. We recommend following these steps to ensure a stable transition.

1. Installing the Vite Integration

First, let’s add the vite_rails gem to our application. This library provides the necessary helpers and configuration templates.

```bash $ bundle add vite_rails ```

After installing the gem, we need to initialize the Vite configuration:

```bash $ bundle exec vite install ```

You also may notice a few things after running this command. It generates a vite.config.ts file, adds an entry to our package.json, and creates the default entrypoints in app/frontend. Strictly speaking, Vite doesn’t require us to use app/frontend, but this is the convention established by the vite_rails gem.

2. Managing Processes with Foreman

Running a Vite-enabled Rails application requires running both the Puma server for the backend and the Vite development server for the frontend. We could open two terminal windows, but managing these processes concurrently is often more practical with a tool like Foreman.

Let’s create or update a Procfile.dev in the root of our project:

```yaml web: bin/rails server -p 3000 vite: bin/vite dev ```

We can start our development environment using:

```bash $ gem install foreman $ foreman start -f Procfile.dev ```

3. Updating Layout Helpers

Webpacker relies on the javascript_pack_tag and stylesheet_pack_tag to inject compiled assets into views. Vite uses a different set of helpers. We need to update our application layout file, typically located at app/views/layouts/application.html.erb.

Let’s remove the old Webpacker tags and replace them with the Vite equivalents:

```erb My Rails Application <%= csrf_meta_tags %> <%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

<%# Vite Helpers %>
<%= vite_client_tag %>
<%= vite_javascript_tag 'application' %>
<%= yield %> ```

One may wonder: why do we need two Vite tags? The answer is straightforward. The vite_client_tag enables Hot Module Replacement (HMR) during development. The vite_javascript_tag targets our primary entrypoint, which Vite created in app/frontend/entrypoints/application.js.

4. Migrating Assets and Configuration

As mentioned earlier, Vite uses the app/frontend directory as its default root. We will need to move our existing JavaScript, CSS, and image assets from app/javascript (the Webpacker default) into app/frontend.

While moving files, we might need to update relative import paths within our JavaScript or SCSS files to reflect the new directory structure. Furthermore, if you had custom configurations in config/webpack/environment.js, you must translate those into Vite plugins within vite.config.ts. This step is often the most complex part of the migration, as it requires mapping Webpack concepts to Rollup equivalents.

5. Removing Webpacker

Once we have verified that all assets are loading correctly via Vite and our test suite passes, we can safely remove Webpacker from our application.

  1. Remove the webpacker gem from the Gemfile.
  2. Delete the config/webpacker.yml configuration file.
  3. Remove the @rails/webpacker package from package.json.
  4. Run bundle install and yarn install (or npm install) to update the dependency trees.

Production Compilation

During development, Vite serves native ES modules to provide fast HMR. In production, though, Vite utilizes Rollup to bundle our assets. Rollup is highly optimized for generating efficient production builds.

The vite_rails integration hooks into the standard assets:precompile Rake task. This is significant, because it means our existing Continuous Integration and Continuous Deployment (CI/CD) pipelines will typically continue to function without modification. When we deploy, Rails will automatically invoke Vite to compile our assets alongside any traditional Sprockets assets we might still have.

Warnings and Limitations

Transitioning from Webpacker to Vite addresses a persistent bottleneck in Rails development, but it is not without its trade-offs.

First, Vite targets modern browsers that support native ESM during development. If you need to support legacy browsers in production, you will need to configure the @vitejs/plugin-legacy plugin, which adds complexity to the build step.

Second, Vite handles CSS differently than Webpack, often injecting it via JavaScript during development. This difference can occasionally lead to a flash of unstyled content (FOUC) in the development environment, though production builds extract CSS normally.

Finally, migrating highly custom Webpack loaders to Rollup plugins can be a laborious process.

Conclusion

By moving away from slow Webpack compilations and embracing ESBuild and HMR, we can drastically reduce wait times during development. While the migration requires structural changes to asset management and careful translation of custom configurations, the reduction in compile times — often from tens of seconds down to under a second — generally justifies the investment.

Sponsored by Durable Programming

Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.

Hire Durable Programming