sasso-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE-MIT +21 -0
- data/README.md +115 -0
- data/lib/generators/sasso/install/install_generator.rb +80 -0
- data/lib/generators/sasso/install/templates/application.scss +13 -0
- data/lib/sasso/rails/compiler.rb +121 -0
- data/lib/sasso/rails/engine.rb +66 -0
- data/lib/sasso/rails/version.rb +10 -0
- data/lib/sasso/rails.rb +20 -0
- data/lib/sasso-rails.rb +5 -0
- data/lib/tasks/sasso_tasks.rake +52 -0
- metadata +93 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ec674d60203b29114c080883f7e4cf438fa36dbc8b43daaebb02f7d197ffbf2e
|
|
4
|
+
data.tar.gz: cd677fde1997dae468d8ad8acdd4de81a41847931ba6f72a9d7b102cfe523bbe
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ca83ee104decbe95b065399c956584959c4f8599658c590c90c371859a920562f611eb7d41171d53e1581a3d66160ccb5bd02fd2545986ad87c9fba975075079
|
|
7
|
+
data.tar.gz: 9df668f046ac42666b9ab9e65aba3b0b29fe3dfb0d018ccf8f7afc821f8f4378e76afbf0b5ca151ac6efc9b987a52160f0279fdcfeaa55215e3bb3738c39347c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the **sasso-rails** gem are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
This gem versions independently of the `sasso` compiler gem; each release notes
|
|
7
|
+
the engine-gem version range it requires.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-06-13
|
|
12
|
+
|
|
13
|
+
Initial release. Requires the `sasso` gem **>= 0.1.1, < 1**.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `Sasso::Rails::Engine` — wires sasso into a Rails app with `config.sasso.*`
|
|
18
|
+
(`builds`, `style`, `load_paths`, `source_dir`, `build_dir`).
|
|
19
|
+
- Rake tasks `sasso:build`, `sasso:watch`, and `sasso:clobber`; `sasso:build`
|
|
20
|
+
is enhanced onto `assets:precompile` (and `sasso:clobber` onto
|
|
21
|
+
`assets:clobber`).
|
|
22
|
+
- `Sasso::Rails::Compiler` — Rails-free build object; compiles each entrypoint
|
|
23
|
+
with the in-process `Sasso.compile` (no subprocess / Node.js) and writes to
|
|
24
|
+
`app/assets/builds`.
|
|
25
|
+
- `bin/rails sasso:install` generator: scaffolds `application.scss`, the builds
|
|
26
|
+
directory, a Sprockets manifest link (when present), and a `Procfile.dev`
|
|
27
|
+
watch process.
|
|
28
|
+
- Propshaft-first, Sprockets-compatible (compiled CSS is served as a static
|
|
29
|
+
build artifact by either pipeline).
|
data/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 linyiru
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# sasso-rails
|
|
2
|
+
|
|
3
|
+
Rails integration for [**sasso**](https://github.com/momiji-rs/sasso-ruby) — a
|
|
4
|
+
pure-Rust, zero-dependency SCSS/Sass → CSS compiler (a byte-for-byte dart-sass
|
|
5
|
+
alternative).
|
|
6
|
+
|
|
7
|
+
Compiles your stylesheets into `app/assets/builds/` for the Rails asset
|
|
8
|
+
pipeline to serve. **Propshaft-first** (the Rails 8 default) and
|
|
9
|
+
Sprockets-compatible.
|
|
10
|
+
|
|
11
|
+
Why this gem over the alternatives:
|
|
12
|
+
|
|
13
|
+
- **No Node.js.** Unlike `cssbundling-rails`, there is no `node`/`yarn`/`npx`
|
|
14
|
+
requirement — the compiler ships as a precompiled Ruby native gem.
|
|
15
|
+
- **In-process, no subprocess.** Unlike `dartsass-rails` / `tailwindcss-rails`
|
|
16
|
+
(which shell out to a standalone binary), sasso-rails calls `Sasso.compile`
|
|
17
|
+
directly through the native extension — no process spawn, no IPC.
|
|
18
|
+
- **Pure Rust core.** No libsass (deprecated), no Dart VM.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Gemfile
|
|
24
|
+
gem "sasso-rails"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bundle install
|
|
29
|
+
bin/rails generate sasso:install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The installer scaffolds `app/assets/stylesheets/application.scss`, creates
|
|
33
|
+
`app/assets/builds/` (with a `.keep`), links the builds directory in a Sprockets
|
|
34
|
+
manifest if one exists, and adds a watch process to `Procfile.dev`.
|
|
35
|
+
|
|
36
|
+
Reference the compiled CSS in your layout:
|
|
37
|
+
|
|
38
|
+
```erb
|
|
39
|
+
<%= stylesheet_link_tag "application" %>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
bin/rails sasso:build # compile once
|
|
46
|
+
bin/rails sasso:watch # recompile on change (or run ./bin/dev)
|
|
47
|
+
bin/rails sasso:clobber # remove generated CSS
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`sasso:build` runs automatically before `assets:precompile`, so deploys
|
|
51
|
+
(`rails assets:precompile`) regenerate the CSS before the pipeline fingerprints
|
|
52
|
+
it. No extra deploy wiring needed.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
In `config/application.rb` (or an environment file):
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# Map each entrypoint (under source_dir) to an output file (under build_dir).
|
|
60
|
+
config.sasso.builds = {
|
|
61
|
+
"application.scss" => "application.css",
|
|
62
|
+
"admin.scss" => "admin.css",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# :expanded | :compressed. Default: :compressed in production, :expanded else.
|
|
66
|
+
config.sasso.style = :compressed
|
|
67
|
+
|
|
68
|
+
# Extra @use/@import include dirs. The entrypoint's own directory is always
|
|
69
|
+
# searched first, so sibling partials need no configuration.
|
|
70
|
+
config.sasso.load_paths = [Rails.root.join("vendor/styles").to_s]
|
|
71
|
+
|
|
72
|
+
# Defaults shown:
|
|
73
|
+
config.sasso.source_dir = "app/assets/stylesheets"
|
|
74
|
+
config.sasso.build_dir = "app/assets/builds"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## How it fits the pipeline
|
|
78
|
+
|
|
79
|
+
`sasso:build` writes plain CSS to `app/assets/builds/`. Both pipelines treat
|
|
80
|
+
that directory as a source of ready-to-serve assets:
|
|
81
|
+
|
|
82
|
+
- **Propshaft** (Rails 8 default) discovers `app/assets/builds` on its load
|
|
83
|
+
path, fingerprints the CSS, and rewrites `url(...)` references — it performs
|
|
84
|
+
no Sass compilation itself.
|
|
85
|
+
- **Sprockets** serves the built CSS as a static asset (the installer adds
|
|
86
|
+
`//= link_tree ../builds` to `app/assets/config/manifest.js`).
|
|
87
|
+
|
|
88
|
+
## Notes & limitations
|
|
89
|
+
|
|
90
|
+
- **Source maps are not supported yet.** The in-process compiler returns CSS
|
|
91
|
+
only; there is no `.css.map` output in any environment.
|
|
92
|
+
- **`builds` is file-to-file.** Each entry maps one input file to one output
|
|
93
|
+
file. There is no directory-glob form (no `"." => "."`); list each entrypoint
|
|
94
|
+
explicitly.
|
|
95
|
+
- **Load paths are explicit.** Only the entrypoint's own directory (searched
|
|
96
|
+
automatically) and `config.sasso.load_paths` are on the `@use`/`@import`
|
|
97
|
+
search path. Gem-vendored or `config.assets.paths` stylesheets are NOT added
|
|
98
|
+
automatically — list their directories in `config.sasso.load_paths`.
|
|
99
|
+
- **No double-minification.** On a Sprockets app that sets
|
|
100
|
+
`config.assets.css_compressor`, sasso-rails stays `:expanded` and lets the
|
|
101
|
+
pipeline compress (unless you pin `config.sasso.style`). Propshaft does not
|
|
102
|
+
compress, so production output is `:compressed` there.
|
|
103
|
+
- **Watch is a 1s mtime poll** (dependency-free, no native fs-events); it covers
|
|
104
|
+
the source dir and `config.sasso.load_paths`.
|
|
105
|
+
|
|
106
|
+
## Versioning
|
|
107
|
+
|
|
108
|
+
This gem versions independently of the `sasso` compiler gem and pins it with a
|
|
109
|
+
range (`sasso >= 0.1.1, < 1`). An app may pin a specific compiler version in its
|
|
110
|
+
own `Gemfile`.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT, matching the Sass ecosystem. (The core `sasso` compiler crate remains
|
|
115
|
+
dual-licensed MIT OR Apache-2.0.)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module Sasso
|
|
6
|
+
module Generators
|
|
7
|
+
# `bin/rails sasso:install` — scaffolds the entrypoint + build dir and wires
|
|
8
|
+
# a dev watch process, mirroring the dartsass/tailwindcss install flow.
|
|
9
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
def create_stylesheet
|
|
13
|
+
template "application.scss", "app/assets/stylesheets/application.scss"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ensure_builds_directory
|
|
17
|
+
create_file "app/assets/builds/.keep" unless File.exist?(builds_keep_path)
|
|
18
|
+
|
|
19
|
+
rules = "/app/assets/builds/*\n!/app/assets/builds/.keep\n"
|
|
20
|
+
if File.exist?(gitignore_path)
|
|
21
|
+
# Idempotent: skip if already present (re-running the installer).
|
|
22
|
+
append_to_file ".gitignore", rules unless File.read(gitignore_path).include?("/app/assets/builds/")
|
|
23
|
+
else
|
|
24
|
+
# No .gitignore at all — create one so generated CSS isn't committed.
|
|
25
|
+
create_file ".gitignore", rules
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Sprockets-only apps must link the builds directory in the manifest;
|
|
30
|
+
# Propshaft discovers it automatically, so guard on the manifest existing.
|
|
31
|
+
def link_builds_in_sprockets_manifest
|
|
32
|
+
manifest = "app/assets/config/manifest.js"
|
|
33
|
+
full = File.join(destination_root, manifest)
|
|
34
|
+
return unless File.exist?(full)
|
|
35
|
+
return if File.read(full).include?("link_tree ../builds")
|
|
36
|
+
|
|
37
|
+
append_to_file manifest, %(//= link_tree ../builds\n)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_watch_to_procfile
|
|
41
|
+
procfile = "Procfile.dev"
|
|
42
|
+
full = File.join(destination_root, procfile)
|
|
43
|
+
if File.exist?(full)
|
|
44
|
+
append_to_file procfile, "css: bin/rails sasso:watch\n" unless File.read(full).include?("sasso:watch")
|
|
45
|
+
else
|
|
46
|
+
create_file procfile, <<~PROCFILE
|
|
47
|
+
web: bin/rails server
|
|
48
|
+
css: bin/rails sasso:watch
|
|
49
|
+
PROCFILE
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Compile once so `app/assets/builds/application.css` exists and the first
|
|
54
|
+
# page load (stylesheet_link_tag "application") doesn't 404.
|
|
55
|
+
def build_initial_css
|
|
56
|
+
rails_command "sasso:build"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def print_instructions
|
|
60
|
+
say ""
|
|
61
|
+
say "sasso-rails installed.", :green
|
|
62
|
+
say " • Edit app/assets/stylesheets/application.scss"
|
|
63
|
+
say " • Reference it with: <%= stylesheet_link_tag \"application\" %>"
|
|
64
|
+
say " • Build once: bin/rails sasso:build"
|
|
65
|
+
say " • Watch in dev: bin/rails sasso:watch"
|
|
66
|
+
say " (or ./bin/dev, if your app has a Procfile.dev runner like foreman)"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def builds_keep_path
|
|
72
|
+
File.join(destination_root, "app/assets/builds/.keep")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def gitignore_path
|
|
76
|
+
File.join(destination_root, ".gitignore")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Entrypoint for sasso-rails. This file is compiled by `sasso:build` to
|
|
2
|
+
// app/assets/builds/application.css, which the asset pipeline serves.
|
|
3
|
+
//
|
|
4
|
+
// Use @use to pull in partials (files named _name.scss) from this directory:
|
|
5
|
+
//
|
|
6
|
+
// @use "components/buttons";
|
|
7
|
+
//
|
|
8
|
+
// Sibling partials resolve automatically; add extra include dirs via
|
|
9
|
+
// config.sasso.load_paths in config/application.rb.
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Sasso
|
|
6
|
+
module Rails
|
|
7
|
+
# Compiles configured Sass/SCSS entrypoints to plain CSS files under the
|
|
8
|
+
# builds directory. Deliberately Rails-free so it can be unit-tested with a
|
|
9
|
+
# plain temp `root:` — the Engine just wires `Rails.root` and config in.
|
|
10
|
+
#
|
|
11
|
+
# Sasso::Rails::Compiler.new(
|
|
12
|
+
# root: Rails.root,
|
|
13
|
+
# builds: { "application.scss" => "application.css" },
|
|
14
|
+
# style: :expanded,
|
|
15
|
+
# load_paths: [], # extra dirs, in addition to the source dir
|
|
16
|
+
# source_dir: "app/assets/stylesheets",
|
|
17
|
+
# build_dir: "app/assets/builds",
|
|
18
|
+
# ).build
|
|
19
|
+
class Compiler
|
|
20
|
+
Error = Class.new(StandardError)
|
|
21
|
+
|
|
22
|
+
ALLOWED_STYLES = %i[expanded compressed].freeze
|
|
23
|
+
|
|
24
|
+
attr_reader :root, :builds, :style, :load_paths, :source_dir, :build_dir
|
|
25
|
+
|
|
26
|
+
def initialize(root:, builds:, style: :expanded, load_paths: [],
|
|
27
|
+
source_dir: "app/assets/stylesheets",
|
|
28
|
+
build_dir: "app/assets/builds")
|
|
29
|
+
@root = File.expand_path(root.to_s)
|
|
30
|
+
@builds = normalize_builds(builds)
|
|
31
|
+
@style = normalize_style(style)
|
|
32
|
+
@load_paths = Array(load_paths).map(&:to_s)
|
|
33
|
+
@source_dir = source_dir.to_s
|
|
34
|
+
@build_dir = build_dir.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Compile every entrypoint; returns the list of written output paths.
|
|
38
|
+
def build
|
|
39
|
+
builds.map { |input, output| build_one(input, output) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Compile a single `input` (relative to source_dir) to `output` (relative
|
|
43
|
+
# to build_dir), returning the absolute path written.
|
|
44
|
+
def build_one(input, output)
|
|
45
|
+
src = File.join(@root, @source_dir, input)
|
|
46
|
+
unless File.file?(src)
|
|
47
|
+
raise Error, "sasso-rails: input stylesheet not found: #{src}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# `Sasso.compile` already searches the entry file's own directory first
|
|
51
|
+
# (for sibling @use/@import); pass any extra include dirs after it.
|
|
52
|
+
css = ::Sasso.compile(src, style: @style, load_paths: @load_paths)
|
|
53
|
+
|
|
54
|
+
dest = File.join(@root, @build_dir, output)
|
|
55
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
56
|
+
File.write(dest, css)
|
|
57
|
+
dest
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Recompile whenever a watched source file changes. Dependency-free poll
|
|
61
|
+
# loop (no `listen` gem): cheap mtime scan of the source + load_path trees.
|
|
62
|
+
# Blocks. A compile error (including on the FIRST pass) is reported and the
|
|
63
|
+
# loop keeps running — the watcher must survive a mid-edit broken file.
|
|
64
|
+
def watch(interval: 1.0)
|
|
65
|
+
safe_build
|
|
66
|
+
snapshot = source_mtimes
|
|
67
|
+
loop do
|
|
68
|
+
sleep interval
|
|
69
|
+
current = source_mtimes
|
|
70
|
+
next if current == snapshot
|
|
71
|
+
|
|
72
|
+
snapshot = current
|
|
73
|
+
safe_build
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Compile, downgrading a Sass/config error to a warning so a watcher does
|
|
80
|
+
# not die on it (used at startup and on every recompile).
|
|
81
|
+
def safe_build
|
|
82
|
+
build
|
|
83
|
+
rescue ::Sasso::CompileError, Error => e
|
|
84
|
+
warn e.message
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# mtime of every .scss/.sass under the source dir AND the configured
|
|
88
|
+
# load_paths (so editing a shared partial in an include dir also rebuilds).
|
|
89
|
+
def source_mtimes
|
|
90
|
+
dirs = [File.join(@root, @source_dir), *@load_paths]
|
|
91
|
+
dirs.flat_map { |d| Dir.glob(File.join(d, "**", "*.{scss,sass}")) }
|
|
92
|
+
.uniq.sort.each_with_object({}) do |f, h|
|
|
93
|
+
h[f] = File.mtime(f).to_f
|
|
94
|
+
rescue Errno::ENOENT
|
|
95
|
+
# raced with a delete; skip
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# config.sasso.builds must be a non-empty { "input" => "output" } map.
|
|
100
|
+
def normalize_builds(builds)
|
|
101
|
+
hash = builds.respond_to?(:to_h) ? builds.to_h : nil
|
|
102
|
+
if hash.nil? || hash.empty?
|
|
103
|
+
raise Error, "sasso-rails: config.sasso.builds must be a non-empty Hash " \
|
|
104
|
+
'of { "input.scss" => "output.css" }'
|
|
105
|
+
end
|
|
106
|
+
hash
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Coerce + validate the style (nil/garbage gives a clear error, not a
|
|
110
|
+
# NoMethodError or an opaque compiler ArgumentError).
|
|
111
|
+
def normalize_style(style)
|
|
112
|
+
sym = style.to_s.to_sym
|
|
113
|
+
unless ALLOWED_STYLES.include?(sym)
|
|
114
|
+
raise Error, "sasso-rails: config.sasso.style must be one of " \
|
|
115
|
+
"#{ALLOWED_STYLES.inspect}, got #{style.inspect}"
|
|
116
|
+
end
|
|
117
|
+
sym
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails"
|
|
4
|
+
require "active_support/ordered_options"
|
|
5
|
+
|
|
6
|
+
module Sasso
|
|
7
|
+
module Rails
|
|
8
|
+
# Wires sasso into a Rails app. Configurable via `config.sasso.*`:
|
|
9
|
+
#
|
|
10
|
+
# config.sasso.builds # { "application.scss" => "application.css" }
|
|
11
|
+
# config.sasso.style # :expanded | :compressed (default: env-based)
|
|
12
|
+
# config.sasso.load_paths # extra @use/@import include dirs (Array)
|
|
13
|
+
# config.sasso.source_dir # default "app/assets/stylesheets"
|
|
14
|
+
# config.sasso.build_dir # default "app/assets/builds"
|
|
15
|
+
#
|
|
16
|
+
# The `sasso:build` rake task is enhanced onto `assets:precompile`, so the
|
|
17
|
+
# CSS is generated before Propshaft/Sprockets fingerprints it on deploy.
|
|
18
|
+
class Engine < ::Rails::Engine
|
|
19
|
+
config.sasso = ::ActiveSupport::OrderedOptions.new
|
|
20
|
+
config.sasso.builds = { "application.scss" => "application.css" }
|
|
21
|
+
config.sasso.style = nil # resolved in `Sasso::Rails.style_for`
|
|
22
|
+
config.sasso.load_paths = []
|
|
23
|
+
config.sasso.source_dir = "app/assets/stylesheets"
|
|
24
|
+
config.sasso.build_dir = "app/assets/builds"
|
|
25
|
+
|
|
26
|
+
# NOTE: no `rake_tasks do load ... end` — Rails::Engine already auto-loads
|
|
27
|
+
# lib/tasks/**/*.rake into the host app. Loading it again here would define
|
|
28
|
+
# each task (and run each `enhance`) twice.
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Build a Compiler from the running app's config + root.
|
|
34
|
+
def compiler(app = ::Rails.application)
|
|
35
|
+
cfg = app.config.sasso
|
|
36
|
+
Compiler.new(
|
|
37
|
+
root: app.root,
|
|
38
|
+
builds: cfg.builds,
|
|
39
|
+
style: style_for(cfg.style),
|
|
40
|
+
load_paths: cfg.load_paths,
|
|
41
|
+
source_dir: cfg.source_dir,
|
|
42
|
+
build_dir: cfg.build_dir,
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Default to compressed CSS in production (smaller payload), expanded
|
|
47
|
+
# elsewhere (readable). An explicit `config.sasso.style` always wins.
|
|
48
|
+
#
|
|
49
|
+
# If the asset pipeline has its own CSS compressor (a Sprockets app that set
|
|
50
|
+
# config.assets.css_compressor), stay :expanded and let the pipeline
|
|
51
|
+
# compress, avoiding wasteful double-minification. Propshaft sets no
|
|
52
|
+
# compressor, so production stays :compressed on the default Rails 8 path.
|
|
53
|
+
def style_for(configured)
|
|
54
|
+
return configured.to_sym if configured
|
|
55
|
+
return :expanded if pipeline_css_compressor?
|
|
56
|
+
|
|
57
|
+
::Rails.env.production? ? :compressed : :expanded
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def pipeline_css_compressor?
|
|
61
|
+
::Rails.application&.config&.assets&.css_compressor.present?
|
|
62
|
+
rescue StandardError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sasso
|
|
4
|
+
module Rails
|
|
5
|
+
# Versioned INDEPENDENTLY of both the `sasso` gem and the `sasso` crate.
|
|
6
|
+
# The gemspec pins the engine gem with a range (sasso >= 0.1.1, < 1), so a
|
|
7
|
+
# compiler bump does not force a lockstep release of this integration gem.
|
|
8
|
+
VERSION = "0.1.0"
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/sasso/rails.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sasso" # the compiler engine gem (native extension)
|
|
4
|
+
require "sasso/rails/version"
|
|
5
|
+
require "sasso/rails/compiler"
|
|
6
|
+
|
|
7
|
+
# Rails integration for the `sasso` SCSS/Sass compiler.
|
|
8
|
+
#
|
|
9
|
+
# Propshaft-first build-task model (mirrors rails/dartsass-rails): compile the
|
|
10
|
+
# configured entrypoints to plain CSS in `app/assets/builds/`, where the asset
|
|
11
|
+
# pipeline (Propshaft on Rails 8, or Sprockets serving it as a static file)
|
|
12
|
+
# fingerprints and serves them. Unlike dartsass-rails/tailwindcss-rails — which
|
|
13
|
+
# shell out to a standalone binary — this calls `Sasso.compile` IN-PROCESS via
|
|
14
|
+
# the native extension, so there is no subprocess, IPC, or binary-path lookup.
|
|
15
|
+
module Sasso
|
|
16
|
+
module Rails
|
|
17
|
+
# Loaded only inside a Rails process; the Compiler is usable standalone.
|
|
18
|
+
require "sasso/rails/engine" if defined?(::Rails::Railtie)
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/sasso-rails.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :sasso do
|
|
4
|
+
desc "Compile the configured Sass/SCSS entrypoints to app/assets/builds"
|
|
5
|
+
task build: :environment do
|
|
6
|
+
written = Sasso::Rails.compiler.build
|
|
7
|
+
written.each { |path| puts "sasso: compiled #{path}" }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "Watch the source stylesheets and recompile on change (Ctrl-C to stop)"
|
|
11
|
+
task watch: :environment do
|
|
12
|
+
compiler = Sasso::Rails.compiler
|
|
13
|
+
puts "sasso: watching #{compiler.source_dir} ..."
|
|
14
|
+
compiler.watch
|
|
15
|
+
rescue Interrupt
|
|
16
|
+
puts "\nsasso: stopped."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "Remove the CSS that sasso:build generated"
|
|
20
|
+
task clobber: :environment do
|
|
21
|
+
compiler = Sasso::Rails.compiler
|
|
22
|
+
compiler.builds.each_value do |output|
|
|
23
|
+
path = File.join(compiler.root, compiler.build_dir, output)
|
|
24
|
+
next unless File.exist?(path)
|
|
25
|
+
|
|
26
|
+
File.delete(path)
|
|
27
|
+
puts "sasso: removed #{path}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Generate CSS before the asset pipeline (Propshaft/Sprockets) digests it, and
|
|
33
|
+
# before the test DB/fixtures are prepared so system/feature tests see fresh CSS.
|
|
34
|
+
# Set SKIP_CSS_BUILD=1 to opt out (Docker multi-stage / separate build step).
|
|
35
|
+
unless ENV["SKIP_CSS_BUILD"]
|
|
36
|
+
if Rake::Task.task_defined?("assets:precompile")
|
|
37
|
+
Rake::Task["assets:precompile"].enhance(["sasso:build"])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Hook whichever test-prep task this app defines (Minitest / RSpec / DB-only).
|
|
41
|
+
%w[test:prepare spec:prepare db:test:prepare].each do |t|
|
|
42
|
+
next unless Rake::Task.task_defined?(t)
|
|
43
|
+
|
|
44
|
+
Rake::Task[t].enhance(["sasso:build"])
|
|
45
|
+
break
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Keep `rails assets:clobber` symmetric.
|
|
50
|
+
if Rake::Task.task_defined?("assets:clobber")
|
|
51
|
+
Rake::Task["assets:clobber"].enhance(["sasso:clobber"])
|
|
52
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sasso-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- momiji-rs
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 7.0.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 7.0.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: sasso
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.1.1
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '1'
|
|
37
|
+
type: :runtime
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 0.1.1
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1'
|
|
47
|
+
description: Compiles Sass/SCSS to CSS in the Rails asset pipeline (Propshaft-first,
|
|
48
|
+
Sprockets-compatible) using the in-process sasso native extension — no Node.js,
|
|
49
|
+
no subprocess.
|
|
50
|
+
email:
|
|
51
|
+
executables: []
|
|
52
|
+
extensions: []
|
|
53
|
+
extra_rdoc_files: []
|
|
54
|
+
files:
|
|
55
|
+
- CHANGELOG.md
|
|
56
|
+
- LICENSE-MIT
|
|
57
|
+
- README.md
|
|
58
|
+
- lib/generators/sasso/install/install_generator.rb
|
|
59
|
+
- lib/generators/sasso/install/templates/application.scss
|
|
60
|
+
- lib/sasso-rails.rb
|
|
61
|
+
- lib/sasso/rails.rb
|
|
62
|
+
- lib/sasso/rails/compiler.rb
|
|
63
|
+
- lib/sasso/rails/engine.rb
|
|
64
|
+
- lib/sasso/rails/version.rb
|
|
65
|
+
- lib/tasks/sasso_tasks.rake
|
|
66
|
+
homepage: https://github.com/momiji-rs/sasso-rails
|
|
67
|
+
licenses:
|
|
68
|
+
- MIT
|
|
69
|
+
metadata:
|
|
70
|
+
homepage_uri: https://github.com/momiji-rs/sasso-rails
|
|
71
|
+
source_code_uri: https://github.com/momiji-rs/sasso-rails
|
|
72
|
+
changelog_uri: https://github.com/momiji-rs/sasso-rails/blob/main/CHANGELOG.md
|
|
73
|
+
rubygems_mfa_required: 'true'
|
|
74
|
+
post_install_message:
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 3.1.0
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 3.5.22
|
|
90
|
+
signing_key:
|
|
91
|
+
specification_version: 4
|
|
92
|
+
summary: Rails integration for the sasso SCSS/Sass compiler
|
|
93
|
+
test_files: []
|