Upgrading from Rails 6.x Webpacker to Rails 7 Importmaps

I've spend a good part of the last few months trying to figure out how I was going to convert my three semi-private Rails 6.1.x apps to Rails 7. I created a bunch of Rails 7 test apps using ESbuild, Importmaps and even Webpacker. While ESbuild may of been an eaiser path (basically node modeles without webpacker stuff), I leaned towards Importmaps. Other than a few node package I used, almost all of my JS was using Stimulus in some form.
So I marched on with my journey to Rails 7. Along the way I did do a lot of refactoring. I've been moving slowly from W3css to Tailwind.css. I already had the mixed css with some of my own scss. I don't think I'll remember all the steps I took, but I try to decribe the steps I took in some kind of order. It may not be the best way, but I did it my way and it worked
Rail7 Branch
I created and checked out a new rails7 branch. Fortunally I didn't have to make any changes to my deployed master, but merging branches was always in the back of my mind.
I then did bin/rails app:update and it did its thing. I hate to admint this, but I have No tests. I've tried many times to get into the test mode, but failed! So I continued to test how I've tested for the last 40 years! Change something then push the car back up the hill and see if the brakes fail again!!
I can't remember if development worked after the update, but I think it did. The gem 'sass-rails' was still in the Gemfile
so I think it worked.
I did have a problem with the update adding ActiveStorage migrations. Apperently I never installed ActiveStorge and really didn't want to. I set config.active_record.migration_error = false to avoid the error and later on changed require "rails/all" to the documentation way to not require ActiveStorage.
Onward to the next step, Rip out Webpacker and convert Gemfile to a fresh Rails 7 Importmaps version.
Rip out Webpacker.
Seemed scary but pretty simple. I removed:
  • app/javascript/application.css
  • app/javascript/channels/consumer.js
  • app/javascript/channels/index.js
  • app/javascript/packs/application.js
  • babel.config.js
  • bin/spring
  • bin/webpack
  • bin/webpack-dev-server
  • bin/yarn
  • config/webpack/development.js
  • config/webpack/environment.js
  • config/webpack/production.js
  • config/webpack/test.js
  • config/webpacker.yml
  • package-lock.json
  • postcss.config.js
  • yarn.lock
  • I then basically merged my orginal Gemfile with the Test App Gemfile. The main stuff being (without my specifics):
    gem "rails", "~> 7.0.0"
    
    # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
    gem "sprockets-rails"
    
    # Use postgresql as the database for Active Record
    gem "pg", "~> 1.1"
    
    # Use the Puma web server [https://github.com/puma/puma]
    gem "puma", "~> 5.0"
    
    # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
    gem "importmap-rails"
    
    # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
    gem "turbo-rails"
    
    # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
    gem "stimulus-rails"
    
    # Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
    gem "tailwindcss-rails"
    
    # Build JSON APIs with ease [https://github.com/rails/jbuilder]
    gem "jbuilder"
    
    gem "redis", "~> 4.0"
    I think at this point development worked, but maybe a few errors/problems with css.
    SASS CSS, etc
    I had a bunch of crap scss. Some problably hadn't been used in years and never found a good way of identifing what wasn't used. I knew the main things I used were colors, buttons, tables and a few almost components. I converted that scss to css with one of the many scss_to_css sites found on the web and added that css to my branch.
    Stimulus Controller needed to be converted to the @hotware version. import { Controller } from "@hotwired/stimulus"
    I added to config/importmap.rb my unique CDNs
    pin "application", preload: true
    pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
    pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
    pin_all_from "app/javascript/controllers", under: "controllers"
    pin "stimulus-flatpickr", to: "https://ga.jspm.io/npm:stimulus-flatpickr@3.0.0-0/dist/index.m.js"
    pin "flatpickr", to: "https://ga.jspm.io/npm:flatpickr@4.6.9/dist/flatpickr.js"
    pin "stimulus-autocomplete", to: "https://ga.jspm.io/npm:stimulus-autocomplete@3.0.0-rc.5/src/autocomplete.js"
    pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/stimulus@3.0.1/dist/stimulus.js"
    I also had to add a link to my layout for flatpickr link rel="stylesheet" href="https://ga.jspm.io/npm:flatpickr@4.6.9/dist/flatpickr.min.css"
    CSS bundling starting working with some problems. Many of them related to the config/tailwind.js file.
    I merged my old tailwing config file with the new verssion
    const defaultTheme = require('tailwindcss/defaultTheme')
    
    module.exports = {
      content: [
        './app/helpers/**/*.rb',
        './app/javascript/**/*.js',
        './app/views/**/*.html.*',
        './app/components/**/*.html.*'
    
      ],
      theme: {
        extend: {
          fontFamily: {
            sans: ['Inter var', ...defaultTheme.fontFamily.sans],
          },
          colors: {
            'orange': '#ffa500',
            'malt': '#991A1E',
            'gold': '#A79055',
            'dark-blue': '#0F3E61',
            'success': '#63ed7a',
            'secondary': "#9db3b8",
            'w3green': "#4CAF50",
            'w3red': "#f44336",
            'blue-link': "#00c",
            lime: {
              lightest: '#f1fff1',
              lighter: '#e2ffe2',
              light: '#c9ffc9',
              DEFAULT: '#b8ffb8',
              dark: '#96ff96',
              darker: '#7cff7c',
              darkest: '#49ff49',
            },
            // gray: colors.trueGray,
    
            green: {
              lighter:'hsla(122, 59%, 64%, 1)',
              light: 'hsla(122, 49%, 54%, 1)',
              DEFAULT: 'hsla(122, 39%, 49%, 1)',
              dark: 'hsla(122, 39%, 39%, 1)',
              darker: 'hsla(122, 39%, 29%, 1)',
            },
          },
        },
      },
      plugins: [
        require('@tailwindcss/forms'),
        require('@tailwindcss/aspect-ratio'),
        require('@tailwindcss/typography'),
      ]
    }
    I have a few Web Components and needed to add that folder to the content: array
    I also had some tailwind webpack components the used @apply and converted them to rails helpers. Something like:
    module ComponentsHelper
      def greenBox
        "box-border box-content m-3 p-4 bg-green-300 border-green-100 border-2 text-black"
      end
    
      def blueBox
        "box-border box-content m-3 p-4 bg-blue-400 border-blue-200 border-2 text-black"
      end
    
      def btn
        "py-1 px-2 text-black hover:text-white rounded font-lg font-bold "
      end
    
      def btnInfo
        btn + "bg-blue-400 text-blue-link hover:text-blue-100"
      end
    
      def btnWarn
        btn + "bg-orange hover:text-yellow-200"
      end
    
      def btnGreen
        btn + "bg-green-500 hover:text-green-100"
      end
    
      def btnDanger
        btn + "bg-red-500 hover:text-red-200"
      end
    
      def btnSuccess
        btn + "bg-success hover:bg-green-700"
      end
    
      def btnSecondary
        btn + "bg-secondary"
      end
    
      def flashAlert(type)
        case type
        when 'danger'
          return "bg-red-200 text-red-600"
        when 'info'
          return "bg-blue-200 text-blue-600"
        when 'success'
          return "bg-green-200 text-green-600"
        when 'warning'
          return "bg-yellow-400 text-yellow-800"
        else
          return "bg-gray-200 text-gray-600"
        end
      end
    
      def destroyConfirmTag(model,confirm_msg:"",klass:"",prompt:"")
        klass= "#{btnDanger} inline-block" if klass.blank?
        confirm_msg = "Are You Sure?" if confirm_msg.blank?
        prompt = "Delete #{model.class.name}" if prompt.blank?
        node = content_tag(:div, class: klass,
          data:{
            controller:"destroyConfirm", 
            action:"click->destroyConfirm#confirm",
            destroyConfirm_cmsg_value:confirm_msg
          }){
          concat(tag.span(prompt))
          concat(button_to( '',model, method: :delete,class:"hidden",data:{destroyConfirm_target:"submit"}))
        }
        node 
      end
    
    end
    The last helper destroyConfirmTag tackled the problem with Rails removing the method::get from delete scaffold. The default change is to change link_to to button_to. But it didn't have a confirm tag. So I just call this on delete links and add back the confirm message. I have a couple delete links that I need to sure they didn't click the wrong link. There is a simple stimulus controller:
    import { Controller } from "@hotwired/stimulus"
    
    export default class extends Controller {
      static targets = [ "submit"]
      static values = { cmsg: String}
    
      connect() {
        // console.log("destroy confirm")
        if (this.hasCmsgValue) {
          this.confirm_msg = this.cmsgValue
        }else{
          this.confirm_msg  = "Are you sure?"
        }
      }
    
      confirm(){
        // console.log(this.submitTarget.closest('form'))
        let ans = confirm(`${this.confirm_msg}`)
        if (ans == true) {
          this.submitTarget.closest('form').submit()
        }
      }
    
    }
    I even toyed with simple Ruby class to generate css from tailwind config file - never finished or used it though. Think I saw some post the the rails_tailwind gem site bitching about colors and components.
    class TwColors
    
      attr_accessor :colors, :css
    
      Colors = {:colors=>                                                  
      {:orange=>"#ffa500",                                      
       :malt=>"#991A1E",                                        
       :gold=>"#A79055",                                        
       :"dark-blue"=>"#0F3E61",                                 
       :lime=>                                                  
        {:lightest=>"#f1fff1",                                  
         :lighter=>"#e2ffe2",                                   
         :light=>"#c9ffc9",                                     
         :DEFAULT=>"#b8ffb8",                                   
         :dark=>"#96ff96",                                      
         :darker=>"#7cff7c",                                    
         :darkest=>"#49ff49"},
       :green=>
        {:lighter=>"hsla(122, 59%, 64%, 1)",
         :light=>"hsla(122, 49%, 54%, 1)",
         :DEFAULT=>"hsla(122, 39%, 49%, 1)",
         :dark=>"hsla(122, 39%, 39%, 1)",
         :darker=>"hsla(122, 39%, 29%, 1)"}}}
    
      def initialize()
        @colors = Colors
        @css = "not parsed yet \n"
        @colors[:colors].each do |k,v|
          unless v.is_a?(Hash)
            add_color_classes(k,v)
          else
            add_nested_color_classes(k,v)
          end
        end
        puts @css
      end
    
      def add_color_classes(k,v)
        @css << ".text-#{k} {\n\tcolor: #{v};\n}"
        @css << ".bg-#{k} {\n\t background-color: #{v};\n}\n"
      end
    
      def add_nested_color_classes(k,v)
        v.each do |kk,vv|
          if kk.to_s == "DEFAULT"
            add_color_classes("#{k}",vv)
          else
            add_color_classes("#{k}-#{kk}",vv)
          end
        end
      end
    
    end
    Merging rails7 branch with master
    One of my major fears was -> What if I merged and Capistrono would not deploy. I spent way too much time trying to figure out how to deploy a branch to my staging server. I found posts on how to do it (old) and nothing worked.
    I then just faked it and ran staging locally.
  • set staging DB to point to develoment
  • bin/rails assets:precompile
  • bin/rails s -e staging
  • This was where I found I had the bundling issues with css classes in components not being picked up. After a little tuning things were working fine.
    Had to remember to bin/rails assets:clobber when I moved back to development.
    Everything seemed to be working fine, especially after a bunch of refactoring. It was time to merge. Made sure I had a backup clone of my repo and did a merge after I figured out how to do it (I've been using the OSX version of Fork and am not proficient with git or Fork.
    Devepment worked fine, as expected.
    cap staging deploy also worked without any changes to the Capistrono config file.
    Played with it with a few more refactoring tweeks and the deployed to production!
    and I'm fine and
    I'm on Rails 7 without any Webpacker crap!!!

    119

    This website collects cookies to deliver better user experience

    Upgrading from Rails 6.x Webpacker to Rails 7 Importmaps