sprockets 3.7.3 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -261
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +527 -320
  5. data/bin/sprockets +11 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +39 -27
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +49 -12
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +25 -3
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +37 -2
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +1 -0
  31. data/lib/sprockets/coffee_script_processor.rb +19 -5
  32. data/lib/sprockets/compressing.rb +43 -3
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +2 -1
  36. data/lib/sprockets/digest_utils.rb +35 -18
  37. data/lib/sprockets/directive_processor.rb +64 -38
  38. data/lib/sprockets/eco_processor.rb +2 -1
  39. data/lib/sprockets/ejs_processor.rb +2 -1
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +33 -32
  43. data/lib/sprockets/errors.rb +1 -0
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +91 -69
  54. data/lib/sprockets/manifest.rb +67 -64
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -62
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +88 -8
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -62
  64. data/lib/sprockets/processor_utils.rb +28 -38
  65. data/lib/sprockets/resolve.rb +177 -93
  66. data/lib/sprockets/sass_cache_store.rb +2 -6
  67. data/lib/sprockets/sass_compressor.rb +13 -1
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +31 -10
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +63 -40
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +21 -11
  78. data/lib/sprockets/unloaded_asset.rb +13 -11
  79. data/lib/sprockets/uri_tar.rb +1 -0
  80. data/lib/sprockets/uri_utils.rb +12 -12
  81. data/lib/sprockets/utils/gzip.rb +46 -14
  82. data/lib/sprockets/utils.rb +64 -90
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +1 -0
  85. data/lib/sprockets.rb +102 -39
  86. metadata +138 -46
  87. data/lib/sprockets/coffee_script_template.rb +0 -17
  88. data/lib/sprockets/deprecation.rb +0 -90
  89. data/lib/sprockets/eco_template.rb +0 -17
  90. data/lib/sprockets/ejs_template.rb +0 -17
  91. data/lib/sprockets/engines.rb +0 -92
  92. data/lib/sprockets/erb_template.rb +0 -11
  93. data/lib/sprockets/legacy.rb +0 -330
  94. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  95. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  96. data/lib/sprockets/sass_template.rb +0 -19
@@ -0,0 +1,483 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+ require 'sprockets/path_utils'
4
+
5
+ module Sprockets
6
+ module SourceMapUtils
7
+ extend self
8
+
9
+ # Public: Transpose source maps into a standard format
10
+ #
11
+ # NOTE: Does not support index maps
12
+ #
13
+ # version => 3
14
+ # file => logical path
15
+ # sources => relative from filename
16
+ #
17
+ # Unnecessary attributes are removed
18
+ #
19
+ # Example
20
+ #
21
+ # map
22
+ # #=> {
23
+ # # "version" => 3,
24
+ # # "file" => "stdin",
25
+ # # "sourceRoot" => "",
26
+ # # "sourceContents" => "blah blah blah",
27
+ # # "sources" => [/root/logical/path.js],
28
+ # # "names" => [..],
29
+ # #}
30
+ # format_source_map(map, input)
31
+ # #=> {
32
+ # # "version" => 3,
33
+ # # "file" => "logical/path.js",
34
+ # # "sources" => ["path.js"],
35
+ # # "names" => [..],
36
+ # #}
37
+ def format_source_map(map, input)
38
+ filename = input[:filename]
39
+ load_path = input[:load_path]
40
+ load_paths = input[:environment].config[:paths]
41
+ mime_exts = input[:environment].config[:mime_exts]
42
+ pipeline_exts = input[:environment].config[:pipeline_exts]
43
+ file = PathUtils.split_subpath(load_path, filename)
44
+ {
45
+ "version" => 3,
46
+ "file" => file,
47
+ "mappings" => map["mappings"],
48
+ "sources" => map["sources"].map do |source|
49
+ source = URIUtils.split_file_uri(source)[2] if source.start_with? "file://"
50
+ source = PathUtils.join(File.dirname(filename), source) unless PathUtils.absolute_path?(source)
51
+ _, source = PathUtils.paths_split(load_paths, source)
52
+ source = PathUtils.relative_path_from(file, source)
53
+ PathUtils.set_pipeline(source, mime_exts, pipeline_exts, :source)
54
+ end,
55
+ "names" => map["names"]
56
+ }
57
+ end
58
+
59
+ # Public: Concatenate two source maps.
60
+ #
61
+ # For an example, if two js scripts are concatenated, the individual source
62
+ # maps for those files can be concatenated to map back to the originals.
63
+ #
64
+ # Examples
65
+ #
66
+ # script3 = "#{script1}#{script2}"
67
+ # map3 = concat_source_maps(map1, map2)
68
+ #
69
+ # a - Source map hash
70
+ # b - Source map hash
71
+ #
72
+ # Returns a new source map hash.
73
+ def concat_source_maps(a, b)
74
+ return a || b unless a && b
75
+ a = make_index_map(a)
76
+ b = make_index_map(b)
77
+
78
+ offset = 0
79
+ if a["sections"].count != 0 && !a["sections"].last["map"]["mappings"].empty?
80
+ last_line_count = a["sections"].last["map"].delete("x_sprockets_linecount")
81
+ offset += last_line_count || 1
82
+
83
+ last_offset = a["sections"].last["offset"]["line"]
84
+ offset += last_offset
85
+ end
86
+
87
+ a["sections"] += b["sections"].map do |section|
88
+ {
89
+ "offset" => section["offset"].merge({ "line" => section["offset"]["line"] + offset }),
90
+ "map" => section["map"].merge({
91
+ "sources" => section["map"]["sources"].map do |source|
92
+ PathUtils.relative_path_from(a["file"], PathUtils.join(File.dirname(b["file"]), source))
93
+ end
94
+ })
95
+ }
96
+ end
97
+ a
98
+ end
99
+
100
+ # Public: Converts source map to index map
101
+ #
102
+ # Example:
103
+ #
104
+ # map
105
+ # # => {
106
+ # "version" => 3,
107
+ # "file" => "..",
108
+ # "mappings" => "AAAA;AACA;..;AACA",
109
+ # "sources" => [..],
110
+ # "names" => [..]
111
+ # }
112
+ # make_index_map(map)
113
+ # # => {
114
+ # "version" => 3,
115
+ # "file" => "..",
116
+ # "sections" => [
117
+ # {
118
+ # "offset" => { "line" => 0, "column" => 0 },
119
+ # "map" => {
120
+ # "version" => 3,
121
+ # "file" => "..",
122
+ # "mappings" => "AAAA;AACA;..;AACA",
123
+ # "sources" => [..],
124
+ # "names" => [..]
125
+ # }
126
+ # }
127
+ # ]
128
+ # }
129
+ def make_index_map(map)
130
+ return map if map.key? "sections"
131
+ {
132
+ "version" => map["version"],
133
+ "file" => map["file"],
134
+ "sections" => [
135
+ {
136
+ "offset" => { "line" => 0, "column" => 0 },
137
+ "map" => map
138
+ }
139
+ ]
140
+ }
141
+ end
142
+
143
+ # Public: Combine two separate source map transformations into a single
144
+ # mapping.
145
+ #
146
+ # Source transformations may happen in discrete steps producing separate
147
+ # source maps. These steps can be combined into a single mapping back to
148
+ # the source.
149
+ #
150
+ # For an example, CoffeeScript may transform a file producing a map. Then
151
+ # Uglifier processes the result and produces another map. The CoffeeScript
152
+ # map can be combined with the Uglifier map so the source lines of the
153
+ # minified output can be traced back to the original CoffeeScript file.
154
+ #
155
+ # Returns a source map hash.
156
+ def combine_source_maps(first, second)
157
+ return second unless first
158
+
159
+ _first = decode_source_map(first)
160
+ _second = decode_source_map(second)
161
+
162
+ new_mappings = []
163
+
164
+ _second[:mappings].each do |m|
165
+ first_line = bsearch_mappings(_first[:mappings], m[:original])
166
+ new_mappings << first_line.merge(generated: m[:generated]) if first_line
167
+ end
168
+
169
+ _first[:mappings] = new_mappings
170
+
171
+ encode_source_map(_first)
172
+ end
173
+
174
+ # Public: Decompress source map
175
+ #
176
+ # Example:
177
+ #
178
+ # decode_source_map(map)
179
+ # # => {
180
+ # version: 3,
181
+ # file: "..",
182
+ # mappings: [
183
+ # { source: "..", generated: [0, 0], original: [0, 0], name: ".."}, ..
184
+ # ],
185
+ # sources: [..],
186
+ # names: [..]
187
+ # }
188
+ #
189
+ # map - Source map hash (v3 spec)
190
+ #
191
+ # Returns an uncompressed source map hash
192
+ def decode_source_map(map)
193
+ return nil unless map
194
+
195
+ mappings, sources, names = [], [], []
196
+ if map["sections"]
197
+ map["sections"].each do |s|
198
+ mappings += decode_source_map(s["map"])[:mappings].each do |m|
199
+ m[:generated][0] += s["offset"]["line"]
200
+ m[:generated][1] += s["offset"]["column"]
201
+ end
202
+ sources |= s["map"]["sources"]
203
+ names |= s["map"]["names"]
204
+ end
205
+ else
206
+ mappings = decode_vlq_mappings(map["mappings"], sources: map["sources"], names: map["names"])
207
+ sources = map["sources"]
208
+ names = map["names"]
209
+ end
210
+ {
211
+ version: 3,
212
+ file: map["file"],
213
+ mappings: mappings,
214
+ sources: sources,
215
+ names: names
216
+ }
217
+ end
218
+
219
+ # Public: Compress source map
220
+ #
221
+ # Example:
222
+ #
223
+ # encode_source_map(map)
224
+ # # => {
225
+ # "version" => 3,
226
+ # "file" => "..",
227
+ # "mappings" => "AAAA;AACA;..;AACA",
228
+ # "sources" => [..],
229
+ # "names" => [..]
230
+ # }
231
+ #
232
+ # map - Source map hash (uncompressed)
233
+ #
234
+ # Returns a compressed source map hash according to source map spec v3
235
+ def encode_source_map(map)
236
+ return nil unless map
237
+ {
238
+ "version" => map[:version],
239
+ "file" => map[:file],
240
+ "mappings" => encode_vlq_mappings(map[:mappings], sources: map[:sources], names: map[:names]),
241
+ "sources" => map[:sources],
242
+ "names" => map[:names]
243
+ }
244
+ end
245
+
246
+ # Public: Compare two source map offsets.
247
+ #
248
+ # Compatible with Array#sort.
249
+ #
250
+ # a - Array [line, column]
251
+ # b - Array [line, column]
252
+ #
253
+ # Returns -1 if a < b, 0 if a == b and 1 if a > b.
254
+ def compare_source_offsets(a, b)
255
+ diff = a[0] - b[0]
256
+ diff = a[1] - b[1] if diff == 0
257
+
258
+ if diff < 0
259
+ -1
260
+ elsif diff > 0
261
+ 1
262
+ else
263
+ 0
264
+ end
265
+ end
266
+
267
+ # Public: Search Array of mappings for closest offset.
268
+ #
269
+ # mappings - Array of mapping Hash objects
270
+ # offset - Array [line, column]
271
+ #
272
+ # Returns mapping Hash object.
273
+ def bsearch_mappings(mappings, offset, from = 0, to = mappings.size - 1)
274
+ mid = (from + to) / 2
275
+
276
+ if from > to
277
+ return from < 1 ? nil : mappings[from-1]
278
+ end
279
+
280
+ case compare_source_offsets(offset, mappings[mid][:generated])
281
+ when 0
282
+ mappings[mid]
283
+ when -1
284
+ bsearch_mappings(mappings, offset, from, mid - 1)
285
+ when 1
286
+ bsearch_mappings(mappings, offset, mid + 1, to)
287
+ end
288
+ end
289
+
290
+ # Public: Decode VLQ mappings and match up sources and symbol names.
291
+ #
292
+ # str - VLQ string from 'mappings' attribute
293
+ # sources - Array of Strings from 'sources' attribute
294
+ # names - Array of Strings from 'names' attribute
295
+ #
296
+ # Returns an Array of Mappings.
297
+ def decode_vlq_mappings(str, sources: [], names: [])
298
+ mappings = []
299
+
300
+ source_id = 0
301
+ original_line = 1
302
+ original_column = 0
303
+ name_id = 0
304
+
305
+ vlq_decode_mappings(str).each_with_index do |group, index|
306
+ generated_column = 0
307
+ generated_line = index + 1
308
+
309
+ group.each do |segment|
310
+ generated_column += segment[0]
311
+ generated = [generated_line, generated_column]
312
+
313
+ if segment.size >= 4
314
+ source_id += segment[1]
315
+ original_line += segment[2]
316
+ original_column += segment[3]
317
+
318
+ source = sources[source_id]
319
+ original = [original_line, original_column]
320
+ else
321
+ # TODO: Research this case
322
+ next
323
+ end
324
+
325
+ if segment[4]
326
+ name_id += segment[4]
327
+ name = names[name_id]
328
+ end
329
+
330
+ mapping = {source: source, generated: generated, original: original}
331
+ mapping[:name] = name if name
332
+ mappings << mapping
333
+ end
334
+ end
335
+
336
+ mappings
337
+ end
338
+
339
+ # Public: Encode mappings Hash into a VLQ encoded String.
340
+ #
341
+ # mappings - Array of Hash mapping objects
342
+ # sources - Array of String sources (default: mappings source order)
343
+ # names - Array of String names (default: mappings name order)
344
+ #
345
+ # Returns a VLQ encoded String.
346
+ def encode_vlq_mappings(mappings, sources: nil, names: nil)
347
+ sources ||= mappings.map { |m| m[:source] }.uniq.compact
348
+ names ||= mappings.map { |m| m[:name] }.uniq.compact
349
+
350
+ sources_index = Hash[sources.each_with_index.to_a]
351
+ names_index = Hash[names.each_with_index.to_a]
352
+
353
+ source_id = 0
354
+ source_line = 1
355
+ source_column = 0
356
+ name_id = 0
357
+
358
+ by_lines = mappings.group_by { |m| m[:generated][0] }
359
+
360
+ ary = (1..(by_lines.keys.max || 1)).map do |line|
361
+ generated_column = 0
362
+
363
+ (by_lines[line] || []).map do |mapping|
364
+ group = []
365
+ group << mapping[:generated][1] - generated_column
366
+ group << sources_index[mapping[:source]] - source_id
367
+ group << mapping[:original][0] - source_line
368
+ group << mapping[:original][1] - source_column
369
+ group << names_index[mapping[:name]] - name_id if mapping[:name]
370
+
371
+ generated_column = mapping[:generated][1]
372
+ source_id = sources_index[mapping[:source]]
373
+ source_line = mapping[:original][0]
374
+ source_column = mapping[:original][1]
375
+ name_id = names_index[mapping[:name]] if mapping[:name]
376
+
377
+ group
378
+ end
379
+ end
380
+
381
+ vlq_encode_mappings(ary)
382
+ end
383
+
384
+ # Public: Base64 VLQ encoding
385
+ #
386
+ # Adopted from ConradIrwin/ruby-source_map
387
+ # https://github.com/ConradIrwin/ruby-source_map/blob/master/lib/source_map/vlq.rb
388
+ #
389
+ # Resources
390
+ #
391
+ # http://en.wikipedia.org/wiki/Variable-length_quantity
392
+ # https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
393
+ # https://github.com/mozilla/source-map/blob/master/lib/source-map/base64-vlq.js
394
+ #
395
+ VLQ_BASE_SHIFT = 5
396
+ VLQ_BASE = 1 << VLQ_BASE_SHIFT
397
+ VLQ_BASE_MASK = VLQ_BASE - 1
398
+ VLQ_CONTINUATION_BIT = VLQ_BASE
399
+
400
+ BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
401
+ BASE64_VALUES = (0...64).inject({}) { |h, i| h[BASE64_DIGITS[i]] = i; h }
402
+
403
+ # Public: Encode a list of numbers into a compact VLQ string.
404
+ #
405
+ # ary - An Array of Integers
406
+ #
407
+ # Returns a VLQ String.
408
+ def vlq_encode(ary)
409
+ result = []
410
+ ary.each do |n|
411
+ vlq = n < 0 ? ((-n) << 1) + 1 : n << 1
412
+ loop do
413
+ digit = vlq & VLQ_BASE_MASK
414
+ vlq >>= VLQ_BASE_SHIFT
415
+ digit |= VLQ_CONTINUATION_BIT if vlq > 0
416
+ result << BASE64_DIGITS[digit]
417
+
418
+ break unless vlq > 0
419
+ end
420
+ end
421
+ result.join
422
+ end
423
+
424
+ # Public: Decode a VLQ string.
425
+ #
426
+ # str - VLQ encoded String
427
+ #
428
+ # Returns an Array of Integers.
429
+ def vlq_decode(str)
430
+ result = []
431
+ shift = 0
432
+ value = 0
433
+ i = 0
434
+
435
+ while i < str.size do
436
+ digit = BASE64_VALUES[str[i]]
437
+ raise ArgumentError unless digit
438
+ continuation = (digit & VLQ_CONTINUATION_BIT) != 0
439
+ digit &= VLQ_BASE_MASK
440
+ value += digit << shift
441
+ if continuation
442
+ shift += VLQ_BASE_SHIFT
443
+ else
444
+ result << ((value & 1) == 1 ? -(value >> 1) : value >> 1)
445
+ value = shift = 0
446
+ end
447
+ i += 1
448
+ end
449
+ result
450
+ end
451
+
452
+ # Public: Encode a mapping array into a compact VLQ string.
453
+ #
454
+ # ary - Two dimensional Array of Integers.
455
+ #
456
+ # Returns a VLQ encoded String separated by , and ;.
457
+ def vlq_encode_mappings(ary)
458
+ ary.map { |group|
459
+ group.map { |segment|
460
+ vlq_encode(segment)
461
+ }.join(',')
462
+ }.join(';')
463
+ end
464
+
465
+ # Public: Decode a VLQ string into mapping numbers.
466
+ #
467
+ # str - VLQ encoded String
468
+ #
469
+ # Returns an two dimensional Array of Integers.
470
+ def vlq_decode_mappings(str)
471
+ mappings = []
472
+
473
+ str.split(';').each_with_index do |group, index|
474
+ mappings[index] = []
475
+ group.split(',').each do |segment|
476
+ mappings[index] << vlq_decode(segment)
477
+ end
478
+ end
479
+
480
+ mappings
481
+ end
482
+ end
483
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/http_utils'
2
3
  require 'sprockets/processor_utils'
3
4
  require 'sprockets/utils'
@@ -18,6 +19,8 @@ module Sprockets
18
19
  config[:transformers]
19
20
  end
20
21
 
22
+ Transformer = Struct.new :from, :to, :proc
23
+
21
24
  # Public: Register a transformer from and to a mime type.
22
25
  #
23
26
  # from - String mime type
@@ -33,10 +36,30 @@ module Sprockets
33
36
  #
34
37
  # Returns nothing.
35
38
  def register_transformer(from, to, proc)
36
- self.config = hash_reassoc(config, :registered_transformers, from) do |transformers|
37
- transformers.merge(to => proc)
39
+ self.config = hash_reassoc(config, :registered_transformers) do |transformers|
40
+ transformers << Transformer.new(from, to, proc)
41
+ end
42
+ compute_transformers!(self.config[:registered_transformers])
43
+ end
44
+
45
+ # Internal: Register transformer for existing type adding a suffix.
46
+ #
47
+ # types - Array of existing mime type Strings
48
+ # type_format - String suffix formatting string
49
+ # extname - String extension to append
50
+ # processor - Callable block that accepts an input Hash.
51
+ #
52
+ # Returns nothing.
53
+ def register_transformer_suffix(types, type_format, extname, processor)
54
+ Array(types).each do |type|
55
+ extensions, charset = mime_types[type].values_at(:extensions, :charset)
56
+ parts = type.split('/')
57
+ suffix_type = type_format.sub('\1', parts[0]).sub('\2', parts[1])
58
+ extensions = extensions.map { |ext| "#{ext}#{extname}" }
59
+
60
+ register_mime_type(suffix_type, extensions: extensions, charset: charset)
61
+ register_transformer(suffix_type, type, processor)
38
62
  end
39
- compute_transformers!
40
63
  end
41
64
 
42
65
  # Internal: Resolve target mime type that the source type should be
@@ -89,53 +112,58 @@ module Sprockets
89
112
  # types - Array of mime type steps
90
113
  #
91
114
  # Returns Processor.
92
- def compose_transformers(transformers, types)
115
+ def compose_transformers(transformers, types, preprocessors, postprocessors)
93
116
  if types.length < 2
94
117
  raise ArgumentError, "too few transform types: #{types.inspect}"
95
118
  end
96
119
 
97
- i = 0
98
- processors = []
99
-
100
- loop do
101
- src = types[i]
102
- dst = types[i+1]
103
- break unless src && dst
104
-
120
+ processors = types.each_cons(2).map { |src, dst|
105
121
  unless processor = transformers[src][dst]
106
122
  raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
107
123
  end
108
- processors.concat config[:postprocessors][src]
109
- processors << processor
110
- processors.concat config[:preprocessors][dst]
124
+ processor
125
+ }
111
126
 
112
- i += 1
113
- end
114
-
115
- if processors.size > 1
116
- compose_processors(*processors.reverse)
117
- elsif processors.size == 1
118
- processors.first
119
- end
127
+ compose_transformer_list processors, preprocessors, postprocessors
120
128
  end
121
129
 
122
130
  private
123
- def compute_transformers!
124
- registered_transformers = self.config[:registered_transformers]
125
- transformers = Hash.new { {} }
131
+ def compose_transformer_list(transformers, preprocessors, postprocessors)
132
+ processors = []
133
+
134
+ transformers.each do |processor|
135
+ processors.concat postprocessors[processor.from]
136
+ processors << processor.proc
137
+ processors.concat preprocessors[processor.to]
138
+ end
139
+
140
+ if processors.size > 1
141
+ compose_processors(*processors.reverse)
142
+ elsif processors.size == 1
143
+ processors.first
144
+ end
145
+ end
146
+
147
+ def compute_transformers!(registered_transformers)
148
+ preprocessors = self.config[:preprocessors]
149
+ postprocessors = self.config[:postprocessors]
150
+ transformers = Hash.new { {} }
126
151
  inverted_transformers = Hash.new { Set.new }
152
+ incoming_edges = registered_transformers.group_by(&:from)
153
+
154
+ registered_transformers.each do |t|
155
+ traversals = dfs_paths([t]) { |k| incoming_edges.fetch(k.to, []) }
127
156
 
128
- registered_transformers.keys.flat_map do |key|
129
- dfs_paths([key]) { |k| registered_transformers[k].keys }
130
- end.each do |types|
131
- src, dst = types.first, types.last
132
- processor = compose_transformers(registered_transformers, types)
157
+ traversals.each do |nodes|
158
+ src, dst = nodes.first.from, nodes.last.to
159
+ processor = compose_transformer_list nodes, preprocessors, postprocessors
133
160
 
134
- transformers[src] = {} unless transformers.key?(src)
135
- transformers[src][dst] = processor
161
+ transformers[src] = {} unless transformers.key?(src)
162
+ transformers[src][dst] = processor
136
163
 
137
- inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
138
- inverted_transformers[dst] << src
164
+ inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
165
+ inverted_transformers[dst] << src
166
+ end
139
167
  end
140
168
 
141
169
  self.config = hash_reassoc(config, :transformers) { transformers }
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/autoload'
2
3
  require 'sprockets/digest_utils'
4
+ require 'sprockets/source_map_utils'
3
5
 
4
6
  module Sprockets
5
7
  # Public: Uglifier/Uglify compressor.
@@ -15,7 +17,7 @@ module Sprockets
15
17
  # Sprockets::UglifierCompressor.new(comments: :copyright)
16
18
  #
17
19
  class UglifierCompressor
18
- VERSION = '1'
20
+ VERSION = '3'
19
21
 
20
22
  # Public: Return singleton instance with default options.
21
23
  #
@@ -35,22 +37,30 @@ module Sprockets
35
37
  attr_reader :cache_key
36
38
 
37
39
  def initialize(options = {})
38
- # Feature detect Uglifier 2.0 option support
39
- if Autoload::Uglifier::DEFAULTS[:copyright]
40
- # Uglifier < 2.x
41
- options[:copyright] ||= false
42
- else
43
- # Uglifier >= 2.x
44
- options[:comments] ||= :none
45
- end
40
+ options[:comments] ||= :none
46
41
 
47
42
  @options = options
48
43
  @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
49
44
  end
50
45
 
51
46
  def call(input)
52
- @uglifier ||= Autoload::Uglifier.new(@options)
53
- @uglifier.compile(input[:data])
47
+ case Autoload::Uglifier::VERSION.to_i
48
+ when 1
49
+ raise "uglifier 1.x is no longer supported, please upgrade to 2.x or newer"
50
+ when 2
51
+ input_options = { source_filename: input[:filename] }
52
+ else
53
+ input_options = { source_map: { filename: input[:filename] } }
54
+ end
55
+
56
+ uglifier = Autoload::Uglifier.new(@options.merge(input_options))
57
+
58
+ js, map = uglifier.compile_with_map(input[:data])
59
+
60
+ map = SourceMapUtils.format_source_map(JSON.parse(map), input)
61
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
62
+
63
+ { data: js, map: map }
54
64
  end
55
65
  end
56
66
  end