もっと良いやり方があるかもしれないけど。
動機
Sinatra で Sass を使うには,
get "/hoge.css" do
sass :hoge
end
みたいな感じ書いてやればいいのだが,この sass
メソッドを使う方法は sass gem もしくは sassc gem を使うことが前提になる(たぶん)。
両 gem は既に開発を終了しており,イマドキの Sass 仕様(@use
とか)には対応していないので,新規プロジェクトでは使いたくない。
Ruby でイマドキの Sass を使うには,sass-embedded gem を採用するのがよいと考えている。
Sinatra では,CSS など静的なもの(動的生成しなくよいもの)は public
ディレクトリーに入れておけばよく,上記のような get
うんぬんというコードはそもそも要らない(開発中だけコンパイルできればよい)。ただ,Sinatra アプリの開発中は,サーバーを起動したまま Sass に加えた変更がすぐに反映されるようにしたい。
これが本記事の動機。
方針
Sass ファイルの変更を監視して,変更が加えられた時点(Sass ファイルをエディターで変更して保存した時点)でコンパイルし,/public
に収めることにした。
Sinatra アプリの動作とは完全に独立。
Sass のコンパイルには,上述した sass-embedded を使うことにする。
ファイルの(変更の)監視は定番の gem である listen を使うことにする。
sass-embedded の紹介
sass-embedded の使い方は超簡単で,
require "sass-embedded"
path = "foo/bar/baz.css.sass"
css = Sass.compile(path, style: :compressed).css
でコンパイル後の CSS が得られる。
style: :compressed
はギチギチに圧縮された CSS を得るためのオプション。これが無い場合(つまり既定では)人間に見やすい CSS が得られる。
compressed
にした場合,先頭に BOM(バイトオーダーマーク)が付きうることに注意しよう。
詳しくは以下の拙文をどうぞ:
Ruby で Sass るには - Qiita
Sass ファイルが Sass 記法(インデント記法)なのか SCSS 記法なのかはファイルパスの拡張子で判断してくれる。
listen の紹介
listen の使い方も超簡単で,大雑把にいうと
require "listen"
listener = Listen.to("監視したいディレクトリー") do |modified, added, removed|
# modified は変更が加わったファイルのパスの配列
# added は新規追加されたファイルのパスの配列
# removed は削除されたファイルのパスの配列
end
listener.start
と書いて,ブロック内で何か処理してやるだけ。
なぜ「ファイルのパス」ではなく「ファイルのパスの配列」かというと,複数のファイルが極めて近いタイミングで変更なり追加なりされると,複数ありうるから。
まあ人手で編集しているときはまずそういうことは起こらないので,「modified
, added
, removed
のどれか一つだけが要素数 1 で,他は空配列」になるだろう。
なお,listener.start
をしてもリスナーを起動するだけで,この式の評価自体は一瞬で終わってしまう。したがって,上記のような内容だけのスクリプトを実行しても,スクリプト自体が一瞬で終わり,当然,ファイルの監視・処理も終わる。
なので,上記のコードを独立のスクリプトで実行させる場合は listener.start
のあとに sleep
を置いてスクリプトが終了しないようにする必要がある。
しかし,Sinatra アプリのコードに組み込んでおけば,Sinatra アプリが動いている間はリスナーが生き続けるので,その必要はない。
ところで,特定の拡張子のファイルだけを監視対象にすることもできる。Listen.to
の引数として,以下のように :only
を渡せばよい。
Listen.to("path/to/directory", only: /\.(?:sass|scss)\z/)
これで,ファイル名の末尾が .sass
か .scss
で終わっているものだけが監視される。
作譜
ファイルツリーはこんな感じ:
├── config.ru
├── public
│ └── style.css
├── Gemfile
├── views
│ └── index.erb
└── assets
└── style.css.sass
assets/style.css.sass
が自動的にコンパイルされて public/style.css
として格納されるようにする。
以下,ファイルの中身を示す。
# frozen_string_literal: true
source "https://rubygems.org"
gem "listen"
gem "sass-embedded"
gem "sinatra"
gem "webrick"
<!DOCTYPE html>
<link href="style.css" rel="stylesheet">
<h1>Hello</h1>
h1
color: red
# frozen_string_literal: true
require "bundler"
Bundler.require
ROOT_DIR = Pathname(__dir__)
ASSETS_DIR = ROOT_DIR / "assets"
PUBLIC_DIR = ROOT_DIR / "public"
# 引数に与えたパスの Sass ファイルを処理して CSS ファイルを
# public の下に収める
def compile_sass(source_path)
source_path = Pathname(source_path)
source_rel_path = source_path.relative_path_from(ASSETS_DIR)
css_path = PUBLIC_DIR / source_rel_path.sub_ext("")
css_path.dirname.mkpath
css_path.write Sass.compile(source_path, style: :compressed).css
puts "[compile] #{source_rel_path}"
end
listener = Listen.to(ASSETS_DIR, only: /\.(?:sass|scss)\z/) do |modified, added, _removed|
[*modified, *added].each do |path|
compile_sass(path)
end
end
listener.start
# 起動時にもコンパイル
ASSETS_DIR.glob("**/*.css.{sass,scss}") do |source_path|
compile_sass(source_path)
end
# Sinatra アプリ
class MyApp < Sinatra::Base
get "/" do
erb :index
end
end
run MyApp
これで
bundle exec rackup
とすれば Sinatra アプリが起動し,http://localhost:9292/ にアクセスすれば赤い「Hello」が表示される。
config.ru
は本来はアプリ本体を記述するファイルではないが,本記事では最小限のファイル数で手法を示すためにこうした。
なお,Sass の処理が必要になるのは development 環境だけなので,Sass まわりのコードは
if settings.development?
end
で囲っておくとよい。
おわりに
「方針」節に書いたように,Sass を処理するところは Sinatra とは全く関係ない。
だから,Sinatra アプリに限らず,ローカルでウェブサーバーを動かしながら,Sass ファイルの編集を行なって,それがただちに反映されるようにしたい場面なら何にでもこの手法が使える。