LoginSignup
0
0

Sinatra アプリで Sass の処理を行う一手法

Posted at

もっと良いやり方があるかもしれないけど。

動機

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 として格納されるようにする。

以下,ファイルの中身を示す。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

gem "listen"
gem "sass-embedded"
gem "sinatra"
gem "webrick"
views/index.erb
<!DOCTYPE html>
<link href="style.css" rel="stylesheet">
<h1>Hello</h1>
assets/style.css.sass
h1
  color: red
config.ru
# 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 ファイルの編集を行なって,それがただちに反映されるようにしたい場面なら何にでもこの手法が使える。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0