sprockets 2.11.3 → 3.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +296 -0
- data/LICENSE +2 -2
- data/README.md +235 -242
- data/bin/sprockets +1 -0
- data/lib/rake/sprocketstask.rb +5 -4
- data/lib/sprockets/asset.rb +143 -210
- data/lib/sprockets/autoload/closure.rb +7 -0
- data/lib/sprockets/autoload/coffee_script.rb +7 -0
- data/lib/sprockets/autoload/eco.rb +7 -0
- data/lib/sprockets/autoload/ejs.rb +7 -0
- data/lib/sprockets/autoload/sass.rb +7 -0
- data/lib/sprockets/autoload/uglifier.rb +7 -0
- data/lib/sprockets/autoload/yui.rb +7 -0
- data/lib/sprockets/autoload.rb +11 -0
- data/lib/sprockets/base.rb +56 -393
- data/lib/sprockets/bower.rb +58 -0
- data/lib/sprockets/bundle.rb +69 -0
- data/lib/sprockets/cache/file_store.rb +168 -14
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cache.rb +236 -0
- data/lib/sprockets/cached_environment.rb +69 -0
- data/lib/sprockets/closure_compressor.rb +35 -10
- data/lib/sprockets/coffee_script_processor.rb +25 -0
- data/lib/sprockets/coffee_script_template.rb +17 -0
- data/lib/sprockets/compressing.rb +44 -23
- data/lib/sprockets/configuration.rb +83 -0
- data/lib/sprockets/context.rb +86 -144
- data/lib/sprockets/dependencies.rb +73 -0
- data/lib/sprockets/deprecation.rb +90 -0
- data/lib/sprockets/digest_utils.rb +180 -0
- data/lib/sprockets/directive_processor.rb +207 -211
- data/lib/sprockets/eco_processor.rb +32 -0
- data/lib/sprockets/eco_template.rb +9 -30
- data/lib/sprockets/ejs_processor.rb +31 -0
- data/lib/sprockets/ejs_template.rb +9 -29
- data/lib/sprockets/encoding_utils.rb +261 -0
- data/lib/sprockets/engines.rb +53 -35
- data/lib/sprockets/environment.rb +17 -64
- data/lib/sprockets/erb_processor.rb +30 -0
- data/lib/sprockets/erb_template.rb +11 -0
- data/lib/sprockets/errors.rb +4 -13
- data/lib/sprockets/file_reader.rb +15 -0
- data/lib/sprockets/http_utils.rb +117 -0
- data/lib/sprockets/jst_processor.rb +35 -15
- data/lib/sprockets/legacy.rb +330 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/loader.rb +325 -0
- data/lib/sprockets/manifest.rb +202 -127
- data/lib/sprockets/manifest_utils.rb +45 -0
- data/lib/sprockets/mime.rb +112 -31
- data/lib/sprockets/path_dependency_utils.rb +85 -0
- data/lib/sprockets/path_digest_utils.rb +47 -0
- data/lib/sprockets/path_utils.rb +287 -0
- data/lib/sprockets/paths.rb +42 -19
- data/lib/sprockets/processing.rb +178 -126
- data/lib/sprockets/processor_utils.rb +180 -0
- data/lib/sprockets/resolve.rb +211 -0
- data/lib/sprockets/sass_cache_store.rb +22 -17
- data/lib/sprockets/sass_compressor.rb +39 -15
- data/lib/sprockets/sass_functions.rb +2 -70
- data/lib/sprockets/sass_importer.rb +2 -29
- data/lib/sprockets/sass_processor.rb +292 -0
- data/lib/sprockets/sass_template.rb +12 -53
- data/lib/sprockets/server.rb +129 -84
- data/lib/sprockets/transformers.rb +145 -0
- data/lib/sprockets/uglifier_compressor.rb +39 -12
- data/lib/sprockets/unloaded_asset.rb +137 -0
- data/lib/sprockets/uri_tar.rb +98 -0
- data/lib/sprockets/uri_utils.rb +188 -0
- data/lib/sprockets/utils/gzip.rb +67 -0
- data/lib/sprockets/utils.rb +210 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +39 -11
- data/lib/sprockets.rb +142 -81
- metadata +100 -80
- data/lib/sprockets/asset_attributes.rb +0 -137
- data/lib/sprockets/bundled_asset.rb +0 -78
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -100
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -58
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'sprockets/http_utils'
|
2
|
+
require 'sprockets/processor_utils'
|
3
|
+
require 'sprockets/utils'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
module Transformers
|
7
|
+
include HTTPUtils, ProcessorUtils, Utils
|
8
|
+
|
9
|
+
# Public: Two level mapping of a source mime type to a target mime type.
|
10
|
+
#
|
11
|
+
# environment.transformers
|
12
|
+
# # => { 'text/coffeescript' => {
|
13
|
+
# 'application/javascript' => ConvertCoffeeScriptToJavaScript
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
def transformers
|
18
|
+
config[:transformers]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Register a transformer from and to a mime type.
|
22
|
+
#
|
23
|
+
# from - String mime type
|
24
|
+
# to - String mime type
|
25
|
+
# proc - Callable block that accepts an input Hash.
|
26
|
+
#
|
27
|
+
# Examples
|
28
|
+
#
|
29
|
+
# register_transformer 'text/coffeescript', 'application/javascript',
|
30
|
+
# ConvertCoffeeScriptToJavaScript
|
31
|
+
#
|
32
|
+
# register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
|
33
|
+
#
|
34
|
+
# Returns nothing.
|
35
|
+
def register_transformer(from, to, proc)
|
36
|
+
self.config = hash_reassoc(config, :registered_transformers, from) do |transformers|
|
37
|
+
transformers.merge(to => proc)
|
38
|
+
end
|
39
|
+
compute_transformers!
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Resolve target mime type that the source type should be
|
43
|
+
# transformed to.
|
44
|
+
#
|
45
|
+
# type - String from mime type
|
46
|
+
# accept - String accept type list (default: '*/*')
|
47
|
+
#
|
48
|
+
# Examples
|
49
|
+
#
|
50
|
+
# resolve_transform_type('text/plain', 'text/plain')
|
51
|
+
# # => 'text/plain'
|
52
|
+
#
|
53
|
+
# resolve_transform_type('image/svg+xml', 'image/png, image/*')
|
54
|
+
# # => 'image/png'
|
55
|
+
#
|
56
|
+
# resolve_transform_type('text/css', 'image/png')
|
57
|
+
# # => nil
|
58
|
+
#
|
59
|
+
# Returns String mime type or nil is no type satisfied the accept value.
|
60
|
+
def resolve_transform_type(type, accept)
|
61
|
+
find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Internal: Expand accept type list to include possible transformed types.
|
65
|
+
#
|
66
|
+
# parsed_accepts - Array of accept q values
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
70
|
+
# expand_transform_accepts([['application/javascript', 1.0]])
|
71
|
+
# # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]]
|
72
|
+
#
|
73
|
+
# Returns an expanded Array of q values.
|
74
|
+
def expand_transform_accepts(parsed_accepts)
|
75
|
+
accepts = []
|
76
|
+
parsed_accepts.each do |(type, q)|
|
77
|
+
accepts.push([type, q])
|
78
|
+
config[:inverted_transformers][type].each do |subtype|
|
79
|
+
accepts.push([subtype, q * 0.8])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
accepts
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal: Compose multiple transformer steps into a single processor
|
86
|
+
# function.
|
87
|
+
#
|
88
|
+
# transformers - Two level Hash of a source mime type to a target mime type
|
89
|
+
# types - Array of mime type steps
|
90
|
+
#
|
91
|
+
# Returns Processor.
|
92
|
+
def compose_transformers(transformers, types)
|
93
|
+
if types.length < 2
|
94
|
+
raise ArgumentError, "too few transform types: #{types.inspect}"
|
95
|
+
end
|
96
|
+
|
97
|
+
i = 0
|
98
|
+
processors = []
|
99
|
+
|
100
|
+
loop do
|
101
|
+
src = types[i]
|
102
|
+
dst = types[i+1]
|
103
|
+
break unless src && dst
|
104
|
+
|
105
|
+
unless processor = transformers[src][dst]
|
106
|
+
raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
|
107
|
+
end
|
108
|
+
processors.concat config[:postprocessors][src]
|
109
|
+
processors << processor
|
110
|
+
processors.concat config[:preprocessors][dst]
|
111
|
+
|
112
|
+
i += 1
|
113
|
+
end
|
114
|
+
|
115
|
+
if processors.size > 1
|
116
|
+
compose_processors(*processors.reverse)
|
117
|
+
elsif processors.size == 1
|
118
|
+
processors.first
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def compute_transformers!
|
124
|
+
registered_transformers = self.config[:registered_transformers]
|
125
|
+
transformers = Hash.new { {} }
|
126
|
+
inverted_transformers = Hash.new { Set.new }
|
127
|
+
|
128
|
+
registered_transformers.keys.flat_map do |key|
|
129
|
+
dfs_paths([key]) { |k| registered_transformers[k].keys }
|
130
|
+
end.each do |types|
|
131
|
+
src, dst = types.first, types.last
|
132
|
+
processor = compose_transformers(registered_transformers, types)
|
133
|
+
|
134
|
+
transformers[src] = {} unless transformers.key?(src)
|
135
|
+
transformers[src][dst] = processor
|
136
|
+
|
137
|
+
inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
|
138
|
+
inverted_transformers[dst] << src
|
139
|
+
end
|
140
|
+
|
141
|
+
self.config = hash_reassoc(config, :transformers) { transformers }
|
142
|
+
self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -1,29 +1,56 @@
|
|
1
|
-
require '
|
1
|
+
require 'sprockets/autoload'
|
2
|
+
require 'sprockets/digest_utils'
|
2
3
|
|
3
4
|
module Sprockets
|
4
|
-
|
5
|
-
|
5
|
+
# Public: Uglifier/Uglify compressor.
|
6
|
+
#
|
7
|
+
# To accept the default options
|
8
|
+
#
|
9
|
+
# environment.register_bundle_processor 'application/javascript',
|
10
|
+
# Sprockets::UglifierCompressor
|
11
|
+
#
|
12
|
+
# Or to pass options to the Uglifier class.
|
13
|
+
#
|
14
|
+
# environment.register_bundle_processor 'application/javascript',
|
15
|
+
# Sprockets::UglifierCompressor.new(comments: :copyright)
|
16
|
+
#
|
17
|
+
class UglifierCompressor
|
18
|
+
VERSION = '1'
|
6
19
|
|
7
|
-
|
8
|
-
|
20
|
+
# Public: Return singleton instance with default options.
|
21
|
+
#
|
22
|
+
# Returns UglifierCompressor object.
|
23
|
+
def self.instance
|
24
|
+
@instance ||= new
|
9
25
|
end
|
10
26
|
|
11
|
-
def
|
12
|
-
|
27
|
+
def self.call(input)
|
28
|
+
instance.call(input)
|
13
29
|
end
|
14
30
|
|
15
|
-
def
|
31
|
+
def self.cache_key
|
32
|
+
instance.cache_key
|
16
33
|
end
|
17
34
|
|
18
|
-
|
35
|
+
attr_reader :cache_key
|
36
|
+
|
37
|
+
def initialize(options = {})
|
19
38
|
# Feature detect Uglifier 2.0 option support
|
20
|
-
if Uglifier::DEFAULTS[:copyright]
|
39
|
+
if Autoload::Uglifier::DEFAULTS[:copyright]
|
21
40
|
# Uglifier < 2.x
|
22
|
-
|
41
|
+
options[:copyright] ||= false
|
23
42
|
else
|
24
43
|
# Uglifier >= 2.x
|
25
|
-
|
44
|
+
options[:comments] ||= :none
|
26
45
|
end
|
46
|
+
|
47
|
+
@options = options
|
48
|
+
@cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(input)
|
52
|
+
@uglifier ||= Autoload::Uglifier.new(@options)
|
53
|
+
@uglifier.compile(input[:data])
|
27
54
|
end
|
28
55
|
end
|
29
56
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'sprockets/uri_utils'
|
2
|
+
require 'sprockets/uri_tar'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# Internal: Used to parse and store the URI to an unloaded asset
|
6
|
+
# Generates keys used to store and retrieve items from cache
|
7
|
+
class UnloadedAsset
|
8
|
+
|
9
|
+
# Internal: Initialize object for generating cache keys
|
10
|
+
#
|
11
|
+
# uri - A String containing complete URI to a file including scheme
|
12
|
+
# and full path such as
|
13
|
+
# "file:///Path/app/assets/js/app.js?type=application/javascript"
|
14
|
+
# env - The current "environment" that assets are being loaded into.
|
15
|
+
# We need it so we know where the +root+ (directory where sprockets
|
16
|
+
# is being invoked). We also need for the `file_digest` method,
|
17
|
+
# since, for some strange reason, memoization is provided by
|
18
|
+
# overriding methods such as `stat` in the `PathUtils` module.
|
19
|
+
#
|
20
|
+
# Returns UnloadedAsset.
|
21
|
+
def initialize(uri, env)
|
22
|
+
@uri = uri.to_s
|
23
|
+
@env = env
|
24
|
+
@compressed_path = URITar.new(uri, env).compressed_path
|
25
|
+
@params = nil # lazy loaded
|
26
|
+
@filename = nil # lazy loaded
|
27
|
+
end
|
28
|
+
attr_reader :compressed_path, :uri
|
29
|
+
|
30
|
+
# Internal: Full file path without schema
|
31
|
+
#
|
32
|
+
# This returns a string containing the full path to the asset without the schema.
|
33
|
+
# Information is loaded lazilly since we want `UnloadedAsset.new(dep, self).relative_path`
|
34
|
+
# to be fast. Calling this method the first time allocates an array and a hash.
|
35
|
+
#
|
36
|
+
# Example
|
37
|
+
#
|
38
|
+
# If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
|
39
|
+
# filename would be `"/Full/path/app/assets/javascripts/application.js"`
|
40
|
+
#
|
41
|
+
# Returns a String.
|
42
|
+
def filename
|
43
|
+
unless @filename
|
44
|
+
load_file_params
|
45
|
+
end
|
46
|
+
@filename
|
47
|
+
end
|
48
|
+
|
49
|
+
# Internal: Hash of param values
|
50
|
+
#
|
51
|
+
# This information is generated and used internally by sprockets.
|
52
|
+
# Known keys include `:type` which store the asset's mime-type, `:id` which is a fully resolved
|
53
|
+
# digest for the asset (includes dependency digest as opposed to a digest of only file contents)
|
54
|
+
# and `:pipeline`. Hash may be empty.
|
55
|
+
#
|
56
|
+
# Example
|
57
|
+
#
|
58
|
+
# If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
|
59
|
+
# Then the params would be `{type: "application/javascript"}`
|
60
|
+
#
|
61
|
+
# Returns a Hash.
|
62
|
+
def params
|
63
|
+
unless @params
|
64
|
+
load_file_params
|
65
|
+
end
|
66
|
+
@params
|
67
|
+
end
|
68
|
+
|
69
|
+
# Internal: Key of asset
|
70
|
+
#
|
71
|
+
# Used to retrieve an asset from the cache based on "compressed" path to asset.
|
72
|
+
# A "compressed" path can either be relative to the root of the project or an
|
73
|
+
# absolute path.
|
74
|
+
#
|
75
|
+
# Returns a String.
|
76
|
+
def asset_key
|
77
|
+
"asset-uri:#{compressed_path}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Dependency History key
|
81
|
+
#
|
82
|
+
# Used to retrieve an array of "histories" each of which contain a set of stored dependencies
|
83
|
+
# for a given asset path and filename digest.
|
84
|
+
#
|
85
|
+
# A dependency can refer to either an asset i.e. index.js
|
86
|
+
# may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
|
87
|
+
# compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
|
88
|
+
# are used.
|
89
|
+
#
|
90
|
+
# For example a history array with one Set of dependencies may look like:
|
91
|
+
#
|
92
|
+
# [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
|
93
|
+
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
|
94
|
+
# "processors:type=text/css&file_type=text/css&pipeline=self",
|
95
|
+
# "file-digest:///Full/path/app/assets/stylesheets"]]
|
96
|
+
#
|
97
|
+
# This method of asset lookup is used to ensure that none of the dependencies have been modified
|
98
|
+
# since last lookup. If one of them has, the key will be different and a new entry must be stored.
|
99
|
+
#
|
100
|
+
# URI depndencies are later converted to "compressed" paths
|
101
|
+
#
|
102
|
+
# Returns a String.
|
103
|
+
def dependency_history_key
|
104
|
+
"asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Internal: Digest key
|
108
|
+
#
|
109
|
+
# Used to retrieve a string containing the "compressed" path to an asset based on
|
110
|
+
# a digest. The digest is generated from dependencies stored via information stored in
|
111
|
+
# the `dependency_history_key` after each of the "dependencies" is "resolved" for example
|
112
|
+
# "environment-version" may be resolved to "environment-1.0-3.2.0" for version "3.2.0" of sprockets
|
113
|
+
#
|
114
|
+
# Returns a String.
|
115
|
+
def digest_key(digest)
|
116
|
+
"asset-uri-digest:#{compressed_path}:#{digest}"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Internal: File digest key
|
120
|
+
#
|
121
|
+
# The digest for a given file won't change if the path and the stat time hasn't changed
|
122
|
+
# We can save time by not re-computing this information and storing it in the cache
|
123
|
+
#
|
124
|
+
# Returns a String.
|
125
|
+
def file_digest_key(stat)
|
126
|
+
"file_digest:#{compressed_path}:#{stat}"
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
# Internal: Parses uri into filename and params hash
|
131
|
+
#
|
132
|
+
# Returns Array with filename and params hash
|
133
|
+
def load_file_params
|
134
|
+
@filename, @params = URIUtils.parse_asset_uri(uri)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'sprockets/path_utils'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Internal: used to "expand" and "compress" values for storage
|
5
|
+
class URITar
|
6
|
+
attr_reader :scheme, :root, :path
|
7
|
+
|
8
|
+
# Internal: Initialize object for compression or expansion
|
9
|
+
#
|
10
|
+
# uri - A String containing URI that may or may not contain the scheme
|
11
|
+
# env - The current "environment" that assets are being loaded into.
|
12
|
+
def initialize(uri, env)
|
13
|
+
@root = env.root
|
14
|
+
@env = env
|
15
|
+
uri = uri.to_s
|
16
|
+
if uri.include?("://".freeze)
|
17
|
+
@scheme, _, @path = uri.partition("://".freeze)
|
18
|
+
@scheme << "://".freeze
|
19
|
+
else
|
20
|
+
@scheme = "".freeze
|
21
|
+
@path = uri
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal: Converts full uri to a "compressed" uri
|
26
|
+
#
|
27
|
+
# If a uri is inside of an environment's root it will
|
28
|
+
# be shortened to be a relative path.
|
29
|
+
#
|
30
|
+
# If a uri is outside of the environment's root the original
|
31
|
+
# uri will be returned.
|
32
|
+
#
|
33
|
+
# Returns String
|
34
|
+
def compress
|
35
|
+
scheme + compressed_path
|
36
|
+
end
|
37
|
+
|
38
|
+
# Internal: Tells us if we are using an absolute path
|
39
|
+
#
|
40
|
+
# Nix* systems start with a `/` like /Users/schneems.
|
41
|
+
# Windows systems start with a drive letter than colon and slash
|
42
|
+
# like C:/Schneems.
|
43
|
+
def absolute_path?
|
44
|
+
PathUtils.absolute_path?(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Internal: Convert a "compressed" uri to an absolute path
|
48
|
+
#
|
49
|
+
# If a uri is inside of the environment's root it will not
|
50
|
+
# start with a slash for example:
|
51
|
+
#
|
52
|
+
# file://this/is/a/relative/path
|
53
|
+
#
|
54
|
+
# If a uri is outside the root, it will start with a slash:
|
55
|
+
#
|
56
|
+
# file:///This/is/an/absolute/path
|
57
|
+
#
|
58
|
+
# Returns String
|
59
|
+
def expand
|
60
|
+
if absolute_path?
|
61
|
+
# Stored path was absolute, don't add root
|
62
|
+
scheme + path
|
63
|
+
else
|
64
|
+
if scheme.empty?
|
65
|
+
File.join(root, path)
|
66
|
+
else
|
67
|
+
# We always want to return an absolute uri,
|
68
|
+
# make sure the path starts with a slash.
|
69
|
+
scheme + File.join("/".freeze, root, path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Internal: Returns "compressed" path
|
75
|
+
#
|
76
|
+
# If the input uri is relative to the environment root
|
77
|
+
# it will return a path relative to the environment root.
|
78
|
+
# Otherwise an absolute path will be returned.
|
79
|
+
#
|
80
|
+
# Only path information is returned, and not scheme.
|
81
|
+
#
|
82
|
+
# Returns String
|
83
|
+
def compressed_path
|
84
|
+
# windows
|
85
|
+
if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
|
86
|
+
consistent_root = "/".freeze + @root
|
87
|
+
else
|
88
|
+
consistent_root = @root
|
89
|
+
end
|
90
|
+
|
91
|
+
if compressed_path = PathUtils.split_subpath(consistent_root, path)
|
92
|
+
compressed_path
|
93
|
+
else
|
94
|
+
path
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Internal: Asset URI related parsing utilities. Mixed into Environment.
|
5
|
+
#
|
6
|
+
# An Asset URI identifies the compiled Asset result. It shares the file:
|
7
|
+
# scheme and requires an absolute path.
|
8
|
+
#
|
9
|
+
# Other query parameters
|
10
|
+
#
|
11
|
+
# type - String output content type. Otherwise assumed from file extension.
|
12
|
+
# This maybe different than the extension if the asset is transformed
|
13
|
+
# from one content type to another. For an example .coffee -> .js.
|
14
|
+
#
|
15
|
+
# id - Unique fingerprint of the entire asset and all its metadata. Assets
|
16
|
+
# will only have the same id if they serialize to an identical value.
|
17
|
+
#
|
18
|
+
# pipeline - String name of pipeline.
|
19
|
+
#
|
20
|
+
module URIUtils
|
21
|
+
extend self
|
22
|
+
|
23
|
+
# Internal: Parse URI into component parts.
|
24
|
+
#
|
25
|
+
# uri - String uri
|
26
|
+
#
|
27
|
+
# Returns Array of components.
|
28
|
+
def split_uri(uri)
|
29
|
+
URI.split(uri)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: Join URI component parts into String.
|
33
|
+
#
|
34
|
+
# Returns String.
|
35
|
+
def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
|
36
|
+
URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal: Parse file: URI into component parts.
|
40
|
+
#
|
41
|
+
# uri - String uri
|
42
|
+
#
|
43
|
+
# Returns [scheme, host, path, query].
|
44
|
+
def split_file_uri(uri)
|
45
|
+
scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
|
46
|
+
|
47
|
+
path = URI::Generic::DEFAULT_PARSER.unescape(path)
|
48
|
+
path.force_encoding(Encoding::UTF_8)
|
49
|
+
|
50
|
+
# Hack for parsing Windows "file:///C:/Users/IEUser" paths
|
51
|
+
path.gsub!(/^\/([a-zA-Z]:)/, '\1'.freeze)
|
52
|
+
|
53
|
+
[scheme, host, path, query]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Internal: Join file: URI component parts into String.
|
57
|
+
#
|
58
|
+
# Returns String.
|
59
|
+
def join_file_uri(scheme, host, path, query)
|
60
|
+
str = "#{scheme}://"
|
61
|
+
str << host if host
|
62
|
+
path = "/#{path}" unless path.start_with?("/")
|
63
|
+
str << URI::Generic::DEFAULT_PARSER.escape(path)
|
64
|
+
str << "?#{query}" if query
|
65
|
+
str
|
66
|
+
end
|
67
|
+
|
68
|
+
# Internal: Check if String is a valid Asset URI.
|
69
|
+
#
|
70
|
+
# str - Possible String asset URI.
|
71
|
+
#
|
72
|
+
# Returns true or false.
|
73
|
+
def valid_asset_uri?(str)
|
74
|
+
# Quick prefix check before attempting a full parse
|
75
|
+
str.start_with?("file://") && parse_asset_uri(str) ? true : false
|
76
|
+
rescue URI::InvalidURIError
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# Internal: Parse Asset URI.
|
81
|
+
#
|
82
|
+
# Examples
|
83
|
+
#
|
84
|
+
# parse("file:///tmp/js/application.coffee?type=application/javascript")
|
85
|
+
# # => "/tmp/js/application.coffee", {type: "application/javascript"}
|
86
|
+
#
|
87
|
+
# uri - String asset URI
|
88
|
+
#
|
89
|
+
# Returns String path and Hash of symbolized parameters.
|
90
|
+
def parse_asset_uri(uri)
|
91
|
+
scheme, _, path, query = split_file_uri(uri)
|
92
|
+
|
93
|
+
unless scheme == 'file'
|
94
|
+
raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
|
95
|
+
end
|
96
|
+
|
97
|
+
return path, parse_uri_query_params(query)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Internal: Build Asset URI.
|
101
|
+
#
|
102
|
+
# Examples
|
103
|
+
#
|
104
|
+
# build("/tmp/js/application.coffee", type: "application/javascript")
|
105
|
+
# # => "file:///tmp/js/application.coffee?type=application/javascript"
|
106
|
+
#
|
107
|
+
# path - String file path
|
108
|
+
# params - Hash of optional parameters
|
109
|
+
#
|
110
|
+
# Returns String URI.
|
111
|
+
def build_asset_uri(path, params = {})
|
112
|
+
join_file_uri("file", nil, path, encode_uri_query_params(params))
|
113
|
+
end
|
114
|
+
|
115
|
+
# Internal: Parse file-digest dependency URI.
|
116
|
+
#
|
117
|
+
# Examples
|
118
|
+
#
|
119
|
+
# parse("file-digest:/tmp/js/application.js")
|
120
|
+
# # => "/tmp/js/application.js"
|
121
|
+
#
|
122
|
+
# uri - String file-digest URI
|
123
|
+
#
|
124
|
+
# Returns String path.
|
125
|
+
def parse_file_digest_uri(uri)
|
126
|
+
scheme, _, path, _ = split_file_uri(uri)
|
127
|
+
|
128
|
+
unless scheme == 'file-digest'.freeze
|
129
|
+
raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
|
130
|
+
end
|
131
|
+
|
132
|
+
path
|
133
|
+
end
|
134
|
+
|
135
|
+
# Internal: Build file-digest dependency URI.
|
136
|
+
#
|
137
|
+
# Examples
|
138
|
+
#
|
139
|
+
# build("/tmp/js/application.js")
|
140
|
+
# # => "file-digest:/tmp/js/application.js"
|
141
|
+
#
|
142
|
+
# path - String file path
|
143
|
+
#
|
144
|
+
# Returns String URI.
|
145
|
+
def build_file_digest_uri(path)
|
146
|
+
join_file_uri('file-digest'.freeze, nil, path, nil)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Internal: Serialize hash of params into query string.
|
150
|
+
#
|
151
|
+
# params - Hash of params to serialize
|
152
|
+
#
|
153
|
+
# Returns String query or nil if empty.
|
154
|
+
def encode_uri_query_params(params)
|
155
|
+
query = []
|
156
|
+
|
157
|
+
params.each do |key, value|
|
158
|
+
case value
|
159
|
+
when Integer
|
160
|
+
query << "#{key}=#{value}"
|
161
|
+
when String, Symbol
|
162
|
+
query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
|
163
|
+
when TrueClass
|
164
|
+
query << "#{key}"
|
165
|
+
when FalseClass, NilClass
|
166
|
+
else
|
167
|
+
raise TypeError, "unexpected type: #{value.class}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
"#{query.join('&')}" if query.any?
|
172
|
+
end
|
173
|
+
|
174
|
+
# Internal: Parse query string into hash of params
|
175
|
+
#
|
176
|
+
# query - String query string
|
177
|
+
#
|
178
|
+
# Return Hash of params.
|
179
|
+
def parse_uri_query_params(query)
|
180
|
+
query.to_s.split('&').reduce({}) do |h, p|
|
181
|
+
k, v = p.split('=', 2)
|
182
|
+
v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
|
183
|
+
h[k.to_sym] = v || true
|
184
|
+
h
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Sprockets
|
2
|
+
module Utils
|
3
|
+
class Gzip
|
4
|
+
# Private: Generates a gzipped file based off of reference file.
|
5
|
+
def initialize(asset)
|
6
|
+
@content_type = asset.content_type
|
7
|
+
@source = asset.source
|
8
|
+
@charset = asset.charset
|
9
|
+
end
|
10
|
+
|
11
|
+
# What non-text mime types should we compress? This list comes from:
|
12
|
+
# https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress
|
13
|
+
COMPRESSABLE_MIME_TYPES = {
|
14
|
+
"application/vnd.ms-fontobject" => true,
|
15
|
+
"application/x-font-opentype" => true,
|
16
|
+
"application/x-font-ttf" => true,
|
17
|
+
"image/x-icon" => true,
|
18
|
+
"image/svg+xml" => true
|
19
|
+
}
|
20
|
+
|
21
|
+
# Private: Returns whether or not an asset can be compressed.
|
22
|
+
#
|
23
|
+
# We want to compress any file that is text based.
|
24
|
+
# You do not want to compress binary
|
25
|
+
# files as they may already be compressed and running them
|
26
|
+
# through a compression algorithm would make them larger.
|
27
|
+
#
|
28
|
+
# Return Boolean.
|
29
|
+
def can_compress?(mime_types)
|
30
|
+
# The "charset" of a mime type is present if the value is
|
31
|
+
# encoded text. We can check this value to see if the asset
|
32
|
+
# can be compressed.
|
33
|
+
#
|
34
|
+
# We also check against our list of non-text compressible mime types
|
35
|
+
@charset || COMPRESSABLE_MIME_TYPES.include?(@content_type)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Private: Opposite of `can_compress?`.
|
39
|
+
#
|
40
|
+
# Returns Boolean.
|
41
|
+
def cannot_compress?(mime_types)
|
42
|
+
!can_compress?(mime_types)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Private: Generates a gzipped file based off of reference asset.
|
46
|
+
#
|
47
|
+
# Compresses the target asset's contents and puts it into a file with
|
48
|
+
# the same name plus a `.gz` extension in the same folder as the original.
|
49
|
+
# Does not modify the target asset.
|
50
|
+
#
|
51
|
+
# Returns nothing.
|
52
|
+
def compress(target)
|
53
|
+
mtime = PathUtils.stat(target).mtime
|
54
|
+
PathUtils.atomic_write("#{target}.gz") do |f|
|
55
|
+
gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
|
56
|
+
gz.mtime = mtime
|
57
|
+
gz.write(@source)
|
58
|
+
gz.close
|
59
|
+
|
60
|
+
File.utime(mtime, mtime, f.path)
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|