sprockets 3.7.3 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|