sprockets 3.0.0 → 4.0.0

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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +397 -408
  4. data/bin/sprockets +12 -7
  5. data/lib/rake/sprocketstask.rb +3 -2
  6. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  7. data/lib/sprockets/asset.rb +19 -23
  8. data/lib/sprockets/autoload/babel.rb +8 -0
  9. data/lib/sprockets/autoload/closure.rb +1 -0
  10. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  11. data/lib/sprockets/autoload/eco.rb +1 -0
  12. data/lib/sprockets/autoload/ejs.rb +1 -0
  13. data/lib/sprockets/autoload/jsminc.rb +8 -0
  14. data/lib/sprockets/autoload/sass.rb +1 -0
  15. data/lib/sprockets/autoload/sassc.rb +8 -0
  16. data/lib/sprockets/autoload/uglifier.rb +1 -0
  17. data/lib/sprockets/autoload/yui.rb +1 -0
  18. data/lib/sprockets/autoload/zopfli.rb +7 -0
  19. data/lib/sprockets/autoload.rb +5 -0
  20. data/lib/sprockets/babel_processor.rb +66 -0
  21. data/lib/sprockets/base.rb +59 -11
  22. data/lib/sprockets/bower.rb +5 -2
  23. data/lib/sprockets/bundle.rb +44 -4
  24. data/lib/sprockets/cache/file_store.rb +32 -7
  25. data/lib/sprockets/cache/memory_store.rb +9 -0
  26. data/lib/sprockets/cache/null_store.rb +8 -0
  27. data/lib/sprockets/cache.rb +42 -5
  28. data/lib/sprockets/cached_environment.rb +14 -19
  29. data/lib/sprockets/closure_compressor.rb +6 -11
  30. data/lib/sprockets/coffee_script_processor.rb +19 -5
  31. data/lib/sprockets/compressing.rb +62 -2
  32. data/lib/sprockets/configuration.rb +3 -7
  33. data/lib/sprockets/context.rb +98 -23
  34. data/lib/sprockets/dependencies.rb +9 -8
  35. data/lib/sprockets/digest_utils.rb +104 -60
  36. data/lib/sprockets/directive_processor.rb +45 -35
  37. data/lib/sprockets/eco_processor.rb +3 -2
  38. data/lib/sprockets/ejs_processor.rb +3 -2
  39. data/lib/sprockets/encoding_utils.rb +8 -4
  40. data/lib/sprockets/environment.rb +9 -4
  41. data/lib/sprockets/erb_processor.rb +28 -21
  42. data/lib/sprockets/errors.rb +1 -1
  43. data/lib/sprockets/exporters/base.rb +72 -0
  44. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  45. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  46. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  47. data/lib/sprockets/exporting.rb +73 -0
  48. data/lib/sprockets/file_reader.rb +1 -0
  49. data/lib/sprockets/http_utils.rb +26 -6
  50. data/lib/sprockets/jsminc_compressor.rb +32 -0
  51. data/lib/sprockets/jst_processor.rb +11 -10
  52. data/lib/sprockets/loader.rb +236 -69
  53. data/lib/sprockets/manifest.rb +97 -44
  54. data/lib/sprockets/manifest_utils.rb +9 -6
  55. data/lib/sprockets/mime.rb +8 -42
  56. data/lib/sprockets/npm.rb +52 -0
  57. data/lib/sprockets/path_dependency_utils.rb +3 -11
  58. data/lib/sprockets/path_digest_utils.rb +2 -1
  59. data/lib/sprockets/path_utils.rb +106 -21
  60. data/lib/sprockets/paths.rb +1 -0
  61. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  62. data/lib/sprockets/processing.rb +31 -51
  63. data/lib/sprockets/processor_utils.rb +81 -15
  64. data/lib/sprockets/resolve.rb +182 -95
  65. data/lib/sprockets/sass_cache_store.rb +1 -0
  66. data/lib/sprockets/sass_compressor.rb +21 -17
  67. data/lib/sprockets/sass_functions.rb +1 -0
  68. data/lib/sprockets/sass_importer.rb +1 -0
  69. data/lib/sprockets/sass_processor.rb +45 -17
  70. data/lib/sprockets/sassc_compressor.rb +56 -0
  71. data/lib/sprockets/sassc_processor.rb +297 -0
  72. data/lib/sprockets/server.rb +57 -34
  73. data/lib/sprockets/source_map_processor.rb +66 -0
  74. data/lib/sprockets/source_map_utils.rb +483 -0
  75. data/lib/sprockets/transformers.rb +63 -35
  76. data/lib/sprockets/uglifier_compressor.rb +23 -20
  77. data/lib/sprockets/unloaded_asset.rb +139 -0
  78. data/lib/sprockets/uri_tar.rb +99 -0
  79. data/lib/sprockets/uri_utils.rb +15 -14
  80. data/lib/sprockets/utils/gzip.rb +99 -0
  81. data/lib/sprockets/utils.rb +43 -59
  82. data/lib/sprockets/version.rb +2 -1
  83. data/lib/sprockets/yui_compressor.rb +5 -14
  84. data/lib/sprockets.rb +103 -33
  85. metadata +151 -22
  86. data/LICENSE +0 -21
  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,8 +48,10 @@ 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 = 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
56
  [scheme, host, path, query]
57
57
  end
@@ -60,9 +60,9 @@ module Sprockets
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,10 +180,11 @@ 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
- h.merge(k.to_sym => v || true)
186
+ h[k.to_sym] = v || true
187
+ h
187
188
  end
188
189
  end
189
190
  end
@@ -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
@@ -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,60 +79,43 @@ 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
 
90
93
  # Internal: Accumulate asset source to buffer and append a trailing
91
94
  # semicolon if necessary.
92
95
  #
93
- # buf - String memo
94
- # asset - Asset
96
+ # buf - String buffer to append to
97
+ # source - String source to append
95
98
  #
96
- # Returns appended buffer String.
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
120
- end
121
-
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
118
+ buf
131
119
  end
132
120
 
133
121
  # Internal: Inject into target module for the duration of the block.
@@ -142,10 +130,6 @@ module Sprockets
142
130
  old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
143
131
  end
144
132
 
145
- unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT
146
- base.send(:include, mod) unless base < mod
147
- end
148
-
149
133
  mod.instance_methods.each do |sym|
150
134
  method = mod.instance_method(sym)
151
135
  base.send(:define_method, sym, method)
@@ -199,16 +183,16 @@ module Sprockets
199
183
  # Returns an Array of node Arrays.
200
184
  def dfs_paths(path)
201
185
  paths = []
202
- stack, seen = [path], Set.new
186
+ stack = [path]
187
+ seen = Set.new
203
188
 
204
189
  while path = stack.pop
205
- if !seen.include?(path.last)
206
- seen.add(path.last)
207
- paths << path if path.size > 1
190
+ seen.add(path.last)
191
+ paths << path
208
192
 
209
- Array(yield path.last).reverse_each do |node|
210
- stack.push(path + [node])
211
- end
193
+ children = yield path.last
194
+ children.reverse_each do |node|
195
+ stack.push(path + [node]) unless seen.include?(node)
212
196
  end
213
197
  end
214
198
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
- VERSION = "3.0.0"
3
+ VERSION = "4.0.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