This is a setup guide for a Rails application. It assumes you generated
the application with the --minimal option. To save time, you
can instead use the template:
rails app:template\
LOCATION=https://railsway.dohmen.io/modern_frontend.rb
To make things easier, let's adjust the Gemfile in one go:
source "https://rubygems.org"
gem "rails", "~> 8.0.2"
# The modern asset pipeline for Rails
gem "propshaft"
# Use sqlite3 as the database for Active Record
gem "sqlite3"
# Use the Puma web server
gem "puma"
# Use Haml for templating
gem "haml-rails"
# Reusable, testable & encapsulated view components
gem "view_component"
group :development, :test do
# Debugger
gem "debug", platforms: %i[mri windows], require: "debug/prelude"
end
Run bundle now. We will set up all these things over the next
sections.
The benefit of using haml-rails over the
haml gem and rspec-rails over the
rspec gem is that they configure Rails's generators to
generate Haml instead of ERB and RSpec instead of Minitest.
The Rails core team offers the jsbundling-rails and
cssbundling-rails gems to build a setup similar to ours. It
offers little flexibility, which is why we will instead do those steps by
hand. To work with propshaft, our JavaScript and CSS bundling will put the
processed files into app/assets/builds. Create that
directory, and put a .keep file in it. We also add it to the
.gitignore file together with node_modules:
# Ignore the build files from CSS and JS
/app/assets/builds/*
!/app/assets/builds/.keep
# Ignore npm dependencies
/node_modules
Following the new Rails convention, we will add our empty JS file as
app/javascript/application.js:
// JavaScript goes here
We create the package.json with our two build scripts:
{
"private": true,
"type": "module",
"scripts": {
"build:js": "esbuild app/javascript/application.js --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets",
"build:css": "postcss ./app/assets/stylesheets/application.css -o ./app/assets/builds/application.css"
}
}
We add the configuration for PostCSS postcss.config.js:
import postcssImport from "postcss-import";
import autoprefixer from "autoprefixer";
export default {
plugins: [postcssImport, autoprefixer],
};
Then we install the required dependencies:
npm i --save-dev autoprefixer esbuild postcss postcss-cli postcss-import
To make sure our teammates install the Node dependencies as well, we will
run npm install after bundle. So we add this line to our
bin/setup right after the bundle check line:
system("npm install")
If you are using a node version manager like
nvm, don't forget to add a
.nvmrc file to your project. Rails has already created a
.ruby-version file for your Ruby version manager.
We will use Foreman to run the three processes in development. We add a
Procfile.dev file:
web: env RUBY_DEBUG_OPEN=true bin/rails server
js: node --run build:js -- --watch
css: node --run build:css -- --watch
We also replace bin/dev to run Foreman:
#!/usr/bin/env sh
# Default to port 3000 if not specified
export PORT="${PORT:-3000}"
exec foreman start -f Procfile.dev --env /dev/null "$@"
Finally, we add Rake tasks in lib/tasks/assets.rake that
integrate with propshaft:
namespace :css do
desc "Build your CSS bundle"
task :build do
system("node --run build:css")
end
desc "Remove CSS builds"
task :clobber do
rm_rf Dir["app/assets/builds/**/[^.]*.{css,css.map}"], verbose: false
end
end
namespace :javascript do
desc "Build your JavaScript bundle"
task :build do
system("node --run build:js")
end
desc "Remove JavaScript builds"
task :clobber do
rm_rf Dir["app/assets/builds/**/[^.]*.{js,js.map}"], verbose: false
end
end
Rake::Task["assets:clobber"].enhance(%w[css:clobber javascript:clobber])
Rake::Task["assets:precompile"].enhance(%w[css:build javascript:build])
You need to replace the layout file with a Haml version. Delete
app/views/layouts/application.html.erb and create a new file
app/views/layouts/application.html.haml:
!!!
%html{ lang: I18n.locale }
%head
%title= content_for(:title) || "Time And Expenses"
%meta{ name: "viewport", content: "width=device-width,initial-scale=1" }
= csrf_meta_tags
= csp_meta_tag
%link{ rel: "icon", href: "/icon.png", type: "image/png" }
%link{ rel: "icon", href: "/icon.svg", type: "image/svg+xml" }
%link{ rel: "apple-touch-icon", href: "/icon.png" }
= stylesheet_link_tag :application
= javascript_include_tag :application, type: "module"
%body
= yield
Note that we made some changes compared to the ERB version:
lang attribute on HTML, so screen readers know
which languages they should read the text in.
mobile-web-app-capable meta tags and the
manifest link comment. We will re-add them in a later chapter when we
made this an PWA.
yield :head as we don't need it.
We create an initializer for ViewComponent in
config/initializers/view_component.rb:
Rails.application.configure do
# Generate a folder for each component for
# Template, CSS, JS...
config.view_component.generate.sidecar = true
# Don't generate helpers
config.generators.helper = nil
end
As we are using ViewComponent, we do not need the generated helpers. So we
will not generate them. We will talk about sidecars later. We will also
add one helper to app/helpers/component_helper.rb that we
will explain in the next chapter:
module ComponentHelper
def component(name, **args, &)
component = "#{name}_component".camelize.constantize
render(component.new(**args), &)
end
end
By default, all components inherit from ApplicationComponent.
Let's create it including the helper above in
app/components/application_component.rb:
class ApplicationComponent < ViewComponent::Base
include ComponentHelper
end
Because the default asset pipeline in Rails no longer supports old
browsers, Rails now blocks older browsers by default. As we don't use
that pipeline, and we want everyone to be able to use our website, we will
remove the blocker. In
app/controllers/application_controller.rb remove the
following line and the comment above it:
allow_browser versions: :modern