Skip to content

Commit

Permalink
Support Rails 7 and Ruby 3 (#1106)
Browse files Browse the repository at this point in the history
This is a major upgrade that aims to make it so generated Rails
applications with suspenders are running on Rails version 7 and Ruby
version 3. We took the approach of making the least amount of changes
possible.

We were unable to support the latest version of Ruby because of a
[failing build][]. A future commit could explore how to solve for this,
but for now, we decided to just stick with version `3.0.5`. We settled
on `3.0.5` and not `3.0.0` in an effort to suppress parser warnings.

The biggest change is that we drop support for deprecated CSS tooling by
removing [Bourbon][] and introduce [PostCSS][] and
[cssbundling-rails][]. We also drop [Bitters][] as a runtime dependency.

Add PostCSS Normalize
---------------------

In order to use [normalize][], we need to use [PostCSS Normalize][]
since we are using [cssbundling-rails][].

We need to move the invocation of the `suspenders:stylesheet_base`
to the `leftovers` method because this method is invoked _after_
`finish_template` is called. We do this because we were running into
issues where the call to `rails css:install:postcss` had not yet run,
but our generator assumes that command has already run. The method must
be named `leftovers` since that's what the [finish_template][] method
expects.

Additionally, we only call this generator if the caller has not
passed anything to the `--css` option when generating a new app. This is
because Rails handles other css frameworks like `bootstrap` and
`tailwind` differently.

Finally, we move the invocation of `outro` to the `leftovers` method
since it ensures the message will be the last thing to be printed to the
console.

Add bin/yarn
------------

Now that we have moved off of [Webpacker][], the [bin/yarn][] binstub is
no longer generated. Because of this, we need to manually have it added
during a new app build.

Introduce CalVer
----------------

Because this upgrade was a breaking change, we thought it was a good
time to switch to [CalVer][]. Since this project is composed of other
projects, every release is a potential breaking change. Using the date
as the `major` version encodes this risk.

Improve console output
----------------------

Ensures the invocation of all generator methods happens _before_ we
print the outro. This is because a generator invokes all methods in the
class in the order they are declared. Specifically, this meant invoking
`generate_views` in the `suspenders_customization` method. Prior to
this, `generate_views` was invoked after the `outro` was printed.

[Webpacker]: https://github.com/rails/webpacker
[failing build]: https://github.com/thoughtbot/suspenders/actions/runs/3418310707
[Bourbon]: https://github.com/thoughtbot/bourbon
[PostCSS]: https://postcss.org
[cssbundling-rails]: https://github.com/rails/cssbundling-rails
[Bitters]: https://github.com/thoughtbot/bitters
[normalize]: https://necolas.github.io/normalize.css/
[PostCSS Normalize]: https://github.com/csstools/postcss-normalize
[finish_template]: https://github.com/rails/rails/blob/d5124b2522130785378238ba714029ce8ef9de97/railties/lib/rails/generators/rails/plugin/plugin_generator.rb#L283-L285
[bin/yarn]: https://github.com/rails/webpacker/blob/master/lib/install/bin/yarn
[CalVer]: https://calver.org

Co-authored-by: Aji Slater <[email protected]>
Co-authored-by: Eric Milford <[email protected]>
Co-authored-by: Stefanni Brasil <[email protected]>
  • Loading branch information
4 people authored Jan 13, 2023
1 parent 5624ca5 commit 1712f3e
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.7.4
3.0.5
13 changes: 13 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
20230113.0 (January, 13, 2023)

Support Rails 7 and Ruby 3. Introduce CalVer.

* Upgraded: Ruby to 3.0.5
* Upgraded: Supported Rails version to 7.0.0
* Removed: Bourbon
* Removed: Bitters
* Removed: Autoprefixer Rails
* Added: cssbundling-rails
* Added: PostCSS Autoprefixer
* Added: PostCSS Normalize

1.56.1 (July 17, 2022)

Fixes a critical error with the previous release
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@ generated projectname/Gemfile.

It includes application gems like:

* [Autoprefixer Rails](https://github.com/ai/autoprefixer-rails) for CSS vendor prefixes
* [Bourbon](https://github.com/thoughtbot/bourbon) for Sass mixins
* [Bitters](https://github.com/thoughtbot/bitters) for scaffold application styles
* [Sidekiq](https://github.com/mperham/sidekiq) for background
processing
* [High Voltage](https://github.com/thoughtbot/high_voltage) for static pages
* [Honeybadger](https://www.honeybadger.io/?affiliate=A43uwl) for exception notification
* [Normalize](https://necolas.github.io/normalize.css/) for resetting browser styles
* [Oj](http://www.ohler.com/oj/)
* [Postgres](https://github.com/ged/ruby-pg) for access to the Postgres database
* [Rack Canonical Host](https://github.com/tylerhunt/rack-canonical-host) to
Expand Down Expand Up @@ -105,6 +101,8 @@ Suspenders also comes with:
* Configuration for [stylelint][stylelint]
* The analytics adapter [Segment][segment] (and therefore config for Google
Analytics, Intercom, Facebook Ads, Twitter Ads, etc.)
* [PostCSS Autoprefixer][autoprefixer] for CSS vendor prefixes
* [PostCSS Normalize][normalize] for resetting browser styles

[setup]: https://robots.thoughtbot.com/bin-setup
[compress]: https://robots.thoughtbot.com/content-compression-with-rack-deflater
Expand All @@ -115,6 +113,8 @@ Suspenders also comes with:
[hound]: https://houndci.com
[stylelint]: https://stylelint.io/
[segment]: https://segment.com
[autoprefixer]: https://github.com/postcss/autoprefixer
[normalize]: https://github.com/csstools/postcss-normalize

## Heroku

Expand Down
5 changes: 5 additions & 0 deletions docs/rails_7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Rails 7

To create a new Rails 7 app with Suspenders, you need to have `node >= 14.6`.

After the app is created, run the server with `bin/dev`.
11 changes: 8 additions & 3 deletions lib/suspenders/app_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ def provide_setup_script
run "chmod a+x bin/setup"
end

def provide_yarn_script
template "bin_yarn", "bin/yarn", force: true
run "chmod a+x bin/yarn"
end

def configure_generators
config = <<-RUBY
Expand All @@ -100,13 +105,13 @@ def configure_local_mail

def setup_asset_host
replace_in_file "config/environments/production.rb",
"# config.asset_host = 'http://assets.example.com'",
%(# config.asset_host = "http://assets.example.com"),
'config.asset_host = ENV.fetch("ASSET_HOST", ENV.fetch("APPLICATION_HOST"))'

if File.exist?("config/initializers/assets.rb")
replace_in_file "config/initializers/assets.rb",
"config.assets.version = '1.0'",
'config.assets.version = (ENV["ASSETS_VERSION"] || "1.0")'
%(Rails.application.config.assets.version = "1.0"),
'Rails.application.config.assets.version = (ENV["ASSETS_VERSION"] || "1.0")'
end

config = <<~EOD
Expand Down
18 changes: 10 additions & 8 deletions lib/suspenders/generators/app_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ class AppGenerator < Rails::Generators::AppGenerator
class_option :skip_system_test,
type: :boolean, default: true, desc: "Skip system test files"

class_option :skip_turbolinks,
type: :boolean, default: true, desc: "Skip turbolinks gem"

class_option :skip_spring, type: :boolean, default: true,
desc: class_options[:skip_spring].description
class_option :css,
type: :string, default: "postcss", aliases: "-c", desc: "Choose CSS processor"

def finish_template
invoke :suspenders_customization
super
end

def leftovers
generate("suspenders:stylesheet_base") unless options[:api] || options[:css] != "postcss"
invoke :outro
end

def suspenders_customization
invoke :customize_gemfile
invoke :setup_development_environment
Expand All @@ -57,7 +59,7 @@ def suspenders_customization
invoke :remove_config_comment_lines
invoke :remove_routes_comment_lines
invoke :run_database_migrations
invoke :outro
invoke :generate_views
end

def customize_gemfile
Expand All @@ -83,6 +85,7 @@ def setup_development_environment
build :set_test_delivery_method
build :raise_on_unpermitted_parameters
build :provide_setup_script
build :provide_yarn_script
build :configure_generators
build :configure_i18n_for_missing_translations
build :configure_quiet_assets
Expand Down Expand Up @@ -150,7 +153,6 @@ def generate_default
generate("suspenders:profiler")
generate("suspenders:json")
generate("suspenders:static")
generate("suspenders:stylesheet_base") unless options[:api]
generate("suspenders:testing")
generate("suspenders:ci")
generate("suspenders:js_driver")
Expand Down Expand Up @@ -180,7 +182,7 @@ def generate_views
end

def outro
say "Congratulations! You just pulled our suspenders."
say "\nCongratulations! You just pulled our suspenders."
say honeybadger_outro
end

Expand Down
25 changes: 5 additions & 20 deletions lib/suspenders/generators/stylesheet_base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,14 @@

module Suspenders
class StylesheetBaseGenerator < Generators::Base
def add_stylesheet_gems
gem "bourbon", ">= 6.0.0"
Bundler.with_unbundled_env { run "bundle install" }
end

def remove_prior_config
remove_file "app/assets/stylesheets/application.css"
end

def add_css_config
def setup_normalize
run "yarn add postcss-normalize"
copy_file(
"application.scss",
"app/assets/stylesheets/application.scss",
"application.postcss.css",
"app/assets/stylesheets/application.postcss.css",
force: true
)
end

def install_bitters
run "bitters install --path app/assets/stylesheets"
end

def install_normalize_css
run "bin/yarn add normalize.css"
copy_file "postcss.config.js", "postcss.config.js", force: true
end
end
end
5 changes: 0 additions & 5 deletions lib/suspenders/generators/views_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ def create_shared_flashes
copy_file "flashes_helper.rb", "app/helpers/flashes_helper.rb"
end

def create_shared_javascripts
copy_file "_javascript.html.erb",
"app/views/application/_javascript.html.erb"
end

def create_shared_css_overrides
copy_file "_css_overrides.html.erb",
"app/views/application/_css_overrides.html.erb"
Expand Down
4 changes: 2 additions & 2 deletions lib/suspenders/version.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module Suspenders
RAILS_VERSION = "~> 6.1.6.1".freeze
RAILS_VERSION = "~> 7.0.0".freeze
RUBY_VERSION = IO
.read("#{File.dirname(__FILE__)}/../../.ruby-version")
.strip
.freeze
VERSION = "1.56.1".freeze
VERSION = "20230113.0".freeze
end
67 changes: 47 additions & 20 deletions spec/features/new_project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
expect(gemfile_file).to match(
/^ruby "#{Suspenders::RUBY_VERSION}"$/o
)
expect(gemfile_file).to match(
/^gem "autoprefixer-rails"$/
)
expect(gemfile_file).to match(
/^gem "rails", "#{Suspenders::RAILS_VERSION}"$/o
)
Expand Down Expand Up @@ -66,6 +63,14 @@
expect("bin/setup").to be_executable
end

it "adds bin/yarn file" do
expect(File).to exist("#{project_path}/bin/yarn")
end

it "makes bin/setup executable" do
expect("bin/yarn").to be_executable
end

it "adds support file for action mailer" do
expect(File).to exist("#{project_path}/spec/support/action_mailer.rb")
end
Expand Down Expand Up @@ -255,10 +260,6 @@
expect(app_json_file).to match(/"name":\s*"#{app_name.dasherize}"/)
end

def app_name
TestPaths::APP_NAME
end

it "adds high_voltage" do
gemfile = IO.read("#{project_path}/Gemfile")
expect(gemfile).to match(/high_voltage/)
Expand All @@ -270,27 +271,53 @@ def app_name
expect(gemfile).to match(/sassc-rails/)
end

it "adds and configures bourbon" do
it "configures Timecop safe mode" do
spec_helper = read_project_file(%w[spec spec_helper.rb])
expect(spec_helper).to match(/Timecop.safe_mode = true/)
end

it "adds and configures a bundler strategy for css and js" do
gemfile = read_project_file("Gemfile")

expect(gemfile).to match(/bourbon/)
expect(gemfile).to match(/cssbundling-rails/)
expect(gemfile).to match(/jsbundling-rails/)
expect(File).to exist("#{project_path}/postcss.config.js")
expect(File).to exist("#{project_path}/package.json")
expect(File).to exist("#{project_path}/bin/dev")
expect(File).to exist("#{project_path}/app/assets/stylesheets/application.postcss.css")
expect(File).to exist("#{project_path}/app/javascript/application.js")
end

it "configures bourbon, and bitters" do
app_css = read_project_file(%w[app assets stylesheets application.scss])
expect(app_css).to match(
/normalize\.css\/normalize.*bourbon.*base/m
)
it "adds normalize.css" do
stylesheet = read_project_file %w[app assets stylesheets application.postcss.css]
dependencies = read_project_file %w[package.json]
configuration = read_project_file %w[postcss.config.js]

expect(stylesheet).to include(%(@import "normalize.css"))
expect(dependencies).to include(%("postcss-normalize"))
expect(configuration).to include(%(require('postcss-normalize')))
end

it "doesn't use turbolinks" do
app_js = read_project_file(%w[app javascript packs application.js])
expect(app_js).not_to match(/turbolinks/)
it "imports css and js" do
layout = read_project_file %w[app views layouts application.html.erb]

expect(layout)
.to include(%(<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>))
expect(layout)
.to include(%(<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>))
end

it "configures Timecop safe mode" do
spec_helper = read_project_file(%w[spec spec_helper.rb])
expect(spec_helper).to match(/Timecop.safe_mode = true/)
it "loads security helpers" do
layout = read_project_file %w[app views layouts application.html.erb]

expect(layout)
.to include(%(<%= csp_meta_tag %>))
expect(layout)
.to include(%(<%= csrf_meta_tags %>))
end

def app_name
TestPaths::APP_NAME
end

def development_config
Expand Down
4 changes: 0 additions & 4 deletions spec/suspenders/app_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,4 @@
it "includes ExitOnFailure" do
expect(described_class.ancestors).to include(Suspenders::ExitOnFailure)
end

it "skips spring by default" do
expect(described_class.class_options[:skip_spring]&.default).to be true
end
end
3 changes: 1 addition & 2 deletions suspenders.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require "date"

Gem::Specification.new do |s|
s.required_ruby_version = ">= #{Suspenders::RUBY_VERSION}"
s.required_rubygems_version = ">= 2.7.4"
s.required_rubygems_version = ">= 3.0.0"
s.authors = ["thoughtbot"]

s.description = <<~HERE
Expand All @@ -25,7 +25,6 @@ Gem::Specification.new do |s|
s.summary = "Generate a Rails app using thoughtbot's best practices."
s.version = Suspenders::VERSION

s.add_dependency "bitters", ">= 2.0.4"
s.add_dependency "parser", ">= 3.0"
s.add_dependency "bundler", ">= 2.1"
s.add_dependency "rails", Suspenders::RAILS_VERSION
Expand Down
12 changes: 8 additions & 4 deletions templates/Gemfile.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,26 @@ end

ruby "<%= Suspenders::RUBY_VERSION %>"

<% unless options[:api] %>
gem "autoprefixer-rails"
<% if options[:api] %>
gem "sprockets", "< 4"
<% end %>

gem "bootsnap", require: false
gem "cssbundling-rails"
gem "honeybadger"
gem "jsbundling-rails"
gem "pg"
gem "puma"
gem "rack-canonical-host"
gem "rails", "<%= Suspenders::RAILS_VERSION %>"
gem "recipient_interceptor"
gem "sassc-rails"
gem "skylight"
gem "sprockets", "< 4"
gem "sprockets-rails"
gem "stimulus-rails"
gem "title"
gem "turbo-rails"
gem "tzinfo-data", platforms: [:mingw, :x64_mingw, :mswin, :jruby]
gem "webpacker"

group :development do
gem "listen"
Expand Down
3 changes: 0 additions & 3 deletions templates/_javascript.html.erb

This file was deleted.

1 change: 1 addition & 0 deletions templates/application.postcss.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "normalize.css"
7 changes: 0 additions & 7 deletions templates/application.scss

This file was deleted.

18 changes: 18 additions & 0 deletions templates/bin_yarn
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env ruby

APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
yarn = ENV["PATH"].split(File::PATH_SEPARATOR)
.select { |dir| File.expand_path(dir) != __dir__ }
.product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"])
.map { |dir, file| File.expand_path(file, dir) }
.find { |file| File.executable?(file) }

if yarn
exec yarn, *ARGV
else
warn "Yarn executable was not detected in the system."
warn "Download Yarn at https://yarnpkg.com/en/docs/install"
exit 1
end
end
Loading

0 comments on commit 1712f3e

Please sign in to comment.