sprockets 4.0.1

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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +665 -0
  4. data/bin/sprockets +93 -0
  5. data/lib/rake/sprocketstask.rb +153 -0
  6. data/lib/sprockets.rb +229 -0
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +202 -0
  9. data/lib/sprockets/autoload.rb +16 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +8 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  13. data/lib/sprockets/autoload/eco.rb +8 -0
  14. data/lib/sprockets/autoload/ejs.rb +8 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +8 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +8 -0
  19. data/lib/sprockets/autoload/yui.rb +8 -0
  20. data/lib/sprockets/autoload/zopfli.rb +7 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +147 -0
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache.rb +271 -0
  26. data/lib/sprockets/cache/file_store.rb +208 -0
  27. data/lib/sprockets/cache/memory_store.rb +75 -0
  28. data/lib/sprockets/cache/null_store.rb +54 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +304 -0
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +200 -0
  37. data/lib/sprockets/directive_processor.rb +414 -0
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +262 -0
  41. data/lib/sprockets/environment.rb +46 -0
  42. data/lib/sprockets/erb_processor.rb +37 -0
  43. data/lib/sprockets/errors.rb +12 -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 +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +50 -0
  53. data/lib/sprockets/loader.rb +345 -0
  54. data/lib/sprockets/manifest.rb +338 -0
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +96 -0
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +82 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +228 -0
  64. data/lib/sprockets/processor_utils.rb +169 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +30 -0
  67. data/lib/sprockets/sass_compressor.rb +63 -0
  68. data/lib/sprockets/sass_functions.rb +3 -0
  69. data/lib/sprockets/sass_importer.rb +3 -0
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +295 -0
  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 +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  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 +191 -0
  81. data/lib/sprockets/utils.rb +202 -0
  82. data/lib/sprockets/utils/gzip.rb +99 -0
  83. data/lib/sprockets/version.rb +4 -0
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. metadata +444 -0
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+
4
+ module Sprockets
5
+ # Internal: Asset URI related parsing utilities. Mixed into Environment.
6
+ #
7
+ # An Asset URI identifies the compiled Asset result. It shares the file:
8
+ # scheme and requires an absolute path.
9
+ #
10
+ # Other query parameters
11
+ #
12
+ # type - String output content type. Otherwise assumed from file extension.
13
+ # This maybe different than the extension if the asset is transformed
14
+ # from one content type to another. For an example .coffee -> .js.
15
+ #
16
+ # id - Unique fingerprint of the entire asset and all its metadata. Assets
17
+ # will only have the same id if they serialize to an identical value.
18
+ #
19
+ # pipeline - String name of pipeline.
20
+ #
21
+ module URIUtils
22
+ extend self
23
+
24
+ # Internal: Parse URI into component parts.
25
+ #
26
+ # uri - String uri
27
+ #
28
+ # Returns Array of components.
29
+ def split_uri(uri)
30
+ URI.split(uri)
31
+ end
32
+
33
+ # Internal: Join URI component parts into String.
34
+ #
35
+ # Returns String.
36
+ def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
37
+ URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
38
+ end
39
+
40
+ # Internal: Parse file: URI into component parts.
41
+ #
42
+ # uri - String uri
43
+ #
44
+ # Returns [scheme, host, path, query].
45
+ def split_file_uri(uri)
46
+ scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
47
+
48
+ path = URI::Generic::DEFAULT_PARSER.unescape(path)
49
+ path.force_encoding(Encoding::UTF_8)
50
+
51
+ # Hack for parsing Windows "/C:/Users/IEUser" paths
52
+ if File::ALT_SEPARATOR && path[2] == ':'
53
+ path = path[1..-1]
54
+ end
55
+
56
+ [scheme, host, path, query]
57
+ end
58
+
59
+ # Internal: Join file: URI component parts into String.
60
+ #
61
+ # Returns String.
62
+ def join_file_uri(scheme, host, path, query)
63
+ str = +"#{scheme}://"
64
+ str << host if host
65
+ path = "/#{path}" unless path.start_with?("/".freeze)
66
+ str << URI::Generic::DEFAULT_PARSER.escape(path)
67
+ str << "?#{query}" if query
68
+ str
69
+ end
70
+
71
+ # Internal: Check if String is a valid Asset URI.
72
+ #
73
+ # str - Possible String asset URI.
74
+ #
75
+ # Returns true or false.
76
+ def valid_asset_uri?(str)
77
+ # Quick prefix check before attempting a full parse
78
+ str.start_with?("file://".freeze) && parse_asset_uri(str) ? true : false
79
+ rescue URI::InvalidURIError
80
+ false
81
+ end
82
+
83
+ # Internal: Parse Asset URI.
84
+ #
85
+ # Examples
86
+ #
87
+ # parse("file:///tmp/js/application.coffee?type=application/javascript")
88
+ # # => "/tmp/js/application.coffee", {type: "application/javascript"}
89
+ #
90
+ # uri - String asset URI
91
+ #
92
+ # Returns String path and Hash of symbolized parameters.
93
+ def parse_asset_uri(uri)
94
+ scheme, _, path, query = split_file_uri(uri)
95
+
96
+ unless scheme == 'file'
97
+ raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
98
+ end
99
+
100
+ return path, parse_uri_query_params(query)
101
+ end
102
+
103
+ # Internal: Build Asset URI.
104
+ #
105
+ # Examples
106
+ #
107
+ # build("/tmp/js/application.coffee", type: "application/javascript")
108
+ # # => "file:///tmp/js/application.coffee?type=application/javascript"
109
+ #
110
+ # path - String file path
111
+ # params - Hash of optional parameters
112
+ #
113
+ # Returns String URI.
114
+ def build_asset_uri(path, params = {})
115
+ join_file_uri("file", nil, path, encode_uri_query_params(params))
116
+ end
117
+
118
+ # Internal: Parse file-digest dependency URI.
119
+ #
120
+ # Examples
121
+ #
122
+ # parse("file-digest:/tmp/js/application.js")
123
+ # # => "/tmp/js/application.js"
124
+ #
125
+ # uri - String file-digest URI
126
+ #
127
+ # Returns String path.
128
+ def parse_file_digest_uri(uri)
129
+ scheme, _, path, _ = split_file_uri(uri)
130
+
131
+ unless scheme == 'file-digest'.freeze
132
+ raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
133
+ end
134
+
135
+ path
136
+ end
137
+
138
+ # Internal: Build file-digest dependency URI.
139
+ #
140
+ # Examples
141
+ #
142
+ # build("/tmp/js/application.js")
143
+ # # => "file-digest:/tmp/js/application.js"
144
+ #
145
+ # path - String file path
146
+ #
147
+ # Returns String URI.
148
+ def build_file_digest_uri(path)
149
+ join_file_uri('file-digest'.freeze, nil, path, nil)
150
+ end
151
+
152
+ # Internal: Serialize hash of params into query string.
153
+ #
154
+ # params - Hash of params to serialize
155
+ #
156
+ # Returns String query or nil if empty.
157
+ def encode_uri_query_params(params)
158
+ query = []
159
+
160
+ params.each do |key, value|
161
+ case value
162
+ when Integer
163
+ query << "#{key}=#{value}"
164
+ when String, Symbol
165
+ query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
166
+ when TrueClass
167
+ query << "#{key}"
168
+ when FalseClass, NilClass
169
+ else
170
+ raise TypeError, "unexpected type: #{value.class}"
171
+ end
172
+ end
173
+
174
+ "#{query.join('&'.freeze)}" if query.any?
175
+ end
176
+
177
+ # Internal: Parse query string into hash of params
178
+ #
179
+ # query - String query string
180
+ #
181
+ # Return Hash of params.
182
+ def parse_uri_query_params(query)
183
+ query.to_s.split('&'.freeze).reduce({}) do |h, p|
184
+ k, v = p.split('='.freeze, 2)
185
+ v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
186
+ h[k.to_sym] = v || true
187
+ h
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
4
+ module Sprockets
5
+ # Internal: Utils, we didn't know where else to put it! Functions may
6
+ # eventually be shuffled into more specific drawers.
7
+ module Utils
8
+ extend self
9
+
10
+ # Internal: Check if object can safely be .dup'd.
11
+ #
12
+ # Similar to ActiveSupport #duplicable? check.
13
+ #
14
+ # obj - Any Object
15
+ #
16
+ # Returns false if .dup would raise a TypeError, otherwise true.
17
+ def duplicable?(obj)
18
+ case obj
19
+ when NilClass, FalseClass, TrueClass, Symbol, Numeric
20
+ false
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ # Internal: Duplicate and store key/value on new frozen hash.
27
+ #
28
+ # Seperated for recursive calls, always use hash_reassoc(hash, *keys).
29
+ #
30
+ # hash - Hash
31
+ # key - Object key
32
+ #
33
+ # Returns Hash.
34
+ def hash_reassoc1(hash, key)
35
+ hash = hash.dup if hash.frozen?
36
+ old_value = hash[key]
37
+ old_value = old_value.dup if duplicable?(old_value)
38
+ new_value = yield old_value
39
+ new_value.freeze if duplicable?(new_value)
40
+ hash.store(key, new_value)
41
+ hash.freeze
42
+ end
43
+
44
+ # Internal: Duplicate and store key/value on new frozen hash.
45
+ #
46
+ # Similar to Hash#store for nested frozen hashes.
47
+ #
48
+ # hash - Hash
49
+ # key_a - Object key. Use multiple keys for nested hashes.
50
+ # key_b - Object key. Use multiple keys for nested hashes.
51
+ # block - Receives current value at key.
52
+ #
53
+ # Examples
54
+ #
55
+ # config = {paths: ["/bin", "/sbin"]}.freeze
56
+ # new_config = hash_reassoc(config, :paths) do |paths|
57
+ # paths << "/usr/local/bin"
58
+ # end
59
+ #
60
+ # Returns duplicated frozen Hash.
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
+ end
66
+ else
67
+ hash_reassoc1(hash, key_a, &block)
68
+ end
69
+ end
70
+
71
+ WHITESPACE_ORDINALS = {0x0A => "\n", 0x20 => " ", 0x09 => "\t"}
72
+ private_constant :WHITESPACE_ORDINALS
73
+
74
+ # Internal: Check if string has a trailing semicolon.
75
+ #
76
+ # str - String
77
+ #
78
+ # Returns true or false.
79
+ def string_end_with_semicolon?(str)
80
+ i = str.size - 1
81
+ while i >= 0
82
+ c = str[i].ord
83
+ i -= 1
84
+
85
+ next if WHITESPACE_ORDINALS[c]
86
+
87
+ return c === 0x3B
88
+ end
89
+
90
+ true
91
+ end
92
+
93
+ # Internal: Accumulate asset source to buffer and append a trailing
94
+ # semicolon if necessary.
95
+ #
96
+ # buf - String buffer to append to
97
+ # source - String source to append
98
+ #
99
+ # Returns buf String.
100
+ def concat_javascript_sources(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}"
114
+ else
115
+ buf << ";"
116
+ end
117
+
118
+ buf
119
+ end
120
+
121
+ # Internal: Inject into target module for the duration of the block.
122
+ #
123
+ # mod - Module
124
+ #
125
+ # Returns result of block.
126
+ def module_include(base, mod)
127
+ old_methods = {}
128
+
129
+ mod.instance_methods.each do |sym|
130
+ old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
131
+ end
132
+
133
+ mod.instance_methods.each do |sym|
134
+ method = mod.instance_method(sym)
135
+ base.send(:define_method, sym, method)
136
+ end
137
+
138
+ yield
139
+ ensure
140
+ mod.instance_methods.each do |sym|
141
+ base.send(:undef_method, sym) if base.method_defined?(sym)
142
+ end
143
+ old_methods.each do |sym, method|
144
+ base.send(:define_method, sym, method)
145
+ end
146
+ end
147
+
148
+ # Internal: Post-order Depth-First search algorithm.
149
+ #
150
+ # Used for resolving asset dependencies.
151
+ #
152
+ # initial - Initial Array of nodes to traverse.
153
+ # block -
154
+ # node - Current node to get children of
155
+ #
156
+ # Returns a Set of nodes.
157
+ def dfs(initial)
158
+ nodes, seen = Set.new, Set.new
159
+ stack = Array(initial).reverse
160
+
161
+ while node = stack.pop
162
+ if seen.include?(node)
163
+ nodes.add(node)
164
+ else
165
+ seen.add(node)
166
+ stack.push(node)
167
+ stack.concat(Array(yield node).reverse)
168
+ end
169
+ end
170
+
171
+ nodes
172
+ end
173
+
174
+ # Internal: Post-order Depth-First search algorithm that gathers all paths
175
+ # along the way.
176
+ #
177
+ # TODO: Rename function.
178
+ #
179
+ # path - Initial Array node path
180
+ # block -
181
+ # node - Current node to get children of
182
+ #
183
+ # Returns an Array of node Arrays.
184
+ def dfs_paths(path)
185
+ paths = []
186
+ stack = [path]
187
+ seen = Set.new
188
+
189
+ while path = stack.pop
190
+ seen.add(path.last)
191
+ paths << path
192
+
193
+ children = yield path.last
194
+ children.reverse_each do |node|
195
+ stack.push(path + [node]) unless seen.include?(node)
196
+ end
197
+ end
198
+
199
+ paths
200
+ end
201
+ end
202
+ 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