diff --git a/lib/sprockets.rb b/lib/sprockets.rb index b2c0bbafb..03c122c1f 100644 --- a/lib/sprockets.rb +++ b/lib/sprockets.rb @@ -128,6 +128,7 @@ module Sprockets register_bundle_metadata_reducer '*/*', :data, proc { String.new("") }, :concat register_bundle_metadata_reducer 'application/javascript', :data, proc { String.new("") }, Utils.method(:concat_javascript_sources) register_bundle_metadata_reducer '*/*', :links, :+ + register_bundle_metadata_reducer '*/*', :sources, proc { [] }, :+ register_bundle_metadata_reducer '*/*', :map, SourceMapUtils.method(:concat_source_maps) require 'sprockets/closure_compressor' diff --git a/lib/sprockets/path_utils.rb b/lib/sprockets/path_utils.rb index 6e7555b43..0277b9b6e 100644 --- a/lib/sprockets/path_utils.rb +++ b/lib/sprockets/path_utils.rb @@ -113,6 +113,21 @@ def relative_path_from(start, dest) dest.relative_path_from(start).to_s end + # Public: Joins path to base path. + # + # base - Root path + # path - Extending path + # + # Example + # + # join('base/path/', '../file.js') + # # => 'base/file.js' + # + # Returns string path starting from base and ending at path + def join(base, path) + (Pathname.new(base) + path).to_s + end + # Internal: Get relative path for root path and subpath. # # path - String path diff --git a/lib/sprockets/sass_processor.rb b/lib/sprockets/sass_processor.rb index 664aba954..04cdf2235 100644 --- a/lib/sprockets/sass_processor.rb +++ b/lib/sprockets/sass_processor.rb @@ -100,11 +100,14 @@ def call(input) private + def expand_source(source, env) + uri, _ = env.resolve!(source, pipeline: :source) + env.load(uri).digest_path + end + def expand_map_sources(mapping, env) mapping.each do |map| - uri, _ = env.resolve!(map[:source], pipeline: :source) - source_path = env.load(uri).digest_path - map[:source] = source_path + map[:source] = expand_source(map[:source], env) end end diff --git a/lib/sprockets/sassc_processor.rb b/lib/sprockets/sassc_processor.rb index 2f103b7be..1c23bc1b4 100644 --- a/lib/sprockets/sassc_processor.rb +++ b/lib/sprockets/sassc_processor.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'sprockets/sass_processor' +require 'sprockets/path_utils' require 'base64' module Sprockets @@ -22,39 +23,42 @@ def call(input) options = engine_options(input, context) engine = Autoload::SassC::Engine.new(input[:data], options) - data = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do - engine.render + css = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do + engine.render.sub(/^\n^\/\*# sourceMappingURL=.*\*\/$/m, '') end - match_data = data.match(/(.*)\n\/\*# sourceMappingURL=data:application\/json;base64,(.+) \*\//m) - css, map = match_data[1], Base64.decode64(match_data[2]) + map = SourceMapUtils.decode_json_source_map(engine.source_map) + sources = map['sources'].map do |s| + expand_source(PathUtils.join(File.dirname(input[:filename]), s), input[:environment]) + end + + map = map["mappings"].each do |m| + m[:source] = PathUtils.join(File.dirname(input[:filename]), m[:source]) + end map = SourceMapUtils.combine_source_maps( input[:metadata][:map], - change_source(SourceMapUtils.decode_json_source_map(map)["mappings"], input[:source_path]) + expand_map_sources(map, input[:environment]) ) engine.dependencies.each do |dependency| context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.filename) end - context.metadata.merge(data: css, map: map) + context.metadata.merge(data: css, map: map, sources: sources) end private - def change_source(mappings, source) - mappings.each { |m| m[:source] = source } - end - def engine_options(input, context) merge_options({ filename: input[:filename], syntax: self.class.syntax, load_paths: input[:environment].paths, importer: @importer_class, - source_map_embed: true, - source_map_file: '.', + source_map_contents: true, + source_map_file: "#{input[:filename]}.map", + omit_source_map_url: true, sprockets: { context: context, environment: input[:environment], diff --git a/lib/sprockets/source_map_processor.rb b/lib/sprockets/source_map_processor.rb index a8f9eea25..1d598bd3b 100644 --- a/lib/sprockets/source_map_processor.rb +++ b/lib/sprockets/source_map_processor.rb @@ -17,9 +17,10 @@ def self.call(input) env = input[:environment] - uri, _ = env.resolve!(input[:filename], accept: accept) - asset = env.load(uri) - map = asset.metadata[:map] || [] + uri, _ = env.resolve!(input[:filename], accept: accept) + asset = env.load(uri) + map = asset.metadata[:map] || [] + sources = asset.metadata[:sources] # TODO: Because of the default piplene hack we have to apply dependencies # from compiled asset to the source map, otherwise the source map cache @@ -39,7 +40,7 @@ def self.call(input) links << uri end - json = env.encode_json_source_map(map, filename: asset.logical_path) + json = env.encode_json_source_map(map, sources: sources, filename: asset.logical_path) { data: json, links: links, dependencies: dependencies } end diff --git a/lib/sprockets/source_map_utils.rb b/lib/sprockets/source_map_utils.rb index 02b32d417..4061bd08a 100644 --- a/lib/sprockets/source_map_utils.rb +++ b/lib/sprockets/source_map_utils.rb @@ -140,7 +140,8 @@ def encode_json_source_map(mappings, sources: nil, names: nil, filename: nil) mappings.each do |m| m[:source] = PathUtils.relative_path_from(filename, m[:source]) end if filename - sources ||= mappings.map { |m| m[:source] }.uniq.compact + sources = sources.map { |s| PathUtils.relative_path_from(filename, s) } if filename && sources + sources = (Array(sources) + mappings.map { |m| m[:source] }).uniq.compact names ||= mappings.map { |m| m[:name] }.uniq.compact mappings = encode_vlq_mappings(mappings, sources: sources, names: names) else diff --git a/sprockets.gemspec b/sprockets.gemspec index 30f5161e9..2e737df5c 100644 --- a/sprockets.gemspec +++ b/sprockets.gemspec @@ -27,7 +27,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rack-test", "~> 0.6" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "sass", "~> 3.4" - s.add_development_dependency "sassc", "~> 1.7" + s.add_development_dependency "sassc", ">= 1.10.1", "< 2.0" s.add_development_dependency "uglifier", "~> 2.3" s.add_development_dependency "yui-compressor", "~> 0.12" s.add_development_dependency "zopfli", "~> 0.0.4" diff --git a/test/test_source_maps.rb b/test/test_source_maps.rb index 698e93668..2e15222d2 100644 --- a/test/test_source_maps.rb +++ b/test/test_source_maps.rb @@ -295,4 +295,39 @@ def setup "names" => [] }, map) end + + test "compile scss source map with imported dependencies" do + asset = silence_warnings do + @env.find_asset("sass/with-import.css") + end + assert asset + assert_equal fixture_path('source-maps/sass/with-import.scss'), asset.filename + assert_equal "text/css", asset.content_type + + assert_match "body {\n color: red; }", asset.source + + asset = silence_warnings do + @env.find_asset("sass/with-import.css.map") + end + assert asset + assert_equal fixture_path('source-maps/sass/with-import.scss'), asset.filename + assert_equal "sass/with-import.css.map", asset.logical_path + assert_equal "application/css-sourcemap+json", asset.content_type + assert_equal [ + "file://#{fixture_path_for_uri('source-maps/sass/_imported.scss')}?type=text/scss&pipeline=source", + "file://#{fixture_path_for_uri('source-maps/sass/with-import.scss')}?type=text/scss&pipeline=source" + ], normalize_uris(asset.links) + + assert map = JSON.parse(asset.source) + assert_equal({ + "version" => 3, + "file" => "sass/with-import.css", + "mappings" => "ACAA,AAAA,IAAI,CAAC;EAAE,KAAK,EAAE,GAAI,GAAI;;ADEtB,AAAA,GAAG,CAAC;EAAE,KAAK,EAAE,IAAK,GAAI", + "sources" => [ + "with-import.source-5d53742ba113ac26396986bf14ab5c7e19ef193e494d5d868a9362e3e057cb26.scss", + "_imported.source-9767e91e9d4b0334e59a1d389e9801bc6a2c5c4a5500a3c2c7915687965b2c16.scss" + ], + "names" => [] + }, map) + end end