102
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
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.
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.
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:[email protected]/dist/index.m.js"
pin "flatpickr", to: "https://ga.jspm.io/npm:[email protected]/dist/flatpickr.js"
pin "stimulus-autocomplete", to: "https://ga.jspm.io/npm:[email protected]/src/autocomplete.js"
pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/[email protected]/dist/stimulus.js"
I also had to add a link to my layout for flatpickr link rel="stylesheet" href="https://ga.jspm.io/npm:[email protected]/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
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
102