Webアプリのモックアップ作業土台を作る その3 Sass Source Map
その1 と その2 Sprockets の続きです。が、今回の作業は その1 の途中からブランチしてます。
Google Chrome の Dev Tools は Sass/SCSS の Source Map に対応しているので利用できるようにしてみます。が、Source Map は仕様も実装も流動的な状態で、環境によって動作しない可能性も大ありなので、あくまで参考まで。
リポジトリ上では Sprockets も Bootstrap も Compass も導入する前の段階からブランチを生やしました。
新仕様と旧仕様
CSS リクエストへのレスポンスでブラウザに Source Map の位置(パス)を知らせる必要があり、その方法は2種類あります。
- レスポンスの HTTP ヘッダで渡す
- ボディ内のコメントで渡す
- HTTP ヘッダ(新)
SourceMap:- HTTP ヘッダ(旧)
X-SourceMap:- コメント(新)
/*# sourceMappingURL=path/to/sourcemap */- コメント(旧)
/*@ sourceMappingURL=
手元の Chrome (27.0.1453.116 Mac stable channel) は HTTP ヘッダを見てくれず、コメントのほうは旧仕様にしか対応していない、という状況なので、ライブラリや出力はそれに合わせています。Beta/Dev Channel では新仕様に対応しているかもしれません(未確認)。
Sass gem の対応状況
Ruby の sass gem は prerelease (alpha) 版だけが Source Map に対応していますが、ここでは特に sass 3.3.0.alpha.136 を決め打ちで利用。
現在の最新は 3.3.0.alpha.198 ですが、198では前段のコメント出力が新仕様になっています。
まずは Gemfile に追加して bundle。
gem 'sass', '3.3.0.alpha.136'
Sinatra Extension を書く
Sinatra Extension でヘルパーメソッドを追加(だいぶやっつけ感がありますが)。ソース全体は Github で参照してもらうことにして、ここではポイントだけ。
旧仕様と新仕様の HTTP ヘッダを出力するヘルパー(JS用のパスを入れたヘッダと混在するとどうなるのかよくわかってない)。
def sass_map_header(template, opts = {}, locals = {}) sourcemap_path_info = request.path_info.sub(/\.css$/, '.sassmap') response['X-SourceMap'] = sourcemap_path_info response['SourceMap'] = sourcemap_path_info end
デフォルトの sass の代替となるヘルパー。ブラウザが前段の HTTP ヘッダに対応してくれれば必要なくなるもの。
def sass_with_map(template, opts = {}, locals = {}) content_type :css css, srcmap = __sass_render(template, opts, locals) @output = css end
Source Map を出力するヘルパー。*.sass ソースへのパスが相対パスに変換されてしまうので file:// スキームに置換しています。
def sass_map(template, opts = {}, locals = {}) content_type :json css, srcmap = __sass_render(template, opts, locals) css_path_info = request.path_info.sub(/\.sassmap$/, '.css') json_opts = { css_path: css_path_info, sourcemap_path: request.path_info } json = JSON srcmap.to_json(json_opts) json['sources'].each do |src_path| src_path.sub!(/^\.\./, 'file://') end @output = json.to_json end
sass → css をコンパイルするメソッド。Sinatra では Tilt::Template を使ってますが、ここでは直接 Sass::Engine インスタンスを作って #render_with_sourcemap を呼びます。
def __sass_render(template, opts = {}, locals = {}) css_path_info = sassmap_path_info = request.path_info css_path_info.sub!(/\.sassmap$/, '.css') sassmap_path_info.sub!(/\.css$/, '.sassmap') sass_path = File.join(settings.views, template.to_s + '.sass') opts = __sass_merged_options(filename: sass_path) engine = ::Sass::Engine.new(File.read(sass_path), opts) engine.render_with_sourcemap(sassmap_path_info) end
Sinatra アプリ修正
Extension を読み込んで
require 'sinatra/sass_sourcemap'
ヘルパーを追加して
configure :development, :test do register Sinatra::Reloader helpers Sinatra::SassSourcemap end
CSS レスポンスを置き換えて
get '/css/application.css' do sass_map_header :application sass_with_map :application end
Source Map レスポンスも設定して
get '/css/application.sassmap' do halt 404, "Not Found\n" if settings.production? sass_map :application end
Rack Up ファイルで $LOAD_PATH に lib を追加すれば完成。
config.ru
require 'bundler' Bundler.setup $LOAD_PATH << File.expand_path(File.join('..','lib'), __FILE__) require File.expand_path(File.join('..','workbench'), __FILE__) require 'rack-livereload'