Upgrading from Rails 6.x Webpacker to Rails 7 Importmaps
Tags: rails, ruby, tailwindcss
Published: 2022-01-07 18:13:57


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!](https://codeslaw.com/blog/10-jokes-only-programmers-will-find-funny-1110518#manager)!

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):

```Ruby
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

```ruby
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

```javascript
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:

```ruby
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:

```javascript
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.

```ruby
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](www.fork.dev) 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!!!