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,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