41
How to Install Shoelace with Rails 7, esbuild, and PostCSS
12/17 Update: I had incorrectly used an import path for
setBasePath
below which inadvertently included the entire Shoelace library. Please review the updated import path for an optimized bundle size!
Rails 7 ships by default with a frontend pipeline based on import maps and Sprockets. I'm going on public record here that I don't like it, not at all. I ran into several insurmountable problems attempting to "pin" Shoelace and install my pick of components as well as load Shoelace's global set of CSS variables. Boo.
Thankfully, what is far more appealing regarding Rails 7 is its additional support for esbuild and PostCSS, two very fast, very capable, and extremely customizable frontend build tools. While I'm bummed that there's no real config file shipping out of the box for esbuild, such things can be put together with the right resources. Maybe the community can step up.
In the meantime, you can definitely use this setup as-is for common use cases such as installing Shoelace. There are however a couple of gotchas which I'll help you resolve herein.
First, create a new Rails 7 app using this command:
rails new rails7demo -j esbuild -c postcss
The -j esbuild
argument tells Rails you'd like to use esbuild for JavaScript bundling, and -c postcss
for CSS bundling.
From here on, you can simply run bin/dev
to boot up Rails along with both esbuild and PostCSS watch processes.
This part is very easy! Simply run this command:
yarn add @shoelace-style/shoelace
Next, you can add a few components to your app/javascript/application.js
file:
import "@shoelace-style/shoelace/dist/components/button/button.js"
import "@shoelace-style/shoelace/dist/components/icon/icon.js"
import "@shoelace-style/shoelace/dist/components/spinner/spinner.js"
and make the site styles look a bit better overall via app/assets/stylesheets/application.postcss.css
:
body {
font-family: -apple-system, sans-serif;
background: #eee;
}
Now, let's test Shoelace out in an HTML view. First, run:
bin/rails generate controller Articles index --skip-routes
and then we'll update the config/routes.rb
file:
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
root "articles#index"
end
We can add the Shoelace components to app/views/articles/index.html.erb
:
<p><sl-button type="primary">
<sl-icon slot="prefix" name="twitter"></sl-icon>
Follow on Twitter
</sl-button></p>
<p><sl-spinner style="font-size: 3rem; --track-width: 6px;"></sl-spinner></p>
Run bin/dev
and go to http://localhost:3000
and…oh no, that doesn't look right at all! We forgot to add Shoelace's global stylesheet to include its CSS variables on the site!
Er, how do we do that? Hmm. If you try to import the stylesheet in the application.js
file, esbuild and PostCSS will take turns clobbering the output application.css
file in app/assets/builds
. 🙁 And if you try to import the stylesheet directly inside of application.postcss.css
, it doesn't work at all, because the PostCSS config doesn't do anything special to @import
statements so you can't actually import anything from the node_modules
folder. ☹️
There are two possible solutions to this:
One is to fix the esbuild/PostCSS clobbering issue by changing the output file in the build:css
script inside package.json
to something other than application.css
, then adding a second stylesheet_link_tag
to your application layout. This is probably the best solution overall. But I thought it would be worthwhile to see if we could keep the existing build configuration as-is, and simply fix the PostCSS import issue instead. So let's try that.
First, run this command:
yarn add postcss-import
Next, update your postcss.config.js
file so it looks like this:
const atImport = require("postcss-import")
module.exports = {
plugins: [
atImport,
require('postcss-nesting'),
require('autoprefixer'),
],
}
Then add this to the top of your application.postcss.css
file:
@import "@shoelace-style/shoelace/dist/themes/light.css";
Now when you boot up your server and try the site again, it should actually work this time! Except…the button is missing its icon. Where's the icon?!
We need to set up a process whereby we copy the icons out of Shoelace's assets folder, and then we tell Shoelace how to find them.
We'll do this the easy way. Since it's unlikely for Shoelace icons to change with any frequency, we don't need to worry about fingerprinting them for cache busting purposes. We can just dump them in public
and call it a day.
First, update your scripts in package.json
so they look like this:
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds",
"build:css": "yarn shoelace:copy-assets && postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css",
"shoelace:copy-assets": "mkdir -p public/shoelace-assets && cp -r node_modules/@shoelace-style/shoelace/dist/assets public/shoelace-assets"
}
All we did here is add yarn shoelace:copy-assets
in front of the PostCSS command, and then in the copy-assets script we create a new folder and copy the files out of node_modules
. We do this every time we boot up the site, so in future if you upgrade Shoelace, you'll always have the most up-to-date icon set.
Next, we'll add the following to application.js
so Shoelace knows where to find the icon assets:
import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js"
setBasePath("/shoelace-assets")
Run bin/dev
and presto! Your Shoelace button now has a Twitter icon to go with it.
And that's how you can use Shoelace components with your shiny new Rails 7 + esbuild + PostCSS app. Enjoy! 🥳
41