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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +72 -0
- data/README.md +665 -0
- data/bin/sprockets +93 -0
- data/lib/rake/sprocketstask.rb +153 -0
- data/lib/sprockets.rb +229 -0
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +202 -0
- data/lib/sprockets/autoload.rb +16 -0
- data/lib/sprockets/autoload/babel.rb +8 -0
- data/lib/sprockets/autoload/closure.rb +8 -0
- data/lib/sprockets/autoload/coffee_script.rb +8 -0
- data/lib/sprockets/autoload/eco.rb +8 -0
- data/lib/sprockets/autoload/ejs.rb +8 -0
- data/lib/sprockets/autoload/jsminc.rb +8 -0
- data/lib/sprockets/autoload/sass.rb +8 -0
- data/lib/sprockets/autoload/sassc.rb +8 -0
- data/lib/sprockets/autoload/uglifier.rb +8 -0
- data/lib/sprockets/autoload/yui.rb +8 -0
- data/lib/sprockets/autoload/zopfli.rb +7 -0
- data/lib/sprockets/babel_processor.rb +66 -0
- data/lib/sprockets/base.rb +147 -0
- data/lib/sprockets/bower.rb +61 -0
- data/lib/sprockets/bundle.rb +105 -0
- data/lib/sprockets/cache.rb +271 -0
- data/lib/sprockets/cache/file_store.rb +208 -0
- data/lib/sprockets/cache/memory_store.rb +75 -0
- data/lib/sprockets/cache/null_store.rb +54 -0
- data/lib/sprockets/cached_environment.rb +64 -0
- data/lib/sprockets/closure_compressor.rb +48 -0
- data/lib/sprockets/coffee_script_processor.rb +39 -0
- data/lib/sprockets/compressing.rb +134 -0
- data/lib/sprockets/configuration.rb +79 -0
- data/lib/sprockets/context.rb +304 -0
- data/lib/sprockets/dependencies.rb +74 -0
- data/lib/sprockets/digest_utils.rb +200 -0
- data/lib/sprockets/directive_processor.rb +414 -0
- data/lib/sprockets/eco_processor.rb +33 -0
- data/lib/sprockets/ejs_processor.rb +32 -0
- data/lib/sprockets/encoding_utils.rb +262 -0
- data/lib/sprockets/environment.rb +46 -0
- data/lib/sprockets/erb_processor.rb +37 -0
- data/lib/sprockets/errors.rb +12 -0
- data/lib/sprockets/exporters/base.rb +71 -0
- data/lib/sprockets/exporters/file_exporter.rb +24 -0
- data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
- data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
- data/lib/sprockets/exporting.rb +73 -0
- data/lib/sprockets/file_reader.rb +16 -0
- data/lib/sprockets/http_utils.rb +135 -0
- data/lib/sprockets/jsminc_compressor.rb +32 -0
- data/lib/sprockets/jst_processor.rb +50 -0
- data/lib/sprockets/loader.rb +345 -0
- data/lib/sprockets/manifest.rb +338 -0
- data/lib/sprockets/manifest_utils.rb +48 -0
- data/lib/sprockets/mime.rb +96 -0
- data/lib/sprockets/npm.rb +52 -0
- data/lib/sprockets/path_dependency_utils.rb +77 -0
- data/lib/sprockets/path_digest_utils.rb +48 -0
- data/lib/sprockets/path_utils.rb +367 -0
- data/lib/sprockets/paths.rb +82 -0
- data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
- data/lib/sprockets/processing.rb +228 -0
- data/lib/sprockets/processor_utils.rb +169 -0
- data/lib/sprockets/resolve.rb +295 -0
- data/lib/sprockets/sass_cache_store.rb +30 -0
- data/lib/sprockets/sass_compressor.rb +63 -0
- data/lib/sprockets/sass_functions.rb +3 -0
- data/lib/sprockets/sass_importer.rb +3 -0
- data/lib/sprockets/sass_processor.rb +313 -0
- data/lib/sprockets/sassc_compressor.rb +56 -0
- data/lib/sprockets/sassc_processor.rb +297 -0
- data/lib/sprockets/server.rb +295 -0
- data/lib/sprockets/source_map_processor.rb +66 -0
- data/lib/sprockets/source_map_utils.rb +483 -0
- data/lib/sprockets/transformers.rb +173 -0
- data/lib/sprockets/uglifier_compressor.rb +66 -0
- data/lib/sprockets/unloaded_asset.rb +139 -0
- data/lib/sprockets/uri_tar.rb +99 -0
- data/lib/sprockets/uri_utils.rb +191 -0
- data/lib/sprockets/utils.rb +202 -0
- data/lib/sprockets/utils/gzip.rb +99 -0
- data/lib/sprockets/version.rb +4 -0
- data/lib/sprockets/yui_compressor.rb +56 -0
- metadata +444 -0
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'set'
|
3
|
+
require 'sprockets/http_utils'
|
4
|
+
require 'sprockets/path_dependency_utils'
|
5
|
+
require 'sprockets/uri_utils'
|
6
|
+
|
7
|
+
module Sprockets
|
8
|
+
module Resolve
|
9
|
+
include HTTPUtils, PathDependencyUtils, URIUtils
|
10
|
+
|
11
|
+
# Public: Find Asset URI for given a logical path by searching the
|
12
|
+
# environment's load paths.
|
13
|
+
#
|
14
|
+
# resolve("application.js")
|
15
|
+
# # => "file:///path/to/app/javascripts/application.js?type=application/javascript"
|
16
|
+
#
|
17
|
+
# An accept content type can be given if the logical path doesn't have a
|
18
|
+
# format extension.
|
19
|
+
#
|
20
|
+
# resolve("application", accept: "application/javascript")
|
21
|
+
# # => "file:///path/to/app/javascripts/application.coffee?type=application/javascript"
|
22
|
+
#
|
23
|
+
# The String Asset URI is returned or nil if no results are found.
|
24
|
+
def resolve(path, load_paths: config[:paths], accept: nil, pipeline: nil, base_path: nil)
|
25
|
+
paths = load_paths
|
26
|
+
|
27
|
+
if valid_asset_uri?(path)
|
28
|
+
uri, deps = resolve_asset_uri(path)
|
29
|
+
elsif absolute_path?(path)
|
30
|
+
filename, type, deps = resolve_absolute_path(paths, path, accept)
|
31
|
+
elsif relative_path?(path)
|
32
|
+
filename, type, path_pipeline, deps, index_alias = resolve_relative_path(paths, path, base_path, accept)
|
33
|
+
else
|
34
|
+
filename, type, path_pipeline, deps, index_alias = resolve_logical_path(paths, path, accept)
|
35
|
+
end
|
36
|
+
|
37
|
+
if filename
|
38
|
+
uri = build_asset_uri(filename, type: type, pipeline: pipeline || path_pipeline, index_alias: index_alias)
|
39
|
+
end
|
40
|
+
|
41
|
+
return uri, deps
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Same as resolve() but raises a FileNotFound exception instead of
|
45
|
+
# nil if no assets are found.
|
46
|
+
def resolve!(path, **kargs)
|
47
|
+
uri, deps = resolve(path, **kargs)
|
48
|
+
|
49
|
+
unless uri
|
50
|
+
message = +"couldn't find file '#{path}'"
|
51
|
+
|
52
|
+
if relative_path?(path) && kargs[:base_path]
|
53
|
+
load_path, _ = paths_split(config[:paths], kargs[:base_path])
|
54
|
+
message << " under '#{load_path}'"
|
55
|
+
end
|
56
|
+
|
57
|
+
message << " with type '#{kargs[:accept]}'" if kargs[:accept]
|
58
|
+
|
59
|
+
load_paths = kargs[:load_paths] || config[:paths]
|
60
|
+
message << "\nChecked in these paths: \n #{ load_paths.join("\n ") }"
|
61
|
+
|
62
|
+
raise FileNotFound, message
|
63
|
+
end
|
64
|
+
|
65
|
+
return uri, deps
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
# Internal: Finds an asset given a URI
|
71
|
+
#
|
72
|
+
# uri - String. Contains file:// scheme, absolute path to
|
73
|
+
# file.
|
74
|
+
# e.g. "file:///Users/schneems/sprockets/test/fixtures/default/gallery.js?type=application/javascript"
|
75
|
+
#
|
76
|
+
# Returns Array. Contains a String uri and Set of dependencies
|
77
|
+
def resolve_asset_uri(uri)
|
78
|
+
filename, _ = URIUtils.parse_asset_uri(uri)
|
79
|
+
return uri, Set.new( [URIUtils.build_file_digest_uri(filename)] )
|
80
|
+
end
|
81
|
+
|
82
|
+
# Internal: Finds a file in a set of given paths
|
83
|
+
#
|
84
|
+
# paths - Array of Strings.
|
85
|
+
# filename - String containing absolute path to a file including extension.
|
86
|
+
# e.g. "/Users/schneems/sprockets/test/fixtures/asset/application.js"
|
87
|
+
# accept - String. A Quality value incoded set of
|
88
|
+
# mime types that we are looking for. Can be nil.
|
89
|
+
# e.g. "application/javascript" or "text/css, */*"
|
90
|
+
#
|
91
|
+
# Returns Array. Filename, type, path_pipeline, deps, index_alias
|
92
|
+
def resolve_absolute_path(paths, filename, accept)
|
93
|
+
deps = Set.new
|
94
|
+
filename = File.expand_path(filename)
|
95
|
+
|
96
|
+
# Ensure path is under load paths
|
97
|
+
return nil, nil, deps unless PathUtils.paths_split(paths, filename)
|
98
|
+
|
99
|
+
_, mime_type = PathUtils.match_path_extname(filename, config[:mime_exts])
|
100
|
+
type = resolve_transform_type(mime_type, accept)
|
101
|
+
return nil, nil, deps if accept && !type
|
102
|
+
|
103
|
+
return nil, nil, deps unless file?(filename)
|
104
|
+
|
105
|
+
deps << URIUtils.build_file_digest_uri(filename)
|
106
|
+
return filename, type, deps
|
107
|
+
end
|
108
|
+
|
109
|
+
# Internal: Finds a relative file in a set of given paths
|
110
|
+
#
|
111
|
+
# paths - Array of Strings.
|
112
|
+
# path - String. A relative filename with or without extension
|
113
|
+
# e.g. "./jquery" or "../foo.js"
|
114
|
+
# dirname - String. Base path where we start looking for the given file.
|
115
|
+
# accept - String. A Quality value incoded set of
|
116
|
+
# mime types that we are looking for. Can be nil.
|
117
|
+
# e.g. "application/javascript" or "text/css, */*"
|
118
|
+
#
|
119
|
+
# Returns Array. Filename, type, path_pipeline, deps, index_alias
|
120
|
+
def resolve_relative_path(paths, path, dirname, accept)
|
121
|
+
filename = File.expand_path(path, dirname)
|
122
|
+
load_path, _ = PathUtils.paths_split(paths, dirname)
|
123
|
+
if load_path && logical_path = PathUtils.split_subpath(load_path, filename)
|
124
|
+
resolve_logical_path([load_path], logical_path, accept)
|
125
|
+
else
|
126
|
+
return nil, nil, nil, Set.new
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Internal: Finds a file in a set of given paths
|
131
|
+
#
|
132
|
+
# paths - Array of Strings.
|
133
|
+
# logical_path - String. A filename with extension
|
134
|
+
# e.g. "coffee/foo.js" or "foo.js"
|
135
|
+
# accept - String. A Quality value incoded set of
|
136
|
+
# mime types that we are looking for. Can be nil.
|
137
|
+
# e.g. "application/javascript" or "text/css, */*"
|
138
|
+
#
|
139
|
+
# Finds a file on the given paths.
|
140
|
+
#
|
141
|
+
# Returns Array. Filename, type, path_pipeline, deps, index_alias
|
142
|
+
def resolve_logical_path(paths, logical_path, accept)
|
143
|
+
extname, mime_type = PathUtils.match_path_extname(logical_path, config[:mime_exts])
|
144
|
+
logical_name = logical_path.chomp(extname)
|
145
|
+
|
146
|
+
extname, pipeline = PathUtils.match_path_extname(logical_name, config[:pipeline_exts])
|
147
|
+
logical_name = logical_name.chomp(extname)
|
148
|
+
|
149
|
+
parsed_accept = parse_accept_options(mime_type, accept)
|
150
|
+
transformed_accepts = expand_transform_accepts(parsed_accept)
|
151
|
+
|
152
|
+
filename, mime_type, deps, index_alias = resolve_under_paths(paths, logical_name, transformed_accepts)
|
153
|
+
|
154
|
+
if filename
|
155
|
+
deps << build_file_digest_uri(filename)
|
156
|
+
type = resolve_transform_type(mime_type, parsed_accept)
|
157
|
+
return filename, type, pipeline, deps, index_alias
|
158
|
+
else
|
159
|
+
return nil, nil, nil, deps
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Internal: Finds a file in a set of given paths
|
164
|
+
#
|
165
|
+
# paths - Array of Strings.
|
166
|
+
# logical_name - String. A filename without extension
|
167
|
+
# e.g. "application" or "coffee/foo"
|
168
|
+
# accepts - Array of array containing mime/version pairs
|
169
|
+
# e.g. [["application/javascript", 1.0]]
|
170
|
+
#
|
171
|
+
# Finds a file with the same name as `logical_name` or "index" inside
|
172
|
+
# of the `logical_name` directory that matches a valid mime-type/version from
|
173
|
+
# `accepts`.
|
174
|
+
#
|
175
|
+
# Returns Array. Filename, type, dependencies, and index_alias
|
176
|
+
def resolve_under_paths(paths, logical_name, accepts)
|
177
|
+
deps = Set.new
|
178
|
+
return nil, nil, deps if accepts.empty?
|
179
|
+
|
180
|
+
# TODO: Allow new path resolves to be registered
|
181
|
+
@resolvers ||= [
|
182
|
+
method(:resolve_main_under_path),
|
183
|
+
method(:resolve_alts_under_path),
|
184
|
+
method(:resolve_index_under_path)
|
185
|
+
]
|
186
|
+
mime_exts = config[:mime_exts]
|
187
|
+
|
188
|
+
paths.each do |load_path|
|
189
|
+
candidates = []
|
190
|
+
@resolvers.each do |fn|
|
191
|
+
result = fn.call(load_path, logical_name, mime_exts)
|
192
|
+
candidates.concat(result[0])
|
193
|
+
deps.merge(result[1])
|
194
|
+
end
|
195
|
+
|
196
|
+
candidate = HTTPUtils.find_best_q_match(accepts, candidates) do |c, matcher|
|
197
|
+
match_mime_type?(c[:type] || "application/octet-stream", matcher)
|
198
|
+
end
|
199
|
+
return candidate[:filename], candidate[:type], deps, candidate[:index_alias] if candidate
|
200
|
+
end
|
201
|
+
|
202
|
+
return nil, nil, deps
|
203
|
+
end
|
204
|
+
|
205
|
+
# Internal: Finds candidate files on a given path
|
206
|
+
#
|
207
|
+
# load_path - String. An absolute path to a directory
|
208
|
+
# logical_name - String. A filename without extension
|
209
|
+
# e.g. "application" or "coffee/foo"
|
210
|
+
# mime_exts - Hash of file extensions and their mime types
|
211
|
+
# e.g. {".xml.builder"=>"application/xml+builder"}
|
212
|
+
#
|
213
|
+
# Finds files that match a given `logical_name` with an acceptable
|
214
|
+
# mime type that is included in `mime_exts` on the `load_path`.
|
215
|
+
#
|
216
|
+
# Returns Array. First element is an Array of hashes or empty, second is a String
|
217
|
+
def resolve_main_under_path(load_path, logical_name, mime_exts)
|
218
|
+
dirname = File.dirname(File.join(load_path, logical_name))
|
219
|
+
candidates = self.find_matching_path_for_extensions(dirname, File.basename(logical_name), mime_exts)
|
220
|
+
candidates.map! do |c|
|
221
|
+
{ filename: c[0], type: c[1] }
|
222
|
+
end
|
223
|
+
return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
# Internal: Finds candidate index files in a given path
|
228
|
+
#
|
229
|
+
# load_path - String. An absolute path to a directory
|
230
|
+
# logical_name - String. A filename without extension
|
231
|
+
# e.g. "application" or "coffee/foo"
|
232
|
+
# mime_exts - Hash of file extensions and their mime types
|
233
|
+
# e.g. {".xml.builder"=>"application/xml+builder"}
|
234
|
+
#
|
235
|
+
# Looking in the given `load_path` this method will find all files under the `logical_name` directory
|
236
|
+
# that are named `index` and have a matching mime type in `mime_exts`.
|
237
|
+
#
|
238
|
+
# Returns Array. First element is an Array of hashes or empty, second is a String
|
239
|
+
def resolve_index_under_path(load_path, logical_name, mime_exts)
|
240
|
+
dirname = File.join(load_path, logical_name)
|
241
|
+
|
242
|
+
if self.directory?(dirname)
|
243
|
+
candidates = self.find_matching_path_for_extensions(dirname, "index".freeze, mime_exts)
|
244
|
+
else
|
245
|
+
candidates = []
|
246
|
+
end
|
247
|
+
|
248
|
+
candidates.map! do |c|
|
249
|
+
{ filename: c[0],
|
250
|
+
type: c[1],
|
251
|
+
index_alias: compress_from_root(c[0].sub(/\/index(\.[^\/]+)$/, '\1')) }
|
252
|
+
end
|
253
|
+
|
254
|
+
return candidates, [ URIUtils.build_file_digest_uri(dirname) ]
|
255
|
+
end
|
256
|
+
|
257
|
+
def resolve_alts_under_path(load_path, logical_name, mime_exts)
|
258
|
+
filenames, deps = self.resolve_alternates(load_path, logical_name)
|
259
|
+
filenames.map! do |fn|
|
260
|
+
_, mime_type = PathUtils.match_path_extname(fn, mime_exts)
|
261
|
+
{ filename: fn, type: mime_type }
|
262
|
+
end
|
263
|
+
return filenames, deps
|
264
|
+
end
|
265
|
+
|
266
|
+
# Internal: Converts mimetype into accept Array
|
267
|
+
#
|
268
|
+
# - mime_type - String, optional. e.g. "text/html"
|
269
|
+
# - explicit_type - String, optional. e.g. "application/javascript"
|
270
|
+
#
|
271
|
+
# When called with an explicit_type and a mime_type, only a mime_type
|
272
|
+
# that matches the given explicit_type will be accepted.
|
273
|
+
#
|
274
|
+
# Returns Array of Array
|
275
|
+
#
|
276
|
+
# [["application/javascript", 1.0]]
|
277
|
+
# [["*/*", 1.0]]
|
278
|
+
# []
|
279
|
+
def parse_accept_options(mime_type, explicit_type)
|
280
|
+
if mime_type
|
281
|
+
return [[mime_type, 1.0]] if explicit_type.nil?
|
282
|
+
return [[mime_type, 1.0]] if HTTPUtils.parse_q_values(explicit_type).any? { |accept, _| HTTPUtils.match_mime_type?(mime_type, accept) }
|
283
|
+
return []
|
284
|
+
end
|
285
|
+
|
286
|
+
accepts = HTTPUtils.parse_q_values(explicit_type)
|
287
|
+
accepts << ['*/*'.freeze, 1.0] if accepts.empty?
|
288
|
+
return accepts
|
289
|
+
end
|
290
|
+
|
291
|
+
def resolve_alternates(load_path, logical_name)
|
292
|
+
return [], Set.new
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sass'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
class SassProcessor
|
6
|
+
# Internal: Cache wrapper for Sprockets cache adapter.
|
7
|
+
class CacheStore < ::Sass::CacheStores::Base
|
8
|
+
VERSION = '1'
|
9
|
+
|
10
|
+
def initialize(cache, version)
|
11
|
+
@cache, @version = cache, "#{VERSION}/#{version}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def _store(key, version, sha, contents)
|
15
|
+
@cache.set("#{@version}/#{version}/#{key}/#{sha}", contents, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def _retrieve(key, version, sha)
|
19
|
+
@cache.get("#{@version}/#{version}/#{key}/#{sha}", true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def path_to(key)
|
23
|
+
key
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Deprecated: Use Sprockets::SassProcessor::CacheStore instead.
|
29
|
+
SassCacheStore = SassProcessor::CacheStore
|
30
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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: Sass CSS minifier.
|
8
|
+
#
|
9
|
+
# To accept the default options
|
10
|
+
#
|
11
|
+
# environment.register_bundle_processor 'text/css',
|
12
|
+
# Sprockets::SassCompressor
|
13
|
+
#
|
14
|
+
# Or to pass options to the Sass::Engine class.
|
15
|
+
#
|
16
|
+
# environment.register_bundle_processor 'text/css',
|
17
|
+
# Sprockets::SassCompressor.new({ ... })
|
18
|
+
#
|
19
|
+
class SassCompressor
|
20
|
+
VERSION = '1'
|
21
|
+
|
22
|
+
# Public: Return singleton instance with default options.
|
23
|
+
#
|
24
|
+
# Returns SassCompressor 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 = {
|
41
|
+
syntax: :scss,
|
42
|
+
cache: false,
|
43
|
+
read_cache: false,
|
44
|
+
style: :compressed
|
45
|
+
}.merge(options).freeze
|
46
|
+
@cache_key = "#{self.class.name}:#{Autoload::Sass::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def call(input)
|
50
|
+
css, map = Autoload::Sass::Engine.new(
|
51
|
+
input[:data],
|
52
|
+
@options.merge(filename: input[:filename])
|
53
|
+
).render_with_sourcemap('')
|
54
|
+
|
55
|
+
css = css.sub("/*# sourceMappingURL= */\n", '')
|
56
|
+
|
57
|
+
map = SourceMapUtils.format_source_map(JSON.parse(map.to_json(css_uri: '')), input)
|
58
|
+
map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
|
59
|
+
|
60
|
+
{ data: css, map: map }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'sprockets/autoload'
|
4
|
+
require 'sprockets/source_map_utils'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Sprockets
|
8
|
+
# Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem.
|
9
|
+
#
|
10
|
+
# For more infomation see:
|
11
|
+
#
|
12
|
+
# https://github.com/sass/sass
|
13
|
+
# https://github.com/rails/sass-rails
|
14
|
+
#
|
15
|
+
class SassProcessor
|
16
|
+
autoload :CacheStore, 'sprockets/sass_cache_store'
|
17
|
+
|
18
|
+
# Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
|
19
|
+
# may override it.
|
20
|
+
def self.syntax
|
21
|
+
:sass
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Return singleton instance with default options.
|
25
|
+
#
|
26
|
+
# Returns SassProcessor object.
|
27
|
+
def self.instance
|
28
|
+
@instance ||= new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.call(input)
|
32
|
+
instance.call(input)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.cache_key
|
36
|
+
instance.cache_key
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :cache_key
|
40
|
+
|
41
|
+
# Public: Initialize template with custom options.
|
42
|
+
#
|
43
|
+
# options - Hash
|
44
|
+
# cache_version - String custom cache version. Used to force a cache
|
45
|
+
# change after code changes are made to Sass Functions.
|
46
|
+
#
|
47
|
+
def initialize(options = {}, &block)
|
48
|
+
@cache_version = options[:cache_version]
|
49
|
+
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
|
50
|
+
@importer_class = options[:importer] || Sass::Importers::Filesystem
|
51
|
+
@sass_config = options[:sass_config] || {}
|
52
|
+
@functions = Module.new do
|
53
|
+
include Functions
|
54
|
+
include options[:functions] if options[:functions]
|
55
|
+
class_eval(&block) if block_given?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def call(input)
|
60
|
+
context = input[:environment].context_class.new(input)
|
61
|
+
|
62
|
+
engine_options = merge_options({
|
63
|
+
filename: input[:filename],
|
64
|
+
syntax: self.class.syntax,
|
65
|
+
cache_store: build_cache_store(input, @cache_version),
|
66
|
+
load_paths: context.environment.paths.map { |p| @importer_class.new(p.to_s) },
|
67
|
+
importer: @importer_class.new(Pathname.new(context.filename).to_s),
|
68
|
+
sprockets: {
|
69
|
+
context: context,
|
70
|
+
environment: input[:environment],
|
71
|
+
dependencies: context.metadata[:dependencies]
|
72
|
+
}
|
73
|
+
})
|
74
|
+
|
75
|
+
engine = Autoload::Sass::Engine.new(input[:data], engine_options)
|
76
|
+
|
77
|
+
css, map = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
|
78
|
+
engine.render_with_sourcemap('')
|
79
|
+
end
|
80
|
+
|
81
|
+
css = css.sub("\n/*# sourceMappingURL= */\n", '')
|
82
|
+
|
83
|
+
map = SourceMapUtils.format_source_map(JSON.parse(map.to_json(css_uri: '')), input)
|
84
|
+
map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
|
85
|
+
|
86
|
+
# Track all imported files
|
87
|
+
sass_dependencies = Set.new([input[:filename]])
|
88
|
+
engine.dependencies.map do |dependency|
|
89
|
+
sass_dependencies << dependency.options[:filename]
|
90
|
+
context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename])
|
91
|
+
end
|
92
|
+
|
93
|
+
context.metadata.merge(data: css, sass_dependencies: sass_dependencies, map: map)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# Public: Build the cache store to be used by the Sass engine.
|
99
|
+
#
|
100
|
+
# input - the input hash.
|
101
|
+
# version - the cache version.
|
102
|
+
#
|
103
|
+
# Override this method if you need to use a different cache than the
|
104
|
+
# Sprockets cache.
|
105
|
+
def build_cache_store(input, version)
|
106
|
+
CacheStore.new(input[:cache], version)
|
107
|
+
end
|
108
|
+
|
109
|
+
def merge_options(options)
|
110
|
+
defaults = @sass_config.dup
|
111
|
+
|
112
|
+
if load_paths = defaults.delete(:load_paths)
|
113
|
+
options[:load_paths] += load_paths
|
114
|
+
end
|
115
|
+
|
116
|
+
options.merge!(defaults)
|
117
|
+
options
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: Functions injected into Sass context during Sprockets evaluation.
|
121
|
+
#
|
122
|
+
# This module may be extended to add global functionality to all Sprockets
|
123
|
+
# Sass environments. Though, scoping your functions to just your environment
|
124
|
+
# is preferred.
|
125
|
+
#
|
126
|
+
# module Sprockets::SassProcessor::Functions
|
127
|
+
# def asset_path(path, options = {})
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
module Functions
|
132
|
+
# Public: Generate a url for asset path.
|
133
|
+
#
|
134
|
+
# Default implementation is deprecated. Currently defaults to
|
135
|
+
# Context#asset_path.
|
136
|
+
#
|
137
|
+
# Will raise NotImplementedError in the future. Users should provide their
|
138
|
+
# own base implementation.
|
139
|
+
#
|
140
|
+
# Returns a Sass::Script::String.
|
141
|
+
def asset_path(path, options = {})
|
142
|
+
path = path.value
|
143
|
+
|
144
|
+
path, _, query, fragment = URI.split(path)[5..8]
|
145
|
+
path = sprockets_context.asset_path(path, options)
|
146
|
+
query = "?#{query}" if query
|
147
|
+
fragment = "##{fragment}" if fragment
|
148
|
+
|
149
|
+
Autoload::Sass::Script::String.new("#{path}#{query}#{fragment}", :string)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Public: Generate a asset url() link.
|
153
|
+
#
|
154
|
+
# path - Sass::Script::String URL path
|
155
|
+
#
|
156
|
+
# Returns a Sass::Script::String.
|
157
|
+
def asset_url(path, options = {})
|
158
|
+
Autoload::Sass::Script::String.new("url(#{asset_path(path, options).value})")
|
159
|
+
end
|
160
|
+
|
161
|
+
# Public: Generate url for image path.
|
162
|
+
#
|
163
|
+
# path - Sass::Script::String URL path
|
164
|
+
#
|
165
|
+
# Returns a Sass::Script::String.
|
166
|
+
def image_path(path)
|
167
|
+
asset_path(path, type: :image)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Public: Generate a image url() link.
|
171
|
+
#
|
172
|
+
# path - Sass::Script::String URL path
|
173
|
+
#
|
174
|
+
# Returns a Sass::Script::String.
|
175
|
+
def image_url(path)
|
176
|
+
asset_url(path, type: :image)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Public: Generate url for video path.
|
180
|
+
#
|
181
|
+
# path - Sass::Script::String URL path
|
182
|
+
#
|
183
|
+
# Returns a Sass::Script::String.
|
184
|
+
def video_path(path)
|
185
|
+
asset_path(path, type: :video)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Public: Generate a video url() link.
|
189
|
+
#
|
190
|
+
# path - Sass::Script::String URL path
|
191
|
+
#
|
192
|
+
# Returns a Sass::Script::String.
|
193
|
+
def video_url(path)
|
194
|
+
asset_url(path, type: :video)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Public: Generate url for audio path.
|
198
|
+
#
|
199
|
+
# path - Sass::Script::String URL path
|
200
|
+
#
|
201
|
+
# Returns a Sass::Script::String.
|
202
|
+
def audio_path(path)
|
203
|
+
asset_path(path, type: :audio)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Public: Generate a audio url() link.
|
207
|
+
#
|
208
|
+
# path - Sass::Script::String URL path
|
209
|
+
#
|
210
|
+
# Returns a Sass::Script::String.
|
211
|
+
def audio_url(path)
|
212
|
+
asset_url(path, type: :audio)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Public: Generate url for font path.
|
216
|
+
#
|
217
|
+
# path - Sass::Script::String URL path
|
218
|
+
#
|
219
|
+
# Returns a Sass::Script::String.
|
220
|
+
def font_path(path)
|
221
|
+
asset_path(path, type: :font)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Public: Generate a font url() link.
|
225
|
+
#
|
226
|
+
# path - Sass::Script::String URL path
|
227
|
+
#
|
228
|
+
# Returns a Sass::Script::String.
|
229
|
+
def font_url(path)
|
230
|
+
asset_url(path, type: :font)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Public: Generate url for javascript path.
|
234
|
+
#
|
235
|
+
# path - Sass::Script::String URL path
|
236
|
+
#
|
237
|
+
# Returns a Sass::Script::String.
|
238
|
+
def javascript_path(path)
|
239
|
+
asset_path(path, type: :javascript)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Public: Generate a javascript url() link.
|
243
|
+
#
|
244
|
+
# path - Sass::Script::String URL path
|
245
|
+
#
|
246
|
+
# Returns a Sass::Script::String.
|
247
|
+
def javascript_url(path)
|
248
|
+
asset_url(path, type: :javascript)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Public: Generate url for stylesheet path.
|
252
|
+
#
|
253
|
+
# path - Sass::Script::String URL path
|
254
|
+
#
|
255
|
+
# Returns a Sass::Script::String.
|
256
|
+
def stylesheet_path(path)
|
257
|
+
asset_path(path, type: :stylesheet)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Public: Generate a stylesheet url() link.
|
261
|
+
#
|
262
|
+
# path - Sass::Script::String URL path
|
263
|
+
#
|
264
|
+
# Returns a Sass::Script::String.
|
265
|
+
def stylesheet_url(path)
|
266
|
+
asset_url(path, type: :stylesheet)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Public: Generate a data URI for asset path.
|
270
|
+
#
|
271
|
+
# path - Sass::Script::String logical asset path
|
272
|
+
#
|
273
|
+
# Returns a Sass::Script::String.
|
274
|
+
def asset_data_url(path)
|
275
|
+
url = sprockets_context.asset_data_uri(path.value)
|
276
|
+
Autoload::Sass::Script::String.new("url(" + url + ")")
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
# Public: The Environment.
|
281
|
+
#
|
282
|
+
# Returns Sprockets::Environment.
|
283
|
+
def sprockets_environment
|
284
|
+
options[:sprockets][:environment]
|
285
|
+
end
|
286
|
+
|
287
|
+
# Public: Mutatable set of dependencies.
|
288
|
+
#
|
289
|
+
# Returns a Set.
|
290
|
+
def sprockets_dependencies
|
291
|
+
options[:sprockets][:dependencies]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Deprecated: Get the Context instance. Use APIs on
|
295
|
+
# sprockets_environment or sprockets_dependencies directly.
|
296
|
+
#
|
297
|
+
# Returns a Context instance.
|
298
|
+
def sprockets_context
|
299
|
+
options[:sprockets][:context]
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
class ScssProcessor < SassProcessor
|
306
|
+
def self.syntax
|
307
|
+
:scss
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Deprecated: Use Sprockets::SassProcessor::Functions instead.
|
312
|
+
SassFunctions = SassProcessor::Functions
|
313
|
+
end
|