sprockets 3.0.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -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 +41 -28
  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 +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  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 +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  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 +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  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 +107 -22
  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 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  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 +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  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 +23 -20
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. data/lib/sprockets/sass_template.rb +0 -7
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/uri_utils'
3
+ require 'sprockets/uri_tar'
4
+
5
+ module Sprockets
6
+ # Internal: Used to parse and store the URI to an unloaded asset
7
+ # Generates keys used to store and retrieve items from cache
8
+ class UnloadedAsset
9
+
10
+ # Internal: Initialize object for generating cache keys
11
+ #
12
+ # uri - A String containing complete URI to a file including scheme
13
+ # and full path such as
14
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
15
+ # env - The current "environment" that assets are being loaded into.
16
+ # We need it so we know where the +root+ (directory where Sprockets
17
+ # is being invoked). We also need it for the `file_digest` method,
18
+ # since, for some strange reason, memoization is provided by
19
+ # overriding methods such as `stat` in the `PathUtils` module.
20
+ #
21
+ # Returns UnloadedAsset.
22
+ def initialize(uri, env)
23
+ @uri = uri.to_s
24
+ @env = env
25
+ @compressed_path = URITar.new(uri, env).compressed_path
26
+ @params = nil # lazy loaded
27
+ @filename = nil # lazy loaded
28
+ end
29
+ attr_reader :compressed_path, :uri
30
+
31
+ # Internal: Full file path without schema
32
+ #
33
+ # This returns a string containing the full path to the asset without the schema.
34
+ # Information is loaded lazily since we want `UnloadedAsset.new(dep, self).relative_path`
35
+ # to be fast. Calling this method the first time allocates an array and a hash.
36
+ #
37
+ # Example
38
+ #
39
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
40
+ # filename would be `"/Full/path/app/assets/javascripts/application.js"`
41
+ #
42
+ # Returns a String.
43
+ def filename
44
+ unless @filename
45
+ load_file_params
46
+ end
47
+ @filename
48
+ end
49
+
50
+ # Internal: Hash of param values
51
+ #
52
+ # This information is generated and used internally by Sprockets.
53
+ # Known keys include `:type` which stores the asset's mime-type, `:id` which is a fully resolved
54
+ # digest for the asset (includes dependency digest as opposed to a digest of only file contents)
55
+ # and `:pipeline`. Hash may be empty.
56
+ #
57
+ # Example
58
+ #
59
+ # If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
60
+ # Then the params would be `{type: "application/javascript"}`
61
+ #
62
+ # Returns a Hash.
63
+ def params
64
+ unless @params
65
+ load_file_params
66
+ end
67
+ @params
68
+ end
69
+
70
+ # Internal: Key of asset
71
+ #
72
+ # Used to retrieve an asset from the cache based on "compressed" path to asset.
73
+ # A "compressed" path can either be relative to the root of the project or an
74
+ # absolute path.
75
+ #
76
+ # Returns a String.
77
+ def asset_key
78
+ "asset-uri:#{compressed_path}"
79
+ end
80
+
81
+ # Public: Dependency History key
82
+ #
83
+ # Used to retrieve an array of "histories" each of which contains a set of stored dependencies
84
+ # for a given asset path and filename digest.
85
+ #
86
+ # A dependency can refer to either an asset e.g. index.js
87
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
88
+ # compilation, such as the VERSION of Sprockets (i.e. the environment) and what "processors"
89
+ # are used.
90
+ #
91
+ # For example a history array with one Set of dependencies may look like:
92
+ #
93
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
94
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
95
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
96
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
97
+ #
98
+ # This method of asset lookup is used to ensure that none of the dependencies have been modified
99
+ # since last lookup. If one of them has, the key will be different and a new entry must be stored.
100
+ #
101
+ # URI dependencies are later converted to "compressed" paths
102
+ #
103
+ # Returns a String.
104
+ def dependency_history_key
105
+ "asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
106
+ end
107
+
108
+ # Internal: Digest key
109
+ #
110
+ # Used to retrieve a string containing the "compressed" path to an asset based on
111
+ # a digest. The digest is generated from dependencies stored via information stored in
112
+ # the `dependency_history_key` after each of the "dependencies" is "resolved".
113
+ # For example "environment-version" may be resolved to "environment-1.0-3.2.0"
114
+ # for version "3.2.0" of Sprockets
115
+ #
116
+ # Returns a String.
117
+ def digest_key(digest)
118
+ "asset-uri-digest:#{compressed_path}:#{digest}"
119
+ end
120
+
121
+ # Internal: File digest key
122
+ #
123
+ # The digest for a given file won't change if the path and the stat time hasn't changed
124
+ # We can save time by not re-computing this information and storing it in the cache
125
+ #
126
+ # Returns a String.
127
+ def file_digest_key(stat)
128
+ "file_digest:#{compressed_path}:#{stat}"
129
+ end
130
+
131
+ private
132
+ # Internal: Parses uri into filename and params hash
133
+ #
134
+ # Returns Array with filename and params hash
135
+ def load_file_params
136
+ @filename, @params = URIUtils.parse_asset_uri(uri)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/path_utils'
3
+
4
+ module Sprockets
5
+ # Internal: used to "expand" and "compress" values for storage
6
+ class URITar
7
+ attr_reader :scheme, :root, :path
8
+
9
+ # Internal: Initialize object for compression or expansion
10
+ #
11
+ # uri - A String containing URI that may or may not contain the scheme
12
+ # env - The current "environment" that assets are being loaded into.
13
+ def initialize(uri, env)
14
+ @root = env.root
15
+ @env = env
16
+ uri = uri.to_s
17
+ if uri.include?("://".freeze)
18
+ @scheme, _, @path = uri.partition("://".freeze)
19
+ @scheme << "://".freeze
20
+ else
21
+ @scheme = "".freeze
22
+ @path = uri
23
+ end
24
+ end
25
+
26
+ # Internal: Converts full uri to a "compressed" uri
27
+ #
28
+ # If a uri is inside of an environment's root it will
29
+ # be shortened to be a relative path.
30
+ #
31
+ # If a uri is outside of the environment's root the original
32
+ # uri will be returned.
33
+ #
34
+ # Returns String
35
+ def compress
36
+ scheme + compressed_path
37
+ end
38
+
39
+ # Internal: Tells us if we are using an absolute path
40
+ #
41
+ # Nix* systems start with a `/` like /Users/schneems.
42
+ # Windows systems start with a drive letter than colon and slash
43
+ # like C:/Schneems.
44
+ def absolute_path?
45
+ PathUtils.absolute_path?(path)
46
+ end
47
+
48
+ # Internal: Convert a "compressed" uri to an absolute path
49
+ #
50
+ # If a uri is inside of the environment's root it will not
51
+ # start with a slash for example:
52
+ #
53
+ # file://this/is/a/relative/path
54
+ #
55
+ # If a uri is outside the root, it will start with a slash:
56
+ #
57
+ # file:///This/is/an/absolute/path
58
+ #
59
+ # Returns String
60
+ def expand
61
+ if absolute_path?
62
+ # Stored path was absolute, don't add root
63
+ scheme + path
64
+ else
65
+ if scheme.empty?
66
+ File.join(root, path)
67
+ else
68
+ # We always want to return an absolute uri,
69
+ # make sure the path starts with a slash.
70
+ scheme + File.join("/".freeze, root, path)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Internal: Returns "compressed" path
76
+ #
77
+ # If the input uri is relative to the environment root
78
+ # it will return a path relative to the environment root.
79
+ # Otherwise an absolute path will be returned.
80
+ #
81
+ # Only path information is returned, and not scheme.
82
+ #
83
+ # Returns String
84
+ def compressed_path
85
+ # windows
86
+ if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
87
+ consistent_root = "/".freeze + @root
88
+ else
89
+ consistent_root = @root
90
+ end
91
+
92
+ if compressed_path = PathUtils.split_subpath(consistent_root, path)
93
+ compressed_path
94
+ else
95
+ path
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'uri'
2
3
 
3
4
  module Sprockets
@@ -17,9 +18,6 @@ module Sprockets
17
18
  #
18
19
  # pipeline - String name of pipeline.
19
20
  #
20
- # encoding - A content encoding such as "gzip" or "deflate". NOT a charset
21
- # like "utf-8".
22
- #
23
21
  module URIUtils
24
22
  extend self
25
23
 
@@ -50,19 +48,21 @@ module Sprockets
50
48
  path = URI::Generic::DEFAULT_PARSER.unescape(path)
51
49
  path.force_encoding(Encoding::UTF_8)
52
50
 
53
- # Hack for parsing Windows "file:///C:/Users/IEUser" paths
54
- path.gsub!(/^\/([a-zA-Z]:)/, '\1')
51
+ # Hack for parsing Windows "/C:/Users/IEUser" paths
52
+ if File::ALT_SEPARATOR && path[2] == ':'
53
+ path = path[1..-1]
54
+ end
55
55
 
56
- [scheme, host, path, query]
56
+ [scheme, host || '', path, query]
57
57
  end
58
58
 
59
59
  # Internal: Join file: URI component parts into String.
60
60
  #
61
61
  # Returns String.
62
62
  def join_file_uri(scheme, host, path, query)
63
- str = "#{scheme}://"
63
+ str = +"#{scheme}://"
64
64
  str << host if host
65
- path = "/#{path}" unless path.start_with?("/")
65
+ path = "/#{path}" unless path.start_with?("/".freeze)
66
66
  str << URI::Generic::DEFAULT_PARSER.escape(path)
67
67
  str << "?#{query}" if query
68
68
  str
@@ -75,7 +75,7 @@ module Sprockets
75
75
  # Returns true or false.
76
76
  def valid_asset_uri?(str)
77
77
  # Quick prefix check before attempting a full parse
78
- str.start_with?("file://") && parse_asset_uri(str) ? true : false
78
+ str.start_with?("file://".freeze) && parse_asset_uri(str) ? true : false
79
79
  rescue URI::InvalidURIError
80
80
  false
81
81
  end
@@ -128,7 +128,7 @@ module Sprockets
128
128
  def parse_file_digest_uri(uri)
129
129
  scheme, _, path, _ = split_file_uri(uri)
130
130
 
131
- unless scheme == 'file-digest'
131
+ unless scheme == 'file-digest'.freeze
132
132
  raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
133
133
  end
134
134
 
@@ -146,7 +146,7 @@ module Sprockets
146
146
  #
147
147
  # Returns String URI.
148
148
  def build_file_digest_uri(path)
149
- join_file_uri("file-digest", nil, path, nil)
149
+ join_file_uri('file-digest'.freeze, nil, path, nil)
150
150
  end
151
151
 
152
152
  # Internal: Serialize hash of params into query string.
@@ -171,7 +171,7 @@ module Sprockets
171
171
  end
172
172
  end
173
173
 
174
- "#{query.join('&')}" if query.any?
174
+ "#{query.join('&'.freeze)}" if query.any?
175
175
  end
176
176
 
177
177
  # Internal: Parse query string into hash of params
@@ -180,8 +180,8 @@ module Sprockets
180
180
  #
181
181
  # Return Hash of params.
182
182
  def parse_uri_query_params(query)
183
- query.to_s.split('&').reduce({}) do |h, p|
184
- k, v = p.split('=', 2)
183
+ query.to_s.split('&'.freeze).reduce({}) do |h, p|
184
+ k, v = p.split('='.freeze, 2)
185
185
  v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
186
186
  h[k.to_sym] = v || true
187
187
  h
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ module Sprockets
3
+ module Utils
4
+ class Gzip
5
+ # Private: Generates a gzipped file based off of reference asset.
6
+ #
7
+ # ZlibArchiver.call(file, source, mtime)
8
+ #
9
+ # Compresses a given `source` using stdlib Zlib algorithm
10
+ # writes contents to the `file` passed in. Sets `mtime` of
11
+ # written file to passed in `mtime`
12
+ module ZlibArchiver
13
+ def self.call(file, source, mtime)
14
+ gz = Zlib::GzipWriter.new(file, Zlib::BEST_COMPRESSION)
15
+ gz.mtime = mtime
16
+ gz.write(source)
17
+ gz.close
18
+
19
+ File.utime(mtime, mtime, file.path)
20
+ end
21
+ end
22
+
23
+ # Private: Generates a gzipped file based off of reference asset.
24
+ #
25
+ # ZopfliArchiver.call(file, source, mtime)
26
+ #
27
+ # Compresses a given `source` using the zopfli gem
28
+ # writes contents to the `file` passed in. Sets `mtime` of
29
+ # written file to passed in `mtime`
30
+ module ZopfliArchiver
31
+ def self.call(file, source, mtime)
32
+ compressed_source = Autoload::Zopfli.deflate(source, format: :gzip, mtime: mtime)
33
+ file.write(compressed_source)
34
+ file.close
35
+
36
+ nil
37
+ end
38
+ end
39
+
40
+ attr_reader :content_type, :source, :charset, :archiver
41
+
42
+ # Private: Generates a gzipped file based off of reference file.
43
+ def initialize(asset, archiver: ZlibArchiver)
44
+ @content_type = asset.content_type
45
+ @source = asset.source
46
+ @charset = asset.charset
47
+ @archiver = archiver
48
+ end
49
+
50
+ # What non-text mime types should we compress? This list comes from:
51
+ # https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress
52
+ COMPRESSABLE_MIME_TYPES = {
53
+ "application/vnd.ms-fontobject" => true,
54
+ "application/x-font-opentype" => true,
55
+ "application/x-font-ttf" => true,
56
+ "image/x-icon" => true,
57
+ "image/svg+xml" => true
58
+ }
59
+
60
+ # Private: Returns whether or not an asset can be compressed.
61
+ #
62
+ # We want to compress any file that is text based.
63
+ # You do not want to compress binary
64
+ # files as they may already be compressed and running them
65
+ # through a compression algorithm would make them larger.
66
+ #
67
+ # Return Boolean.
68
+ def can_compress?
69
+ # The "charset" of a mime type is present if the value is
70
+ # encoded text. We can check this value to see if the asset
71
+ # can be compressed.
72
+ #
73
+ # We also check against our list of non-text compressible mime types
74
+ @charset || COMPRESSABLE_MIME_TYPES.include?(@content_type)
75
+ end
76
+
77
+ # Private: Opposite of `can_compress?`.
78
+ #
79
+ # Returns Boolean.
80
+ def cannot_compress?
81
+ !can_compress?
82
+ end
83
+
84
+ # Private: Generates a gzipped file based off of reference asset.
85
+ #
86
+ # Compresses the target asset's contents and puts it into a file with
87
+ # the same name plus a `.gz` extension in the same folder as the original.
88
+ # Does not modify the target asset.
89
+ #
90
+ # Returns nothing.
91
+ def compress(file, target)
92
+ mtime = Sprockets::PathUtils.stat(target).mtime
93
+ archiver.call(file, source, mtime)
94
+
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
 
3
4
  module Sprockets
@@ -24,7 +25,7 @@ module Sprockets
24
25
 
25
26
  # Internal: Duplicate and store key/value on new frozen hash.
26
27
  #
27
- # Seperated for recursive calls, always use hash_reassoc(hash, *keys).
28
+ # Separated for recursive calls, always use hash_reassoc(hash, *keys).
28
29
  #
29
30
  # hash - Hash
30
31
  # key - Object key
@@ -45,7 +46,8 @@ module Sprockets
45
46
  # Similar to Hash#store for nested frozen hashes.
46
47
  #
47
48
  # hash - Hash
48
- # key - Object keys. Use multiple keys for nested hashes.
49
+ # key_a - Object key. Use multiple keys for nested hashes.
50
+ # key_b - Object key. Use multiple keys for nested hashes.
49
51
  # block - Receives current value at key.
50
52
  #
51
53
  # Examples
@@ -56,16 +58,19 @@ module Sprockets
56
58
  # end
57
59
  #
58
60
  # Returns duplicated frozen Hash.
59
- def hash_reassoc(hash, *keys, &block)
60
- if keys.size == 1
61
- hash_reassoc1(hash, keys[0], &block)
62
- else
63
- hash_reassoc1(hash, keys[0]) do |value|
64
- hash_reassoc(value, *keys[1..-1], &block)
61
+ def hash_reassoc(hash, key_a, key_b = nil, &block)
62
+ if key_b
63
+ hash_reassoc1(hash, key_a) do |value|
64
+ hash_reassoc(value, key_b, &block)
65
65
  end
66
+ else
67
+ hash_reassoc1(hash, key_a, &block)
66
68
  end
67
69
  end
68
70
 
71
+ WHITESPACE_ORDINALS = {0x0A => "\n", 0x20 => " ", 0x09 => "\t"}
72
+ private_constant :WHITESPACE_ORDINALS
73
+
69
74
  # Internal: Check if string has a trailing semicolon.
70
75
  #
71
76
  # str - String
@@ -74,16 +79,14 @@ module Sprockets
74
79
  def string_end_with_semicolon?(str)
75
80
  i = str.size - 1
76
81
  while i >= 0
77
- c = str[i]
82
+ c = str[i].ord
78
83
  i -= 1
79
- if c == "\n" || c == " " || c == "\t"
80
- next
81
- elsif c != ";"
82
- return false
83
- else
84
- return true
85
- end
84
+
85
+ next if WHITESPACE_ORDINALS[c]
86
+
87
+ return c === 0x3B
86
88
  end
89
+
87
90
  true
88
91
  end
89
92
 
@@ -95,40 +98,28 @@ module Sprockets
95
98
  #
96
99
  # Returns buf String.
97
100
  def concat_javascript_sources(buf, source)
98
- if string_end_with_semicolon?(buf)
99
- buf << source
101
+ return buf if source.bytesize <= 0
102
+
103
+ buf << source
104
+ # If the source contains non-ASCII characters, indexing on it becomes O(N).
105
+ # This will lead to O(N^2) performance in string_end_with_semicolon?, so we should use 32 bit encoding to make sure indexing stays O(1)
106
+ source = source.encode(Encoding::UTF_32LE) unless source.ascii_only?
107
+ return buf if string_end_with_semicolon?(source)
108
+
109
+ # If the last character in the string was whitespace,
110
+ # such as a newline, then we want to put the semicolon
111
+ # before the whitespace. Otherwise append a semicolon.
112
+ if whitespace = WHITESPACE_ORDINALS[source[-1].ord]
113
+ buf[-1] = ";#{whitespace}"
100
114
  else
101
- buf << ";\n" << source
115
+ buf << ";"
102
116
  end
103
- end
104
117
 
105
- # Internal: Prepends a leading "." to an extension if its missing.
106
- #
107
- # normalize_extension("js")
108
- # # => ".js"
109
- #
110
- # normalize_extension(".css")
111
- # # => ".css"
112
- #
113
- def normalize_extension(extension)
114
- extension = extension.to_s
115
- if extension[/^\./]
116
- extension
117
- else
118
- ".#{extension}"
119
- end
118
+ buf
120
119
  end
121
120
 
122
- # Internal: Feature detect if UnboundMethods can #bind to any Object or
123
- # just Objects that share the same super class.
124
- # Basically if RUBY_VERSION >= 2.
125
- UNBOUND_METHODS_BIND_TO_ANY_OBJECT = begin
126
- foo = Module.new { def bar; end }
127
- foo.instance_method(:bar).bind(Object.new)
128
- true
129
- rescue TypeError
130
- false
131
- end
121
+ MODULE_INCLUDE_MUTEX = Mutex.new
122
+ private_constant :MODULE_INCLUDE_MUTEX
132
123
 
133
124
  # Internal: Inject into target module for the duration of the block.
134
125
  #
@@ -136,28 +127,29 @@ module Sprockets
136
127
  #
137
128
  # Returns result of block.
138
129
  def module_include(base, mod)
139
- old_methods = {}
140
-
141
- mod.instance_methods.each do |sym|
142
- old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
143
- end
130
+ MODULE_INCLUDE_MUTEX.synchronize do
131
+ old_methods = {}
144
132
 
145
- unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT
146
- base.send(:include, mod) unless base < mod
147
- end
133
+ mod.instance_methods.each do |sym|
134
+ old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
135
+ end
148
136
 
149
- mod.instance_methods.each do |sym|
150
- method = mod.instance_method(sym)
151
- base.send(:define_method, sym, method)
152
- end
137
+ mod.instance_methods.each do |sym|
138
+ method = mod.instance_method(sym)
139
+ if base.method_defined?(sym)
140
+ base.send(:alias_method, sym, sym)
141
+ end
142
+ base.send(:define_method, sym, method)
143
+ end
153
144
 
154
- yield
155
- ensure
156
- mod.instance_methods.each do |sym|
157
- base.send(:undef_method, sym) if base.method_defined?(sym)
158
- end
159
- old_methods.each do |sym, method|
160
- base.send(:define_method, sym, method)
145
+ yield
146
+ ensure
147
+ mod.instance_methods.each do |sym|
148
+ base.send(:undef_method, sym) if base.method_defined?(sym)
149
+ end
150
+ old_methods.each do |sym, method|
151
+ base.send(:define_method, sym, method)
152
+ end
161
153
  end
162
154
  end
163
155
 
@@ -199,16 +191,16 @@ module Sprockets
199
191
  # Returns an Array of node Arrays.
200
192
  def dfs_paths(path)
201
193
  paths = []
202
- stack, seen = [path], Set.new
194
+ stack = [path]
195
+ seen = Set.new
203
196
 
204
197
  while path = stack.pop
205
- if !seen.include?(path.last)
206
- seen.add(path.last)
207
- paths << path if path.size > 1
198
+ seen.add(path.last)
199
+ paths << path
208
200
 
209
- Array(yield path.last).reverse_each do |node|
210
- stack.push(path + [node])
211
- end
201
+ children = yield path.last
202
+ children.reverse_each do |node|
203
+ stack.push(path + [node]) unless seen.include?(node)
212
204
  end
213
205
  end
214
206
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
- VERSION = "3.0.3"
3
+ VERSION = "4.2.0"
3
4
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
2
4
 
3
5
  module Sprockets
4
6
  # Public: YUI compressor.
@@ -35,12 +37,7 @@ module Sprockets
35
37
 
36
38
  def initialize(options = {})
37
39
  @options = options
38
- @cache_key = [
39
- self.class.name,
40
- Autoload::YUI::Compressor::VERSION,
41
- VERSION,
42
- options
43
- ].freeze
40
+ @cache_key = "#{self.class.name}:#{Autoload::YUI::Compressor::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
44
41
  end
45
42
 
46
43
  def call(input)
@@ -48,15 +45,9 @@ module Sprockets
48
45
 
49
46
  case input[:content_type]
50
47
  when 'application/javascript'
51
- key = @cache_key + [input[:content_type], input[:data]]
52
- input[:cache].fetch(key) do
53
- Autoload::YUI::JavaScriptCompressor.new(@options).compress(data)
54
- end
48
+ Autoload::YUI::JavaScriptCompressor.new(@options).compress(data)
55
49
  when 'text/css'
56
- key = @cache_key + [input[:content_type], input[:data]]
57
- input[:cache].fetch(key) do
58
- Autoload::YUI::CssCompressor.new(@options).compress(data)
59
- end
50
+ Autoload::YUI::CssCompressor.new(@options).compress(data)
60
51
  else
61
52
  data
62
53
  end