lib/middleman-core/renderers/sass.rb
require 'sassc' module Middleman module Renderers # Sass renderer class Sass < ::Middleman::Extension opts = { output_style: :nested } opts[:line_comments] = false if ENV['TEST'] define_setting :sass, opts, 'Sass engine options' define_setting :sass_assets_paths, [], 'Paths to extra SASS/SCSS files' define_setting :sass_source_maps, nil, 'Whether to inline sourcemap into Sass' # Setup extension def initialize(app, options={}, &block) super app.files.ignore :sass_cache, :source, /(^|\/)\.sass-cache\// # Tell Tilt to use it as well (for inline sass blocks) ::Tilt.register 'sass', SassPlusCSSFilenameTemplate ::Tilt.prefer(SassPlusCSSFilenameTemplate) # Tell Tilt to use it as well (for inline scss blocks) ::Tilt.register 'scss', ScssPlusCSSFilenameTemplate ::Tilt.prefer(ScssPlusCSSFilenameTemplate) require 'middleman-core/renderers/sass_functions' end # A SassTemplate for Tilt which outputs debug messages class SassPlusCSSFilenameTemplate < ::Tilt::SassTemplate def initialize(*args, &block) super @context = @options[:context] if @options.key?(:context) end # Define the expected syntax for the template # @return [Symbol] def syntax :sass end def prepare; end # Add exception messaging # @param [Class] context # @return [String] def evaluate(context, _) @context ||= context @engine = ::SassC::Engine.new(data, sass_options) begin @engine.render rescue ::SassC::SyntaxError => e raise e if @context.app.build? exception_to_css(e) end end def exception_to_css(e) header = "#{e.class}: #{e.message}" <<~END /* #{header.gsub('*/', '*\\/')} Backtrace:\n#{e.backtrace.join("\n").gsub('*/', '*\\/')} */ body:before { white-space: pre; font-family: monospace; content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; } END end # Change Sass path, for url functions, to the build folder if we're building # @return [Hash] def sass_options ctx = @context preexisting_load_paths = begin ::Sass.load_paths rescue [] end more_opts = { load_paths: preexisting_load_paths + ctx.app.config[:sass_assets_paths], filename: eval_file, line: line, syntax: syntax, custom: {}.merge!(options[:custom] || {}).merge!( middleman_context: ctx.app, current_resource: ctx.current_resource ) } if ctx.app.config[:sass_source_maps] || (ctx.app.config[:sass_source_maps].nil? && ctx.app.development?) more_opts[:source_map_file] = '.' more_opts[:source_map_embed] = true more_opts[:source_map_contents] = true end if ctx.is_a?(::Middleman::TemplateContext) && file more_opts[:css_filename] = file.sub(/\.s[ac]ss$/, '') end {}.merge!(options).merge!(more_opts) end end # SCSS version of the above template class ScssPlusCSSFilenameTemplate < SassPlusCSSFilenameTemplate # Define the expected syntax for the template # @return [Symbol] def syntax :scss end end end end end