DEV Community

Jared White
Jared White

Posted on

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!

Shoelace is a fantastic library of frontend components that are framework-agnostic and work well with Hotwire-style web projects. I wanted to try out the brand-new release of Rails 7 along with Shoelace, but I quickly ran into a snag.

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.

Setting Up Rails

First, create a new Rails 7 app using this command:

rails new rails7demo -j esbuild -c postcss
Enter fullscreen mode Exit fullscreen mode

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.

Installing Shoelace

This part is very easy! Simply run this command:

yarn add @shoelace-style/shoelace
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Now, let's test Shoelace out in an HTML view. First, run:

bin/rails generate controller Articles index --skip-routes
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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. ☹️

Fixing the Import Problem 😃👍

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
Enter fullscreen mode Exit fullscreen mode

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'),
  ],
}
Enter fullscreen mode Exit fullscreen mode

Then add this to the top of your application.postcss.css file:

@import "@shoelace-style/shoelace/dist/themes/light.css";
Enter fullscreen mode Exit fullscreen mode

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.

Copying Icon Assets

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"
  }
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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! 🥳

Discussion (3)

Collapse
morzaram0 profile image
Chris King

How has your experience been with this? I've been considering of focusing on getting the MVP out there and this looks smoother than bootstrap. Tailwind is killing me with the lack of ui components. I tried getting these two working so I can use tailwind with shoelace but it's not worth the sturggle.

Collapse
jaredcwhite profile image
Jared White Author • Edited on

Vanilla CSS + Shoelace > Tailwind. 😄

If you're curious how far vanilla can get you, I just launched a new site for the SSG I maintain:

edge.bridgetownrb.com

Personally, I'm never going back to a legacy CSS framework (Tailwind, Bootstrap, Bulma, whatever). As far as I'm concerned, they're no longer necessary.

Collapse
justinmcodes profile image
jmarsh24

With this configuration how to do handle import relative css files? Do you require them explicitly because globs are not supported?