sprockets 3.7.3 → 4.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +2 -299
- data/README.md +21 -35
- data/bin/sprockets +11 -8
- data/lib/rake/sprocketstask.rb +2 -2
- data/lib/sprockets/asset.rb +8 -21
- data/lib/sprockets/autoload/babel.rb +7 -0
- data/lib/sprockets/autoload/jsminc.rb +7 -0
- data/lib/sprockets/autoload/sassc.rb +7 -0
- data/lib/sprockets/autoload.rb +3 -0
- data/lib/sprockets/babel_processor.rb +58 -0
- data/lib/sprockets/base.rb +8 -8
- data/lib/sprockets/bower.rb +4 -2
- data/lib/sprockets/bundle.rb +1 -1
- data/lib/sprockets/cache.rb +2 -4
- data/lib/sprockets/closure_compressor.rb +1 -2
- data/lib/sprockets/coffee_script_processor.rb +9 -3
- data/lib/sprockets/compressing.rb +2 -2
- data/lib/sprockets/configuration.rb +1 -7
- data/lib/sprockets/context.rb +10 -18
- data/lib/sprockets/digest_utils.rb +40 -52
- data/lib/sprockets/directive_processor.rb +10 -15
- data/lib/sprockets/erb_processor.rb +1 -13
- data/lib/sprockets/http_utils.rb +19 -4
- data/lib/sprockets/jsminc_compressor.rb +31 -0
- data/lib/sprockets/jst_processor.rb +10 -10
- data/lib/sprockets/loader.rb +34 -28
- data/lib/sprockets/manifest.rb +3 -35
- data/lib/sprockets/manifest_utils.rb +0 -2
- data/lib/sprockets/mime.rb +7 -62
- data/lib/sprockets/path_dependency_utils.rb +2 -11
- data/lib/sprockets/path_digest_utils.rb +1 -1
- data/lib/sprockets/path_utils.rb +43 -18
- data/lib/sprockets/preprocessors/default_source_map.rb +24 -0
- data/lib/sprockets/processing.rb +30 -61
- data/lib/sprockets/processor_utils.rb +27 -28
- data/lib/sprockets/resolve.rb +172 -92
- data/lib/sprockets/sass_cache_store.rb +1 -6
- data/lib/sprockets/sass_compressor.rb +14 -1
- data/lib/sprockets/sass_processor.rb +18 -8
- data/lib/sprockets/sassc_compressor.rb +30 -0
- data/lib/sprockets/sassc_processor.rb +68 -0
- data/lib/sprockets/server.rb +11 -22
- data/lib/sprockets/source_map_comment_processor.rb +29 -0
- data/lib/sprockets/source_map_processor.rb +40 -0
- data/lib/sprockets/source_map_utils.rb +345 -0
- data/lib/sprockets/transformers.rb +62 -35
- data/lib/sprockets/uglifier_compressor.rb +12 -5
- data/lib/sprockets/unloaded_asset.rb +12 -11
- data/lib/sprockets/uri_tar.rb +4 -2
- data/lib/sprockets/uri_utils.rb +5 -8
- data/lib/sprockets/utils.rb +30 -79
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets.rb +80 -35
- metadata +70 -41
- data/LICENSE +0 -21
- data/lib/sprockets/coffee_script_template.rb +0 -17
- data/lib/sprockets/deprecation.rb +0 -90
- data/lib/sprockets/eco_template.rb +0 -17
- data/lib/sprockets/ejs_template.rb +0 -17
- data/lib/sprockets/engines.rb +0 -92
- data/lib/sprockets/erb_template.rb +0 -11
- data/lib/sprockets/legacy.rb +0 -330
- data/lib/sprockets/legacy_proc_processor.rb +0 -35
- data/lib/sprockets/legacy_tilt_processor.rb +0 -29
- data/lib/sprockets/sass_template.rb +0 -19
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'sprockets/autoload'
|
2
2
|
require 'sprockets/digest_utils'
|
3
|
+
require 'sprockets/source_map_utils'
|
3
4
|
|
4
5
|
module Sprockets
|
5
6
|
# Public: Sass CSS minifier.
|
@@ -45,7 +46,19 @@ module Sprockets
|
|
45
46
|
end
|
46
47
|
|
47
48
|
def call(input)
|
48
|
-
Autoload::Sass::Engine.new(
|
49
|
+
css, map = Autoload::Sass::Engine.new(
|
50
|
+
input[:data],
|
51
|
+
@options.merge(filename: 'filename')
|
52
|
+
).render_with_sourcemap('')
|
53
|
+
|
54
|
+
css = css.sub("/*# sourceMappingURL= */\n", '')
|
55
|
+
|
56
|
+
map = SourceMapUtils.combine_source_maps(
|
57
|
+
input[:metadata][:map],
|
58
|
+
SourceMapUtils.decode_json_source_map(map.to_json(css_uri: 'uri'))["mappings"]
|
59
|
+
)
|
60
|
+
|
61
|
+
{ data: css, map: map }
|
49
62
|
end
|
50
63
|
end
|
51
64
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/utils'
|
2
2
|
require 'sprockets/autoload'
|
3
|
+
require 'sprockets/source_map_utils'
|
3
4
|
require 'uri'
|
4
5
|
|
5
6
|
module Sprockets
|
@@ -45,7 +46,8 @@ module Sprockets
|
|
45
46
|
def initialize(options = {}, &block)
|
46
47
|
@cache_version = options[:cache_version]
|
47
48
|
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
|
48
|
-
|
49
|
+
@importer_class = options[:importer] || Sass::Importers::Filesystem
|
50
|
+
@sass_config = options[:sass_config] || {}
|
49
51
|
@functions = Module.new do
|
50
52
|
include Functions
|
51
53
|
include options[:functions] if options[:functions]
|
@@ -56,24 +58,32 @@ module Sprockets
|
|
56
58
|
def call(input)
|
57
59
|
context = input[:environment].context_class.new(input)
|
58
60
|
|
59
|
-
|
61
|
+
engine_options = {
|
60
62
|
filename: input[:filename],
|
61
63
|
syntax: self.class.syntax,
|
62
64
|
cache_store: build_cache_store(input, @cache_version),
|
63
|
-
load_paths:
|
65
|
+
load_paths: context.environment.paths.map { |p| @importer_class.new(p.to_s) },
|
66
|
+
importer: @importer_class.new(Pathname.new(context.filename).to_s),
|
64
67
|
sprockets: {
|
65
68
|
context: context,
|
66
69
|
environment: input[:environment],
|
67
70
|
dependencies: context.metadata[:dependencies]
|
68
71
|
}
|
69
|
-
}
|
72
|
+
}.merge!(@sass_config)
|
70
73
|
|
71
|
-
engine = Autoload::Sass::Engine.new(input[:data],
|
74
|
+
engine = Autoload::Sass::Engine.new(input[:data], engine_options)
|
72
75
|
|
73
|
-
css = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
|
74
|
-
engine.
|
76
|
+
css, map = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
|
77
|
+
engine.render_with_sourcemap('')
|
75
78
|
end
|
76
79
|
|
80
|
+
css = css.sub("\n/*# sourceMappingURL= */\n", '')
|
81
|
+
|
82
|
+
map = SourceMapUtils.combine_source_maps(
|
83
|
+
input[:metadata][:map],
|
84
|
+
SourceMapUtils.decode_json_source_map(map.to_json(css_uri: '', type: :inline))["mappings"]
|
85
|
+
)
|
86
|
+
|
77
87
|
# Track all imported files
|
78
88
|
sass_dependencies = Set.new([input[:filename]])
|
79
89
|
engine.dependencies.map do |dependency|
|
@@ -81,7 +91,7 @@ module Sprockets
|
|
81
91
|
context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename])
|
82
92
|
end
|
83
93
|
|
84
|
-
context.metadata.merge(data: css, sass_dependencies: sass_dependencies)
|
94
|
+
context.metadata.merge(data: css, sass_dependencies: sass_dependencies, map: map)
|
85
95
|
end
|
86
96
|
|
87
97
|
# Public: Build the cache store to be used by the Sass engine.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sprockets/autoload'
|
2
|
+
require 'sprockets/sass_compressor'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
class SasscCompressor < SassCompressor
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = {
|
9
|
+
syntax: :scss,
|
10
|
+
style: :compressed,
|
11
|
+
source_map_embed: true,
|
12
|
+
source_map_file: '.'
|
13
|
+
}.merge(options).freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
data = Autoload::SassC::Engine.new(input[:data], @options.merge(filename: 'filename')).render
|
18
|
+
|
19
|
+
match_data = data.match(/(.*)\n\/\*# sourceMappingURL=data:application\/json;base64,(.+) \*\//m)
|
20
|
+
css, map = match_data[1], Base64.decode64(match_data[2])
|
21
|
+
|
22
|
+
map = SourceMapUtils.combine_source_maps(
|
23
|
+
input[:metadata][:map],
|
24
|
+
SourceMapUtils.decode_json_source_map(map)["mappings"]
|
25
|
+
)
|
26
|
+
|
27
|
+
{ data: css, map: map }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'sprockets/sass_processor'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
class SasscProcessor < SassProcessor
|
6
|
+
def initialize(options = {}, &block)
|
7
|
+
@cache_version = options[:cache_version]
|
8
|
+
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::SassC::VERSION}:#{@cache_version}".freeze
|
9
|
+
@importer_class = options[:importer]
|
10
|
+
@sass_config = options[:sass_config] || {}
|
11
|
+
@functions = Module.new do
|
12
|
+
include SassProcessor::Functions
|
13
|
+
include options[:functions] if options[:functions]
|
14
|
+
class_eval(&block) if block_given?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(input)
|
19
|
+
context = input[:environment].context_class.new(input)
|
20
|
+
|
21
|
+
options = engine_options(input, context)
|
22
|
+
engine = Autoload::SassC::Engine.new(input[:data], options)
|
23
|
+
|
24
|
+
data = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do
|
25
|
+
engine.render
|
26
|
+
end
|
27
|
+
|
28
|
+
match_data = data.match(/(.*)\n\/\*# sourceMappingURL=data:application\/json;base64,(.+) \*\//m)
|
29
|
+
css, map = match_data[1], Base64.decode64(match_data[2])
|
30
|
+
|
31
|
+
map = SourceMapUtils.combine_source_maps(
|
32
|
+
input[:metadata][:map],
|
33
|
+
change_source(SourceMapUtils.decode_json_source_map(map)["mappings"], input[:source_path])
|
34
|
+
)
|
35
|
+
|
36
|
+
context.metadata.merge(data: css, map: map)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def change_source(mappings, source)
|
42
|
+
mappings.each { |m| m[:source] = source }
|
43
|
+
end
|
44
|
+
|
45
|
+
def engine_options(input, context)
|
46
|
+
{
|
47
|
+
filename: input[:filename],
|
48
|
+
syntax: self.class.syntax,
|
49
|
+
load_paths: input[:environment].paths,
|
50
|
+
importer: @importer_class,
|
51
|
+
source_map_embed: true,
|
52
|
+
source_map_file: '.',
|
53
|
+
sprockets: {
|
54
|
+
context: context,
|
55
|
+
environment: input[:environment],
|
56
|
+
dependencies: context.metadata[:dependencies]
|
57
|
+
}
|
58
|
+
}.merge!(@sass_config)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class ScsscProcessor < SasscProcessor
|
64
|
+
def self.syntax
|
65
|
+
:scss
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/sprockets/server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'set'
|
1
2
|
require 'time'
|
2
3
|
require 'rack/utils'
|
3
4
|
|
@@ -6,6 +7,9 @@ module Sprockets
|
|
6
7
|
# `CachedEnvironment` that provides a Rack compatible `call`
|
7
8
|
# interface and url generation helpers.
|
8
9
|
module Server
|
10
|
+
# Supported HTTP request methods.
|
11
|
+
ALLOWED_REQUEST_METHODS = ['GET', 'HEAD'].to_set.freeze
|
12
|
+
|
9
13
|
# `call` implements the Rack 1.x specification which accepts an
|
10
14
|
# `env` Hash and returns a three item tuple with the status code,
|
11
15
|
# headers, and body.
|
@@ -23,7 +27,7 @@ module Sprockets
|
|
23
27
|
start_time = Time.now.to_f
|
24
28
|
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
25
29
|
|
26
|
-
|
30
|
+
unless ALLOWED_REQUEST_METHODS.include? env['REQUEST_METHOD']
|
27
31
|
return method_not_allowed_response
|
28
32
|
end
|
29
33
|
|
@@ -42,19 +46,6 @@ module Sprockets
|
|
42
46
|
return forbidden_response(env)
|
43
47
|
end
|
44
48
|
|
45
|
-
# Look up the asset.
|
46
|
-
options = {}
|
47
|
-
options[:pipeline] = :self if body_only?(env)
|
48
|
-
|
49
|
-
asset = find_asset(path, options)
|
50
|
-
|
51
|
-
# 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests.
|
52
|
-
# 3.x/4.x prefers strong validation of fingerprint to body contents, but
|
53
|
-
# 2.x just ignored it.
|
54
|
-
if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self"
|
55
|
-
fingerprint = nil
|
56
|
-
end
|
57
|
-
|
58
49
|
if fingerprint
|
59
50
|
if_match = fingerprint
|
60
51
|
elsif env['HTTP_IF_MATCH']
|
@@ -65,6 +56,9 @@ module Sprockets
|
|
65
56
|
if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
|
66
57
|
end
|
67
58
|
|
59
|
+
# Look up the asset.
|
60
|
+
asset = find_asset(path)
|
61
|
+
|
68
62
|
if asset.nil?
|
69
63
|
status = :not_found
|
70
64
|
elsif fingerprint && asset.etag != fingerprint
|
@@ -115,7 +109,7 @@ module Sprockets
|
|
115
109
|
#
|
116
110
|
# http://example.org/assets/../../../etc/passwd
|
117
111
|
#
|
118
|
-
path.include?("..") || absolute_path?(path)
|
112
|
+
path.include?("..") || absolute_path?(path)
|
119
113
|
end
|
120
114
|
|
121
115
|
def head_request?(env)
|
@@ -236,11 +230,6 @@ module Sprockets
|
|
236
230
|
gsub('/', '\\\\002f ')
|
237
231
|
end
|
238
232
|
|
239
|
-
# Test if `?body=1` or `body=true` query param is set
|
240
|
-
def body_only?(env)
|
241
|
-
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
242
|
-
end
|
243
|
-
|
244
233
|
def cache_headers(env, etag)
|
245
234
|
headers = {}
|
246
235
|
|
@@ -251,11 +240,11 @@ module Sprockets
|
|
251
240
|
# If the request url contains a fingerprint, set a long
|
252
241
|
# expires on the response
|
253
242
|
if path_fingerprint(env["PATH_INFO"])
|
254
|
-
headers["Cache-Control"]
|
243
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
255
244
|
|
256
245
|
# Otherwise set `must-revalidate` since the asset could be modified.
|
257
246
|
else
|
258
|
-
headers["Cache-Control"]
|
247
|
+
headers["Cache-Control"] << ", must-revalidate"
|
259
248
|
headers["Vary"] = "Accept-Encoding"
|
260
249
|
end
|
261
250
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sprockets
|
2
|
+
class SourceMapCommentProcessor
|
3
|
+
def self.call(input)
|
4
|
+
case input[:content_type]
|
5
|
+
when "application/javascript"
|
6
|
+
comment = "\n//# sourceMappingURL=%s"
|
7
|
+
map_type = "application/js-sourcemap+json"
|
8
|
+
when "text/css"
|
9
|
+
comment = "\n/*# sourceMappingURL=%s */"
|
10
|
+
map_type = "application/css-sourcemap+json"
|
11
|
+
else
|
12
|
+
fail input[:content_type]
|
13
|
+
end
|
14
|
+
|
15
|
+
env = input[:environment]
|
16
|
+
|
17
|
+
uri, _ = env.resolve!(input[:filename], accept: input[:content_type])
|
18
|
+
asset = env.load(uri)
|
19
|
+
|
20
|
+
uri, _ = env.resolve!(input[:filename], accept: map_type)
|
21
|
+
map = env.load(uri)
|
22
|
+
|
23
|
+
asset.metadata.merge(
|
24
|
+
data: asset.source + (comment % map.digest_path),
|
25
|
+
links: asset.links + [asset.uri, map.uri]
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class SourceMapProcessor
|
5
|
+
def self.call(input)
|
6
|
+
case input[:content_type]
|
7
|
+
when "application/js-sourcemap+json"
|
8
|
+
accept = "application/javascript"
|
9
|
+
when "application/css-sourcemap+json"
|
10
|
+
accept = "text/css"
|
11
|
+
else
|
12
|
+
fail input[:content_type]
|
13
|
+
end
|
14
|
+
|
15
|
+
links = Set.new(input[:metadata][:links])
|
16
|
+
|
17
|
+
env = input[:environment]
|
18
|
+
|
19
|
+
uri, _ = env.resolve!(input[:filename], accept: accept)
|
20
|
+
asset = env.load(uri)
|
21
|
+
map = asset.metadata[:map] || []
|
22
|
+
|
23
|
+
map.map { |m| m[:source] }.uniq.compact.each do |source|
|
24
|
+
# TODO: Resolve should expect fingerprints
|
25
|
+
fingerprint = source[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
|
26
|
+
if fingerprint
|
27
|
+
path = source.sub("-#{fingerprint}", "")
|
28
|
+
else
|
29
|
+
path = source
|
30
|
+
end
|
31
|
+
uri, _ = env.resolve!(path)
|
32
|
+
links << env.load(uri).uri
|
33
|
+
end
|
34
|
+
|
35
|
+
json = env.encode_json_source_map(map, filename: asset.logical_path)
|
36
|
+
|
37
|
+
{ data: json, links: links }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
module SourceMapUtils
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Public: Concatenate two source maps.
|
8
|
+
#
|
9
|
+
# For an example, if two js scripts are concatenated, the individual source
|
10
|
+
# maps for those files can be concatenated to map back to the originals.
|
11
|
+
#
|
12
|
+
# Examples
|
13
|
+
#
|
14
|
+
# script3 = "#{script1}#{script2}"
|
15
|
+
# map3 = concat_source_maps(map1, map2)
|
16
|
+
#
|
17
|
+
# a - Array of source mapping Hashes
|
18
|
+
# b - Array of source mapping Hashes
|
19
|
+
#
|
20
|
+
# Returns a new Array of source mapping Hashes.
|
21
|
+
def concat_source_maps(a, b)
|
22
|
+
a ||= []
|
23
|
+
b ||= []
|
24
|
+
mappings = a.dup
|
25
|
+
|
26
|
+
if a.any?
|
27
|
+
offset = a.last[:generated][0]
|
28
|
+
else
|
29
|
+
offset = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
b.each do |m|
|
33
|
+
mappings << m.merge(generated: [m[:generated][0] + offset, m[:generated][1]])
|
34
|
+
end
|
35
|
+
mappings
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Combine two seperate source map transformations into a single
|
39
|
+
# mapping.
|
40
|
+
#
|
41
|
+
# Source transformations may happen in discrete steps producing separate
|
42
|
+
# source maps. These steps can be combined into a single mapping back to
|
43
|
+
# the source.
|
44
|
+
#
|
45
|
+
# For an example, CoffeeScript may transform a file producing a map. Then
|
46
|
+
# Uglifier processes the result and produces another map. The CoffeeScript
|
47
|
+
# map can be combined with the Uglifier map so the source lines of the
|
48
|
+
# minified output can be traced back to the original CoffeeScript file.
|
49
|
+
#
|
50
|
+
# original_map = [{ :source => "index.coffee", :generated => [2, 0], :original => [1, 0] }]
|
51
|
+
# second_map = [{ :source => "index.js", :generated => [1, 1], :original => [2, 0] }]
|
52
|
+
# combine_source_maps(original_map, second_map)
|
53
|
+
# # => [{:source=>"index.coffee", :generated => [1, 1], :original => [1, 0]}]
|
54
|
+
#
|
55
|
+
# Returns a new Array of source mapping Hashes.
|
56
|
+
def combine_source_maps(original_map, second_map)
|
57
|
+
original_map ||= []
|
58
|
+
return second_map.dup if original_map.empty?
|
59
|
+
|
60
|
+
new_map = []
|
61
|
+
|
62
|
+
second_map.each do |m|
|
63
|
+
original_line = bsearch_mappings(original_map, m[:original])
|
64
|
+
next unless original_line
|
65
|
+
new_map << original_line.merge(generated: m[:generated])
|
66
|
+
end
|
67
|
+
|
68
|
+
new_map
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Compare two source map offsets.
|
72
|
+
#
|
73
|
+
# Compatible with Array#sort.
|
74
|
+
#
|
75
|
+
# a - Array [line, column]
|
76
|
+
# b - Array [line, column]
|
77
|
+
#
|
78
|
+
# Returns -1 if a < b, 0 if a == b and 1 if a > b.
|
79
|
+
def compare_source_offsets(a, b)
|
80
|
+
diff = a[0] - b[0]
|
81
|
+
diff = a[1] - b[1] if diff == 0
|
82
|
+
|
83
|
+
if diff < 0
|
84
|
+
-1
|
85
|
+
elsif diff > 0
|
86
|
+
1
|
87
|
+
else
|
88
|
+
0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: Search Array of mappings for closest offset.
|
93
|
+
#
|
94
|
+
# mappings - Array of mapping Hash objects
|
95
|
+
# offset - Array [line, column]
|
96
|
+
#
|
97
|
+
# Returns mapping Hash object.
|
98
|
+
def bsearch_mappings(mappings, offset, from = 0, to = mappings.size - 1)
|
99
|
+
mid = (from + to) / 2
|
100
|
+
|
101
|
+
if from > to
|
102
|
+
return from < 1 ? nil : mappings[from-1]
|
103
|
+
end
|
104
|
+
|
105
|
+
case compare_source_offsets(offset, mappings[mid][:generated])
|
106
|
+
when 0
|
107
|
+
mappings[mid]
|
108
|
+
when -1
|
109
|
+
bsearch_mappings(mappings, offset, from, mid - 1)
|
110
|
+
when 1
|
111
|
+
bsearch_mappings(mappings, offset, mid + 1, to)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Decode Source Map JSON into Ruby objects.
|
116
|
+
#
|
117
|
+
# json - String source map JSON
|
118
|
+
#
|
119
|
+
# Returns Hash.
|
120
|
+
def decode_json_source_map(json)
|
121
|
+
map = JSON.parse(json)
|
122
|
+
map['mappings'] = decode_vlq_mappings(map['mappings'], sources: map['sources'], names: map['names'])
|
123
|
+
map
|
124
|
+
end
|
125
|
+
|
126
|
+
# Public: Encode mappings to Source Map JSON.
|
127
|
+
#
|
128
|
+
# mappings - Array of Hash or String VLQ encoded mappings
|
129
|
+
# sources - Array of String sources
|
130
|
+
# names - Array of String names
|
131
|
+
# filename - String filename
|
132
|
+
#
|
133
|
+
# Returns JSON String.
|
134
|
+
def encode_json_source_map(mappings, sources: nil, names: nil, filename: nil)
|
135
|
+
case mappings
|
136
|
+
when String
|
137
|
+
when Array
|
138
|
+
sources ||= mappings.map { |m| m[:source] }.uniq.compact
|
139
|
+
names ||= mappings.map { |m| m[:name] }.uniq.compact
|
140
|
+
mappings = encode_vlq_mappings(mappings, sources: sources, names: names)
|
141
|
+
else
|
142
|
+
raise TypeError, "could not encode mappings: #{mappings}"
|
143
|
+
end
|
144
|
+
|
145
|
+
JSON.generate({
|
146
|
+
"version" => 3,
|
147
|
+
"file" => filename,
|
148
|
+
"mappings" => mappings,
|
149
|
+
"sources" => sources,
|
150
|
+
"names" => names
|
151
|
+
})
|
152
|
+
end
|
153
|
+
|
154
|
+
# Public: Decode VLQ mappings and match up sources and symbol names.
|
155
|
+
#
|
156
|
+
# str - VLQ string from 'mappings' attribute
|
157
|
+
# sources - Array of Strings from 'sources' attribute
|
158
|
+
# names - Array of Strings from 'names' attribute
|
159
|
+
#
|
160
|
+
# Returns an Array of Mappings.
|
161
|
+
def decode_vlq_mappings(str, sources: [], names: [])
|
162
|
+
mappings = []
|
163
|
+
|
164
|
+
source_id = 0
|
165
|
+
original_line = 1
|
166
|
+
original_column = 0
|
167
|
+
name_id = 0
|
168
|
+
|
169
|
+
vlq_decode_mappings(str).each_with_index do |group, index|
|
170
|
+
generated_column = 0
|
171
|
+
generated_line = index + 1
|
172
|
+
|
173
|
+
group.each do |segment|
|
174
|
+
generated_column += segment[0]
|
175
|
+
generated = [generated_line, generated_column]
|
176
|
+
|
177
|
+
if segment.size >= 4
|
178
|
+
source_id += segment[1]
|
179
|
+
original_line += segment[2]
|
180
|
+
original_column += segment[3]
|
181
|
+
|
182
|
+
source = sources[source_id]
|
183
|
+
original = [original_line, original_column]
|
184
|
+
else
|
185
|
+
# TODO: Research this case
|
186
|
+
next
|
187
|
+
end
|
188
|
+
|
189
|
+
if segment[4]
|
190
|
+
name_id += segment[4]
|
191
|
+
name = names[name_id]
|
192
|
+
end
|
193
|
+
|
194
|
+
mapping = {source: source, generated: generated, original: original}
|
195
|
+
mapping[:name] = name if name
|
196
|
+
mappings << mapping
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
mappings
|
201
|
+
end
|
202
|
+
|
203
|
+
# Public: Encode mappings Hash into a VLQ encoded String.
|
204
|
+
#
|
205
|
+
# mappings - Array of Hash mapping objects
|
206
|
+
# sources - Array of String sources (default: mappings source order)
|
207
|
+
# names - Array of String names (default: mappings name order)
|
208
|
+
#
|
209
|
+
# Returns a VLQ encoded String.
|
210
|
+
def encode_vlq_mappings(mappings, sources: nil, names: nil)
|
211
|
+
sources ||= mappings.map { |m| m[:source] }.uniq.compact
|
212
|
+
names ||= mappings.map { |m| m[:name] }.uniq.compact
|
213
|
+
|
214
|
+
sources_index = Hash[sources.each_with_index.to_a]
|
215
|
+
names_index = Hash[names.each_with_index.to_a]
|
216
|
+
|
217
|
+
source_id = 0
|
218
|
+
source_line = 1
|
219
|
+
source_column = 0
|
220
|
+
name_id = 0
|
221
|
+
|
222
|
+
by_lines = mappings.group_by { |m| m[:generated][0] }
|
223
|
+
|
224
|
+
ary = (1..(by_lines.keys.max || 1)).map do |line|
|
225
|
+
generated_column = 0
|
226
|
+
|
227
|
+
(by_lines[line] || []).map do |mapping|
|
228
|
+
group = []
|
229
|
+
group << mapping[:generated][1] - generated_column
|
230
|
+
group << sources_index[mapping[:source]] - source_id
|
231
|
+
group << mapping[:original][0] - source_line
|
232
|
+
group << mapping[:original][1] - source_column
|
233
|
+
group << names_index[mapping[:name]] - name_id if mapping[:name]
|
234
|
+
|
235
|
+
generated_column = mapping[:generated][1]
|
236
|
+
source_id = sources_index[mapping[:source]]
|
237
|
+
source_line = mapping[:original][0]
|
238
|
+
source_column = mapping[:original][1]
|
239
|
+
name_id = names_index[mapping[:name]] if mapping[:name]
|
240
|
+
|
241
|
+
group
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
vlq_encode_mappings(ary)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Public: Base64 VLQ encoding
|
249
|
+
#
|
250
|
+
# Adopted from ConradIrwin/ruby-source_map
|
251
|
+
# https://github.com/ConradIrwin/ruby-source_map/blob/master/lib/source_map/vlq.rb
|
252
|
+
#
|
253
|
+
# Resources
|
254
|
+
#
|
255
|
+
# http://en.wikipedia.org/wiki/Variable-length_quantity
|
256
|
+
# https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
257
|
+
# https://github.com/mozilla/source-map/blob/master/lib/source-map/base64-vlq.js
|
258
|
+
#
|
259
|
+
VLQ_BASE_SHIFT = 5
|
260
|
+
VLQ_BASE = 1 << VLQ_BASE_SHIFT
|
261
|
+
VLQ_BASE_MASK = VLQ_BASE - 1
|
262
|
+
VLQ_CONTINUATION_BIT = VLQ_BASE
|
263
|
+
|
264
|
+
BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
|
265
|
+
BASE64_VALUES = (0...64).inject({}) { |h, i| h[BASE64_DIGITS[i]] = i; h }
|
266
|
+
|
267
|
+
# Public: Encode a list of numbers into a compact VLQ string.
|
268
|
+
#
|
269
|
+
# ary - An Array of Integers
|
270
|
+
#
|
271
|
+
# Returns a VLQ String.
|
272
|
+
def vlq_encode(ary)
|
273
|
+
result = []
|
274
|
+
ary.each do |n|
|
275
|
+
vlq = n < 0 ? ((-n) << 1) + 1 : n << 1
|
276
|
+
loop do
|
277
|
+
digit = vlq & VLQ_BASE_MASK
|
278
|
+
vlq >>= VLQ_BASE_SHIFT
|
279
|
+
digit |= VLQ_CONTINUATION_BIT if vlq > 0
|
280
|
+
result << BASE64_DIGITS[digit]
|
281
|
+
|
282
|
+
break unless vlq > 0
|
283
|
+
end
|
284
|
+
end
|
285
|
+
result.join
|
286
|
+
end
|
287
|
+
|
288
|
+
# Public: Decode a VLQ string.
|
289
|
+
#
|
290
|
+
# str - VLQ encoded String
|
291
|
+
#
|
292
|
+
# Returns an Array of Integers.
|
293
|
+
def vlq_decode(str)
|
294
|
+
result = []
|
295
|
+
chars = str.split('')
|
296
|
+
while chars.any?
|
297
|
+
vlq = 0
|
298
|
+
shift = 0
|
299
|
+
continuation = true
|
300
|
+
while continuation
|
301
|
+
char = chars.shift
|
302
|
+
raise ArgumentError unless char
|
303
|
+
digit = BASE64_VALUES[char]
|
304
|
+
continuation = false if (digit & VLQ_CONTINUATION_BIT) == 0
|
305
|
+
digit &= VLQ_BASE_MASK
|
306
|
+
vlq += digit << shift
|
307
|
+
shift += VLQ_BASE_SHIFT
|
308
|
+
end
|
309
|
+
result << (vlq & 1 == 1 ? -(vlq >> 1) : vlq >> 1)
|
310
|
+
end
|
311
|
+
result
|
312
|
+
end
|
313
|
+
|
314
|
+
# Public: Encode a mapping array into a compact VLQ string.
|
315
|
+
#
|
316
|
+
# ary - Two dimensional Array of Integers.
|
317
|
+
#
|
318
|
+
# Returns a VLQ encoded String seperated by , and ;.
|
319
|
+
def vlq_encode_mappings(ary)
|
320
|
+
ary.map { |group|
|
321
|
+
group.map { |segment|
|
322
|
+
vlq_encode(segment)
|
323
|
+
}.join(',')
|
324
|
+
}.join(';')
|
325
|
+
end
|
326
|
+
|
327
|
+
# Public: Decode a VLQ string into mapping numbers.
|
328
|
+
#
|
329
|
+
# str - VLQ encoded String
|
330
|
+
#
|
331
|
+
# Returns an two dimensional Array of Integers.
|
332
|
+
def vlq_decode_mappings(str)
|
333
|
+
mappings = []
|
334
|
+
|
335
|
+
str.split(';').each_with_index do |group, index|
|
336
|
+
mappings[index] = []
|
337
|
+
group.split(',').each do |segment|
|
338
|
+
mappings[index] << vlq_decode(segment)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
mappings
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|