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,173 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/http_utils'
3
+ require 'sprockets/processor_utils'
4
+ require 'sprockets/utils'
5
+
6
+ module Sprockets
7
+ module Transformers
8
+ include HTTPUtils, ProcessorUtils, Utils
9
+
10
+ # Public: Two level mapping of a source mime type to a target mime type.
11
+ #
12
+ # environment.transformers
13
+ # # => { 'text/coffeescript' => {
14
+ # 'application/javascript' => ConvertCoffeeScriptToJavaScript
15
+ # }
16
+ # }
17
+ #
18
+ def transformers
19
+ config[:transformers]
20
+ end
21
+
22
+ Transformer = Struct.new :from, :to, :proc
23
+
24
+ # Public: Register a transformer from and to a mime type.
25
+ #
26
+ # from - String mime type
27
+ # to - String mime type
28
+ # proc - Callable block that accepts an input Hash.
29
+ #
30
+ # Examples
31
+ #
32
+ # register_transformer 'text/coffeescript', 'application/javascript',
33
+ # ConvertCoffeeScriptToJavaScript
34
+ #
35
+ # register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
36
+ #
37
+ # Returns nothing.
38
+ def register_transformer(from, 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)
62
+ end
63
+ end
64
+
65
+ # Internal: Resolve target mime type that the source type should be
66
+ # transformed to.
67
+ #
68
+ # type - String from mime type
69
+ # accept - String accept type list (default: '*/*')
70
+ #
71
+ # Examples
72
+ #
73
+ # resolve_transform_type('text/plain', 'text/plain')
74
+ # # => 'text/plain'
75
+ #
76
+ # resolve_transform_type('image/svg+xml', 'image/png, image/*')
77
+ # # => 'image/png'
78
+ #
79
+ # resolve_transform_type('text/css', 'image/png')
80
+ # # => nil
81
+ #
82
+ # Returns String mime type or nil is no type satisfied the accept value.
83
+ def resolve_transform_type(type, accept)
84
+ find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys)
85
+ end
86
+
87
+ # Internal: Expand accept type list to include possible transformed types.
88
+ #
89
+ # parsed_accepts - Array of accept q values
90
+ #
91
+ # Examples
92
+ #
93
+ # expand_transform_accepts([['application/javascript', 1.0]])
94
+ # # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]]
95
+ #
96
+ # Returns an expanded Array of q values.
97
+ def expand_transform_accepts(parsed_accepts)
98
+ accepts = []
99
+ parsed_accepts.each do |(type, q)|
100
+ accepts.push([type, q])
101
+ config[:inverted_transformers][type].each do |subtype|
102
+ accepts.push([subtype, q * 0.8])
103
+ end
104
+ end
105
+ accepts
106
+ end
107
+
108
+ # Internal: Compose multiple transformer steps into a single processor
109
+ # function.
110
+ #
111
+ # transformers - Two level Hash of a source mime type to a target mime type
112
+ # types - Array of mime type steps
113
+ #
114
+ # Returns Processor.
115
+ def compose_transformers(transformers, types, preprocessors, postprocessors)
116
+ if types.length < 2
117
+ raise ArgumentError, "too few transform types: #{types.inspect}"
118
+ end
119
+
120
+ processors = types.each_cons(2).map { |src, dst|
121
+ unless processor = transformers[src][dst]
122
+ raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
123
+ end
124
+ processor
125
+ }
126
+
127
+ compose_transformer_list processors, preprocessors, postprocessors
128
+ end
129
+
130
+ private
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 { {} }
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, []) }
156
+
157
+ traversals.each do |nodes|
158
+ src, dst = nodes.first.from, nodes.last.to
159
+ processor = compose_transformer_list nodes, preprocessors, postprocessors
160
+
161
+ transformers[src] = {} unless transformers.key?(src)
162
+ transformers[src][dst] = processor
163
+
164
+ inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
165
+ inverted_transformers[dst] << src
166
+ end
167
+ end
168
+
169
+ self.config = hash_reassoc(config, :transformers) { transformers }
170
+ self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers }
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
4
+ require 'sprockets/source_map_utils'
5
+
6
+ module Sprockets
7
+ # Public: Uglifier/Uglify compressor.
8
+ #
9
+ # To accept the default options
10
+ #
11
+ # environment.register_bundle_processor 'application/javascript',
12
+ # Sprockets::UglifierCompressor
13
+ #
14
+ # Or to pass options to the Uglifier class.
15
+ #
16
+ # environment.register_bundle_processor 'application/javascript',
17
+ # Sprockets::UglifierCompressor.new(comments: :copyright)
18
+ #
19
+ class UglifierCompressor
20
+ VERSION = '3'
21
+
22
+ # Public: Return singleton instance with default options.
23
+ #
24
+ # Returns UglifierCompressor object.
25
+ def self.instance
26
+ @instance ||= new
27
+ end
28
+
29
+ def self.call(input)
30
+ instance.call(input)
31
+ end
32
+
33
+ def self.cache_key
34
+ instance.cache_key
35
+ end
36
+
37
+ attr_reader :cache_key
38
+
39
+ def initialize(options = {})
40
+ options[:comments] ||= :none
41
+
42
+ @options = options
43
+ @cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
44
+ end
45
+
46
+ def call(input)
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 }
64
+ end
65
+ end
66
+ end
@@ -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