sprockets 4.0.1

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