dartsass-rails icon indicating copy to clipboard operation
dartsass-rails copied to clipboard

Migration from `sassc-rails` workarounds

Open nevans opened this issue 1 year ago • 13 comments

It would help enormously if we had better documentation on the incompatibilities with sassc-rails and known workarounds for them. I'm willing to create a README.md or docs/migrating.md PR, but I'd need guidance (and I can't promise when I'll get around to creating it).

Following are my initial thoughts start towards what might be needed in a docs/migrating.md, and the workarounds that I used. 🙂 You can maybe consider it a "rough draft". I would appreciate advice on my workarounds: Can they be simplified or improved? Are they factually inaccurate (I did test them all, but there might be something abnormal in my testing env)? Which ones deserve to be included in a documentation PR? What am I missing?

Glob imports

Imports need to be explicitly enumerated. A simple pattern is to convert all @import "foo/**/*"; glob imports into @import "foo"; and add a new foo/_index.scss file which contains explicit imports for all of the globbed files. If foo/_index.scss already exists, @import "foo/glob"; and foo/_glob.scss could be used instead.

This can be tedious and error-prone to do by hand, so it would be nice to add a find|awk bash one-liner to the documentation. Even nicer would be a dartsass:migrate-globs task.

Require directives

Although sass-rails and sassc-rails both process the sprockets require directives in .scss files, dartsass-rails does not. I found several different workarounds for this. The first two should work with sprockets or propshaft, but the others require sprockets.

Convert to @import

Fortunately, dartsass-rails adds all gem asset paths as load paths for dartsass. So, if the gem is only using basic css or scss, we can generally replace //= require "foo" with @import "foo";

Unfortunately, when the gems themselves rely on sprockets features, this won't work. For example: jquery-ui-rails uses sprockets require directives. And font-awesome-rails uses erb to process a .css.erb file. Other gems may have other incompatibilities.

Convert to node packages

In many cases, a node package exists for the library in question. In that case, the simplest approach may be to convert from the bundled gems to the node package. The @import statement may need to use the full path to the stylesheet, relative to node_modules. E.g: //= require "jquery-ui" might be converted to @import 'jquery-ui/dist/themes/base/jquery-ui.min';. Alternatively, the path to the node package's dist directory could be added to config.assets.paths.

However, in some cases, the node packages require non-trivial changes to the application code, or the gems are significantly simpler to use, or the gems come with additional functionality such as image assets and helper modules. In those cases, the next two workarounds will probably work.

Use a sprockets css entrypoint to require dartsass build output

Add a prefix or suffix to the entrypoint with the problematic require directives. Add this new file to the config.dartsass.builds hash. Remove the sprockets requires from the top, to avoid confusion (optional, they are comments so they're simply ignored).

Create a new .css file with the same base name as the original entrypoint (only changing the extension from .scss to .css). Add this file to either config.assets.precompile or manifest.js. Copy all of the original require directives into this file (nothing else). Add a require directive for the renamed .scss file at the bottom of this css file--or if require self was used, replace that. Once dartsass has run and written to app/assets/builds, the new sprockets entrypoint should be able to work just like before.

Use new entrypoints and new stylesheet_link_tags

Convert the = require that work into @import. The problematic imports can mostly be discovered by simply converting them all and then commenting out the ones that don't work until dartsass:build runs without errors.

  • For each problematic import:
    • add the built name to config.assets.precompile. Sprockets precompiles these entrypoints using the asset paths and manifests of all loaded rails engines (gems).
    • Above every stylesheet_link_tag for the existing entrypoint, add a stylesheet_link_tag for each of these new entrypoints.
  • CSS ordering is significant and this moves the sprockets processed stylesheets to first. If that breaks the styles, create more entrypoints in either dartsass or sprockets as needed.

Examples

The examples are based on the following hypothetical broken scss:

// app/assets/stylesheets/admin.scss
/*
 *= require "bootstrap"  
 *= require "bootstrap-responsive"
 *= require "font-awesome"  
 *= require "jquery-ui"
 *= require "something-else-etc-etc"  
 */

.my.awesome.scss.styles {}
.etc .etc .etc {}
# config/initializers/assets.rb
config.dartsass.builds = { "admin.scss" => "admin.css" }
<%# app/views/layouts/admin.html.erb %>
<%= stylesheet_link_tag "admin", media: "all" %>

Use a sprockets css entrypoint to require dartsass build output

The example could be converted to something like the following:

// app/assets/stylesheets/admin.css
/*
 *= require "bootstrap"  
 *= require "bootstrap-responsive"
 *= require "font-awesome"  
 *= require "jquery-ui"
 *= require "something-else-etc-etc"  
 *= require "admin-scss"  
 */
// EOF
// app/assets/stylesheets/admin-scss.scss
.my.awesome.scss.styles {}
.etc .etc .etc {}
# config/initializers/assets.rb
config.assets.precompile += %w[admin.scss]
config.dartsass.builds = { "admin-scss.scss" => "admin-scss.css" }

The erb would be unchanged.

Use new entrypoints and new stylesheet_link_tags

The example would be converted to something like the following:

// app/assets/stylesheets/base-layout.scss
@import "bootstrap";
@import "bootstrap-responsive";
// app/assets/stylesheets/admin.scss
@import "something-else-etc-etc";

.my.awesome.scss.styles {}
.etc .etc .etc {}
# config/initializers/assets.rb
config.assets.precompile += %w[
  font-awesome.css
  jquery-ui.css
]
config.dartsass.builds = {
  "base-layout" => "base-layout.css"
  "admin-scss.scss" => "admin-scss.css"
}
<%# app/views/layouts/admin.html.erb %>
<%= stylesheet_link_tag "base-layout", media: "all" %>
<%= stylesheet_link_tag "font-awesome", media: "all" %>
<%= stylesheet_link_tag "jquery-ui", media: "all" %>
<%= stylesheet_link_tag "admin", media: "all" %>

nevans avatar Jul 08 '23 18:07 nevans