Skip to content

Instantly share code, notes, and snippets.

@davidteren
Created May 8, 2025 00:16
Show Gist options
  • Save davidteren/d8ba11681054048745524aefb2f653ac to your computer and use it in GitHub Desktop.
Save davidteren/d8ba11681054048745524aefb2f653ac to your computer and use it in GitHub Desktop.
Webpacker to Vite Migration: A Phased Approach

Webpacker to Vite Migration: A Phased Approach

!NOTE: this guide currently only covers the first phase. The Vite setup and migration are not detailed here.

Overview

This guide documents our approach to migrating from Webpacker to a modern frontend asset pipeline. We implemented a phased strategy:

  1. Phase 1 (Current): Replace Webpacker with CDN-based resources
  2. Phase 2 (Future): Migrate to Vite for a modern build system

Why Replace Webpacker?

Webpacker presented several challenges:

  • No longer actively maintained
  • Slow build process
  • Added complexity to the application
  • Difficult to upgrade

Implementation Approach

Architecture

Our CDN implementation uses a parallel layout approach that allows for:

  • Side-by-side testing of Webpacker and CDN implementations
  • Gradual migration of controllers to the CDN layout
  • Easy rollback if issues are encountered

Key Components

1. Controller Concern

# app/controllers/concerns/cdn_layout.rb
module CdnLayout
  extend ActiveSupport::Concern
  
  included do
    layout 'application_cdn'
  end
end

This concern can be included in any controller to switch it to the CDN layout:

class ApplicationController < ActionController::Base
  # Uncomment to use the CDN-based layout
  # include CdnLayout
  
  # ...
end

2. CDN Layout File

<!-- app/views/layouts/application_cdn.html.erb (simplified) -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Meta tags and Rails-specific tags -->
    
    <!-- Fomantic UI CDN -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css">
    
    <!-- DataTables with Fomantic UI styling -->
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.semanticui.min.css">
    <link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.semanticui.min.css">
    
    <!-- Application CSS -->
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    
    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    
    <!-- Fomantic UI JS -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.js"></script>
    
    <!-- DataTables JS -->
    <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.6/js/dataTables.semanticui.min.js"></script>
    <script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
    
    <!-- Turbolinks -->
    <%= javascript_include_tag 'turbolinks', 'data-turbolinks-track': 'reload' %>
    
    <!-- Application JS -->
    <%= javascript_include_tag 'application_cdn', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <!-- Layout content -->
  </body>
</html>

3. JavaScript Initialization

// app/assets/javascripts/application_cdn.js
document.addEventListener("turbolinks:load", function() {
  // Initialize Fomantic UI components
  $('.ui.dropdown').dropdown();
  $('.ui.accordion').accordion();
  $('.ui.checkbox').checkbox();
  $('.ui.modal').modal();
  $('.ui.popup').popup();
  $('.ui.rating').rating();
  $('.ui.sidebar').sidebar();
  $('.ui.sticky').sticky();
  $('.ui.tab').tab();
  $('.ui.toast').toast();
  
  // Initialize DataTables if present
  if ($.fn.DataTable && $('#data_table').length) {
    $('#data_table').DataTable({
      responsive: true,
      dom: 'lfrtip',
      lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
      pageLength: 10,
      order: [[0, 'desc']],
      language: {
        search: "_INPUT_",
        searchPlaceholder: "Search records...",
        lengthMenu: "Show _MENU_ entries",
        info: "Showing _START_ to _END_ of _TOTAL_ entries",
        infoEmpty: "Showing 0 to 0 of 0 entries",
        paginate: {
          first: '<i class="angle double left icon"></i>',
          previous: '<i class="angle left icon"></i>',
          next: '<i class="angle right icon"></i>',
          last: '<i class="angle double right icon"></i>'
        }
      },
      columnDefs: [
        { className: "center aligned", targets: [0, 3] }
      ],
      drawCallback: function() {
        $('.paginate_button').addClass('ui button');
        $('.paginate_button.current').addClass('primary');
        $('.dataTables_length select').addClass('ui dropdown');
        $('.dataTables_filter input').addClass('ui input');
      }
    });
  }
});

// Turbolinks integration - prevent memory leaks
document.addEventListener('turbolinks:before-cache', function() {
  if ($.fn.DataTable && $.fn.DataTable.isDataTable('#data_table')) {
    $('#data_table').DataTable().destroy();
  }
});

4. Test Controller and Route

# app/controllers/cdn_test_controller.rb
class CdnTestController < ApplicationController
  include CdnLayout
  
  def index
    # This controller uses the CDN layout
  end
end

# config/routes.rb
Rails.application.routes.draw do
  # CDN Test route
  get 'cdn_test', to: 'cdn_test#index'
  
  # Other routes...
end

5. CSS Enhancements

/* DataTables Styling */
.dataTables_wrapper {
  padding: 0.5em 0;
}

.dataTables_wrapper .dataTables_paginate .paginate_button {
  padding: 0.5em 1em;
  margin-left: 2px;
  border: 1px solid transparent;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.current {
  background: #2185d0;
  color: white !important;
}

/* Status Indicators */
.center.aligned i.icon.green,
.center.aligned i.icon.red {
  font-size: 1.2em;
}

/* Responsive Adjustments */
@media only screen and (max-width: 767px) {
  .ui.container {
    width: auto !important;
    margin-left: 1em !important;
    margin-right: 1em !important;
  }
}

Implementation Process

  1. Create Controller Concern: Implemented a reusable controller concern
  2. Create CDN Layout: Developed a new layout file with CDN resources
  3. Add JavaScript Initialization: Created dedicated JS for component initialization
  4. Create Test Controller: Added a controller and view for testing
  5. Enhance CSS: Added CSS improvements for better styling
  6. Test Implementation: Verified all components work correctly

Using the CDN Implementation

Testing the CDN Approach

  1. Visit /cdn_test to see a page using the CDN-based layout
  2. Compare with regular pages to ensure everything looks correct

Switching to CDN

To switch the entire application to use the CDN-based layout:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Uncomment to use the CDN-based layout
  include CdnLayout
  
  # ...
end

Reverting if Needed

If you encounter issues with the CDN approach:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Comment out to use the default Webpacker layout
  # include CdnLayout
  
  # ...
end

Testing Strategy

We implemented a comprehensive testing approach:

  1. Component Testing: Verify all Fomantic UI components render correctly
  2. DataTables Testing: Ensure tables initialize properly with Fomantic UI styling
  3. Event Handling: Test JavaScript event handlers work as expected
  4. Responsive Design: Verify responsive behavior across different device sizes
  5. Layout Switching: Test switching between standard and CDN layouts

Benefits of the CDN Approach

  1. Performance

    • Faster page loads (CDN assets are often cached by browsers)
    • Reduced server load for asset compilation
  2. Development

    • Simplified development environment
    • No need to run Webpacker in development
    • Faster feedback cycle
  3. Maintenance

    • Reduced dependency complexity
    • Access to the latest Fomantic UI features and fixes
    • Easier upgrades

Next Steps: Migrating to Vite

After successfully implementing the CDN approach:

  1. Remove Webpacker dependencies from the application
  2. Update the package.json file to remove unused packages
  3. Implement Vite for local asset management
  4. Gradually migrate CDN resources to locally managed assets

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment