sprockets 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sprockets/digest_utils'
|
3
|
+
require 'sprockets/path_digest_utils'
|
4
|
+
require 'sprockets/uri_utils'
|
5
|
+
|
6
|
+
module Sprockets
|
7
|
+
# `Dependencies` is an internal mixin whose public methods are exposed on the
|
8
|
+
# `Environment` and `CachedEnvironment` classes.
|
9
|
+
module Dependencies
|
10
|
+
include DigestUtils, PathDigestUtils, URIUtils
|
11
|
+
|
12
|
+
# Public: Mapping dependency schemes to resolver functions.
|
13
|
+
#
|
14
|
+
# key - String scheme
|
15
|
+
# value - Proc.call(Environment, String)
|
16
|
+
#
|
17
|
+
# Returns Hash.
|
18
|
+
def dependency_resolvers
|
19
|
+
config[:dependency_resolvers]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: Default set of dependency URIs for assets.
|
23
|
+
#
|
24
|
+
# Returns Set of String URIs.
|
25
|
+
def dependencies
|
26
|
+
config[:dependencies]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Register new dependency URI resolver.
|
30
|
+
#
|
31
|
+
# scheme - String scheme
|
32
|
+
# block -
|
33
|
+
# environment - Environment
|
34
|
+
# uri - String dependency URI
|
35
|
+
#
|
36
|
+
# Returns nothing.
|
37
|
+
def register_dependency_resolver(scheme, &block)
|
38
|
+
self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
|
39
|
+
hash.merge(scheme => block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Add environmental dependency inheirted by all assets.
|
44
|
+
#
|
45
|
+
# uri - String dependency URI
|
46
|
+
#
|
47
|
+
# Returns nothing.
|
48
|
+
def add_dependency(uri)
|
49
|
+
self.config = hash_reassoc(config, :dependencies) do |set|
|
50
|
+
set + Set.new([uri])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
alias_method :depend_on, :add_dependency
|
54
|
+
|
55
|
+
# Internal: Resolve dependency URIs.
|
56
|
+
#
|
57
|
+
# Returns resolved Object.
|
58
|
+
def resolve_dependency(str)
|
59
|
+
# Optimize for the most common scheme to
|
60
|
+
# save 22k allocations on an average Spree app.
|
61
|
+
scheme = if str.start_with?('file-digest:'.freeze)
|
62
|
+
'file-digest'.freeze
|
63
|
+
else
|
64
|
+
str[/([^:]+)/, 1]
|
65
|
+
end
|
66
|
+
|
67
|
+
if resolver = config[:dependency_resolvers][scheme]
|
68
|
+
resolver.call(self, str)
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'digest/sha2'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module Sprockets
|
8
|
+
# Internal: Hash functions and digest related utilities. Mixed into
|
9
|
+
# Environment.
|
10
|
+
module DigestUtils
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# Internal: Default digest class.
|
14
|
+
#
|
15
|
+
# Returns a Digest::Base subclass.
|
16
|
+
def digest_class
|
17
|
+
Digest::SHA256
|
18
|
+
end
|
19
|
+
|
20
|
+
# Internal: Maps digest bytesize to the digest class.
|
21
|
+
DIGEST_SIZES = {
|
22
|
+
16 => Digest::MD5,
|
23
|
+
20 => Digest::SHA1,
|
24
|
+
32 => Digest::SHA256,
|
25
|
+
48 => Digest::SHA384,
|
26
|
+
64 => Digest::SHA512
|
27
|
+
}
|
28
|
+
|
29
|
+
# Internal: Detect digest class hash algorithm for digest bytes.
|
30
|
+
#
|
31
|
+
# While not elegant, all the supported digests have a unique bytesize.
|
32
|
+
#
|
33
|
+
# Returns Digest::Base or nil.
|
34
|
+
def detect_digest_class(bytes)
|
35
|
+
DIGEST_SIZES[bytes.bytesize]
|
36
|
+
end
|
37
|
+
|
38
|
+
ADD_VALUE_TO_DIGEST = {
|
39
|
+
String => ->(val, digest) { digest << val },
|
40
|
+
FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze },
|
41
|
+
TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze },
|
42
|
+
NilClass => ->(val, digest) { digest << 'NilClass'.freeze },
|
43
|
+
|
44
|
+
Symbol => ->(val, digest) {
|
45
|
+
digest << 'Symbol'.freeze
|
46
|
+
digest << val.to_s
|
47
|
+
},
|
48
|
+
Integer => ->(val, digest) {
|
49
|
+
digest << 'Integer'.freeze
|
50
|
+
digest << val.to_s
|
51
|
+
},
|
52
|
+
Array => ->(val, digest) {
|
53
|
+
digest << 'Array'.freeze
|
54
|
+
val.each do |element|
|
55
|
+
ADD_VALUE_TO_DIGEST[element.class].call(element, digest)
|
56
|
+
end
|
57
|
+
},
|
58
|
+
Hash => ->(val, digest) {
|
59
|
+
digest << 'Hash'.freeze
|
60
|
+
val.sort.each do |array|
|
61
|
+
ADD_VALUE_TO_DIGEST[Array].call(array, digest)
|
62
|
+
end
|
63
|
+
},
|
64
|
+
Set => ->(val, digest) {
|
65
|
+
digest << 'Set'.freeze
|
66
|
+
ADD_VALUE_TO_DIGEST[Array].call(val, digest)
|
67
|
+
},
|
68
|
+
Encoding => ->(val, digest) {
|
69
|
+
digest << 'Encoding'.freeze
|
70
|
+
digest << val.name
|
71
|
+
},
|
72
|
+
}
|
73
|
+
if 0.class != Integer # Ruby < 2.4
|
74
|
+
ADD_VALUE_TO_DIGEST[Fixnum] = ->(val, digest) {
|
75
|
+
digest << 'Integer'.freeze
|
76
|
+
digest << val.to_s
|
77
|
+
}
|
78
|
+
ADD_VALUE_TO_DIGEST[Bignum] = ->(val, digest) {
|
79
|
+
digest << 'Integer'.freeze
|
80
|
+
digest << val.to_s
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
ADD_VALUE_TO_DIGEST.compare_by_identity.rehash
|
85
|
+
|
86
|
+
ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
|
87
|
+
raise TypeError, "couldn't digest #{ val }"
|
88
|
+
}
|
89
|
+
private_constant :ADD_VALUE_TO_DIGEST
|
90
|
+
|
91
|
+
# Internal: Generate a hexdigest for a nested JSON serializable object.
|
92
|
+
#
|
93
|
+
# This is used for generating cache keys, so its pretty important its
|
94
|
+
# wicked fast. Microbenchmarks away!
|
95
|
+
#
|
96
|
+
# obj - A JSON serializable object.
|
97
|
+
#
|
98
|
+
# Returns a String digest of the object.
|
99
|
+
def digest(obj)
|
100
|
+
build_digest(obj).digest
|
101
|
+
end
|
102
|
+
|
103
|
+
# Internal: Generate a hexdigest for a nested JSON serializable object.
|
104
|
+
#
|
105
|
+
# The same as `pack_hexdigest(digest(obj))`.
|
106
|
+
#
|
107
|
+
# obj - A JSON serializable object.
|
108
|
+
#
|
109
|
+
# Returns a String digest of the object.
|
110
|
+
def hexdigest(obj)
|
111
|
+
build_digest(obj).hexdigest!
|
112
|
+
end
|
113
|
+
|
114
|
+
# Internal: Pack a binary digest to a hex encoded string.
|
115
|
+
#
|
116
|
+
# bin - String bytes
|
117
|
+
#
|
118
|
+
# Returns hex String.
|
119
|
+
def pack_hexdigest(bin)
|
120
|
+
bin.unpack('H*'.freeze).first
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Unpack a hex encoded digest string into binary bytes.
|
124
|
+
#
|
125
|
+
# hex - String hex
|
126
|
+
#
|
127
|
+
# Returns binary String.
|
128
|
+
def unpack_hexdigest(hex)
|
129
|
+
[hex].pack('H*')
|
130
|
+
end
|
131
|
+
|
132
|
+
# Internal: Pack a binary digest to a base64 encoded string.
|
133
|
+
#
|
134
|
+
# bin - String bytes
|
135
|
+
#
|
136
|
+
# Returns base64 String.
|
137
|
+
def pack_base64digest(bin)
|
138
|
+
[bin].pack('m0')
|
139
|
+
end
|
140
|
+
|
141
|
+
# Internal: Pack a binary digest to a urlsafe base64 encoded string.
|
142
|
+
#
|
143
|
+
# bin - String bytes
|
144
|
+
#
|
145
|
+
# Returns urlsafe base64 String.
|
146
|
+
def pack_urlsafe_base64digest(bin)
|
147
|
+
str = pack_base64digest(bin)
|
148
|
+
str.tr!('+/'.freeze, '-_'.freeze)
|
149
|
+
str.tr!('='.freeze, ''.freeze)
|
150
|
+
str
|
151
|
+
end
|
152
|
+
|
153
|
+
# Internal: Maps digest class to the CSP hash algorithm name.
|
154
|
+
HASH_ALGORITHMS = {
|
155
|
+
Digest::SHA256 => 'sha256'.freeze,
|
156
|
+
Digest::SHA384 => 'sha384'.freeze,
|
157
|
+
Digest::SHA512 => 'sha512'.freeze
|
158
|
+
}
|
159
|
+
|
160
|
+
# Public: Generate hash for use in the `integrity` attribute of an asset tag
|
161
|
+
# as per the subresource integrity specification.
|
162
|
+
#
|
163
|
+
# digest - The String byte digest of the asset content.
|
164
|
+
#
|
165
|
+
# Returns a String or nil if hash algorithm is incompatible.
|
166
|
+
def integrity_uri(digest)
|
167
|
+
case digest
|
168
|
+
when Digest::Base
|
169
|
+
digest_class = digest.class
|
170
|
+
digest = digest.digest
|
171
|
+
when String
|
172
|
+
digest_class = DIGEST_SIZES[digest.bytesize]
|
173
|
+
else
|
174
|
+
raise TypeError, "unknown digest: #{digest.inspect}"
|
175
|
+
end
|
176
|
+
|
177
|
+
if hash_name = HASH_ALGORITHMS[digest_class]
|
178
|
+
"#{hash_name}-#{pack_base64digest(digest)}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Public: Generate hash for use in the `integrity` attribute of an asset tag
|
183
|
+
# as per the subresource integrity specification.
|
184
|
+
#
|
185
|
+
# digest - The String hexbyte digest of the asset content.
|
186
|
+
#
|
187
|
+
# Returns a String or nil if hash algorithm is incompatible.
|
188
|
+
def hexdigest_integrity_uri(hexdigest)
|
189
|
+
integrity_uri(unpack_hexdigest(hexdigest))
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def build_digest(obj)
|
194
|
+
digest = digest_class.new
|
195
|
+
|
196
|
+
ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest)
|
197
|
+
digest
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,414 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'set'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
# The `DirectiveProcessor` is responsible for parsing and evaluating
|
7
|
+
# directive comments in a source file.
|
8
|
+
#
|
9
|
+
# A directive comment starts with a comment prefix, followed by an "=",
|
10
|
+
# then the directive name, then any arguments.
|
11
|
+
#
|
12
|
+
# // JavaScript
|
13
|
+
# //= require "foo"
|
14
|
+
#
|
15
|
+
# # CoffeeScript
|
16
|
+
# #= require "bar"
|
17
|
+
#
|
18
|
+
# /* CSS
|
19
|
+
# *= require "baz"
|
20
|
+
# */
|
21
|
+
#
|
22
|
+
# This makes it possible to disable or modify the processor to do whatever
|
23
|
+
# you'd like. You could add your own custom directives or invent your own
|
24
|
+
# directive syntax.
|
25
|
+
#
|
26
|
+
# `Environment#processors` includes `DirectiveProcessor` by default.
|
27
|
+
#
|
28
|
+
# To remove the processor entirely:
|
29
|
+
#
|
30
|
+
# env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
|
31
|
+
# env.unregister_processor('application/javascript', Sprockets::DirectiveProcessor)
|
32
|
+
#
|
33
|
+
# Then inject your own preprocessor:
|
34
|
+
#
|
35
|
+
# env.register_processor('text/css', MyProcessor)
|
36
|
+
#
|
37
|
+
class DirectiveProcessor
|
38
|
+
# Directives are denoted by a `=` followed by the name, then
|
39
|
+
# argument list.
|
40
|
+
#
|
41
|
+
# A few different styles are allowed:
|
42
|
+
#
|
43
|
+
# // =require foo
|
44
|
+
# //= require foo
|
45
|
+
# //= require "foo"
|
46
|
+
#
|
47
|
+
DIRECTIVE_PATTERN = /
|
48
|
+
^ \W* = \s* (\w+.*?) (\*\/)? $
|
49
|
+
/x
|
50
|
+
|
51
|
+
def self.instance
|
52
|
+
# Default to C comment styles
|
53
|
+
@instance ||= new(comments: ["//", ["/*", "*/"]])
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.call(input)
|
57
|
+
instance.call(input)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(comments: [])
|
61
|
+
@header_pattern = compile_header_pattern(Array(comments))
|
62
|
+
end
|
63
|
+
|
64
|
+
def call(input)
|
65
|
+
dup._call(input)
|
66
|
+
end
|
67
|
+
|
68
|
+
def _call(input)
|
69
|
+
@environment = input[:environment]
|
70
|
+
@uri = input[:uri]
|
71
|
+
@filename = input[:filename]
|
72
|
+
@dirname = File.dirname(@filename)
|
73
|
+
# If loading a source map file like `application.js.map` resolve
|
74
|
+
# dependencies using `.js` instead of `.js.map`
|
75
|
+
@content_type = SourceMapProcessor.original_content_type(input[:content_type], error_when_not_found: false)
|
76
|
+
@required = Set.new(input[:metadata][:required])
|
77
|
+
@stubbed = Set.new(input[:metadata][:stubbed])
|
78
|
+
@links = Set.new(input[:metadata][:links])
|
79
|
+
@dependencies = Set.new(input[:metadata][:dependencies])
|
80
|
+
@to_link = Set.new
|
81
|
+
@to_load = Set.new
|
82
|
+
|
83
|
+
data, directives = process_source(input[:data])
|
84
|
+
process_directives(directives)
|
85
|
+
|
86
|
+
{
|
87
|
+
data: data,
|
88
|
+
required: @required,
|
89
|
+
stubbed: @stubbed,
|
90
|
+
links: @links,
|
91
|
+
to_load: @to_load,
|
92
|
+
to_link: @to_link,
|
93
|
+
dependencies: @dependencies
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
# Directives will only be picked up if they are in the header
|
99
|
+
# of the source file. C style (/* */), JavaScript (//), and
|
100
|
+
# Ruby (#) comments are supported.
|
101
|
+
#
|
102
|
+
# Directives in comments after the first non-whitespace line
|
103
|
+
# of code will not be processed.
|
104
|
+
def compile_header_pattern(comments)
|
105
|
+
re = comments.map { |c|
|
106
|
+
case c
|
107
|
+
when String
|
108
|
+
"(?:#{Regexp.escape(c)}.*\\n?)+"
|
109
|
+
when Array
|
110
|
+
"(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})"
|
111
|
+
else
|
112
|
+
raise TypeError, "unknown comment type: #{c.class}"
|
113
|
+
end
|
114
|
+
}.join("|")
|
115
|
+
Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+")
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_source(source)
|
119
|
+
header = source[@header_pattern, 0] || ""
|
120
|
+
body = $' || source
|
121
|
+
|
122
|
+
header, directives = extract_directives(header)
|
123
|
+
|
124
|
+
data = +""
|
125
|
+
data.force_encoding(body.encoding)
|
126
|
+
data << header unless header.empty?
|
127
|
+
data << body
|
128
|
+
# Ensure body ends in a new line
|
129
|
+
data << "\n" if data.length > 0 && data[-1] != "\n"
|
130
|
+
|
131
|
+
return data, directives
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns an Array of directive structures. Each structure
|
135
|
+
# is an Array with the line number as the first element, the
|
136
|
+
# directive name as the second element, followed by any
|
137
|
+
# arguments.
|
138
|
+
#
|
139
|
+
# [[1, "require", "foo"], [2, "require", "bar"]]
|
140
|
+
#
|
141
|
+
def extract_directives(header)
|
142
|
+
processed_header = +""
|
143
|
+
directives = []
|
144
|
+
|
145
|
+
header.lines.each_with_index do |line, index|
|
146
|
+
if directive = line[DIRECTIVE_PATTERN, 1]
|
147
|
+
name, *args = Shellwords.shellwords(directive)
|
148
|
+
if respond_to?("process_#{name}_directive", true)
|
149
|
+
directives << [index + 1, name, *args]
|
150
|
+
# Replace directive line with a clean break
|
151
|
+
line = "\n"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
processed_header << line
|
155
|
+
end
|
156
|
+
|
157
|
+
processed_header.chomp!
|
158
|
+
# Ensure header ends in a new line like before it was processed
|
159
|
+
processed_header << "\n" if processed_header.length > 0 && header[-1] == "\n"
|
160
|
+
|
161
|
+
return processed_header, directives
|
162
|
+
end
|
163
|
+
|
164
|
+
# Gathers comment directives in the source and processes them.
|
165
|
+
# Any directive method matching `process_*_directive` will
|
166
|
+
# automatically be available. This makes it easy to extend the
|
167
|
+
# processor.
|
168
|
+
#
|
169
|
+
# To implement a custom directive called `require_glob`, subclass
|
170
|
+
# `Sprockets::DirectiveProcessor`, then add a method called
|
171
|
+
# `process_require_glob_directive`.
|
172
|
+
#
|
173
|
+
# class DirectiveProcessor < Sprockets::DirectiveProcessor
|
174
|
+
# def process_require_glob_directive(glob)
|
175
|
+
# Dir["#{dirname}/#{glob}"].sort.each do |filename|
|
176
|
+
# require(filename)
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# Replace the current processor on the environment with your own:
|
182
|
+
#
|
183
|
+
# env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
|
184
|
+
# env.register_processor('text/css', DirectiveProcessor)
|
185
|
+
#
|
186
|
+
def process_directives(directives)
|
187
|
+
directives.each do |line_number, name, *args|
|
188
|
+
begin
|
189
|
+
send("process_#{name}_directive", *args)
|
190
|
+
rescue Exception => e
|
191
|
+
e.set_backtrace(["#{@filename}:#{line_number}"] + e.backtrace)
|
192
|
+
raise e
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# The `require` directive functions similar to Ruby's own `require`.
|
198
|
+
# It provides a way to declare a dependency on a file in your path
|
199
|
+
# and ensures it's only loaded once before the source file.
|
200
|
+
#
|
201
|
+
# `require` works with files in the environment path:
|
202
|
+
#
|
203
|
+
# //= require "foo.js"
|
204
|
+
#
|
205
|
+
# Extensions are optional. If your source file is ".js", it
|
206
|
+
# assumes you are requiring another ".js".
|
207
|
+
#
|
208
|
+
# //= require "foo"
|
209
|
+
#
|
210
|
+
# Relative paths work too. Use a leading `./` to denote a relative
|
211
|
+
# path:
|
212
|
+
#
|
213
|
+
# //= require "./bar"
|
214
|
+
#
|
215
|
+
def process_require_directive(path)
|
216
|
+
@required << resolve(path, accept: @content_type, pipeline: :self)
|
217
|
+
end
|
218
|
+
|
219
|
+
# `require_self` causes the body of the current file to be inserted
|
220
|
+
# before any subsequent `require` directives. Useful in CSS files, where
|
221
|
+
# it's common for the index file to contain global styles that need to
|
222
|
+
# be defined before other dependencies are loaded.
|
223
|
+
#
|
224
|
+
# /*= require "reset"
|
225
|
+
# *= require_self
|
226
|
+
# *= require_tree .
|
227
|
+
# */
|
228
|
+
#
|
229
|
+
def process_require_self_directive
|
230
|
+
if @required.include?(@uri)
|
231
|
+
raise ArgumentError, "require_self can only be called once per source file"
|
232
|
+
end
|
233
|
+
@required << @uri
|
234
|
+
end
|
235
|
+
|
236
|
+
# `require_directory` requires all the files inside a single
|
237
|
+
# directory. It's similar to `path/*` since it does not follow
|
238
|
+
# nested directories.
|
239
|
+
#
|
240
|
+
# //= require_directory "./javascripts"
|
241
|
+
#
|
242
|
+
def process_require_directory_directive(path = ".")
|
243
|
+
path = expand_relative_dirname(:require_directory, path)
|
244
|
+
require_paths(*@environment.stat_directory_with_dependencies(path))
|
245
|
+
end
|
246
|
+
|
247
|
+
# `require_tree` requires all the nested files in a directory.
|
248
|
+
# Its glob equivalent is `path/**/*`.
|
249
|
+
#
|
250
|
+
# //= require_tree "./public"
|
251
|
+
#
|
252
|
+
def process_require_tree_directive(path = ".")
|
253
|
+
path = expand_relative_dirname(:require_tree, path)
|
254
|
+
require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
|
255
|
+
end
|
256
|
+
|
257
|
+
# Allows you to state a dependency on a file without
|
258
|
+
# including it.
|
259
|
+
#
|
260
|
+
# This is used for caching purposes. Any changes made to
|
261
|
+
# the dependency file will invalidate the cache of the
|
262
|
+
# source file.
|
263
|
+
#
|
264
|
+
# This is useful if you are using ERB and File.read to pull
|
265
|
+
# in contents from another file.
|
266
|
+
#
|
267
|
+
# //= depend_on "foo.png"
|
268
|
+
#
|
269
|
+
def process_depend_on_directive(path)
|
270
|
+
resolve(path)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Allows you to state a dependency on an asset without including
|
274
|
+
# it.
|
275
|
+
#
|
276
|
+
# This is used for caching purposes. Any changes that would
|
277
|
+
# invalidate the asset dependency will invalidate the cache of
|
278
|
+
# the source file.
|
279
|
+
#
|
280
|
+
# Unlike `depend_on`, the path must be a requirable asset.
|
281
|
+
#
|
282
|
+
# //= depend_on_asset "bar.js"
|
283
|
+
#
|
284
|
+
def process_depend_on_asset_directive(path)
|
285
|
+
to_load(resolve(path))
|
286
|
+
end
|
287
|
+
|
288
|
+
# Allows dependency to be excluded from the asset bundle.
|
289
|
+
#
|
290
|
+
# The `path` must be a valid asset and may or may not already
|
291
|
+
# be part of the bundle. Once stubbed, it is blacklisted and
|
292
|
+
# can't be brought back by any other `require`.
|
293
|
+
#
|
294
|
+
# //= stub "jquery"
|
295
|
+
#
|
296
|
+
def process_stub_directive(path)
|
297
|
+
@stubbed << resolve(path, accept: @content_type, pipeline: :self)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Declares a linked dependency on the target asset.
|
301
|
+
#
|
302
|
+
# The `path` must be a valid asset and should not already be part of the
|
303
|
+
# bundle. Any linked assets will automatically be compiled along with the
|
304
|
+
# current.
|
305
|
+
#
|
306
|
+
# /*= link "logo.png" */
|
307
|
+
#
|
308
|
+
def process_link_directive(path)
|
309
|
+
uri = to_load(resolve(path))
|
310
|
+
@to_link << uri
|
311
|
+
end
|
312
|
+
|
313
|
+
# `link_directory` links all the files inside a single
|
314
|
+
# directory. It's similar to `path/*` since it does not follow
|
315
|
+
# nested directories.
|
316
|
+
#
|
317
|
+
# //= link_directory "./fonts"
|
318
|
+
#
|
319
|
+
# Use caution when linking against JS or CSS assets. Include an explicit
|
320
|
+
# extension or content type in these cases.
|
321
|
+
#
|
322
|
+
# //= link_directory "./scripts" .js
|
323
|
+
#
|
324
|
+
def process_link_directory_directive(path = ".", accept = nil)
|
325
|
+
path = expand_relative_dirname(:link_directory, path)
|
326
|
+
accept = expand_accept_shorthand(accept)
|
327
|
+
link_paths(*@environment.stat_directory_with_dependencies(path), accept)
|
328
|
+
end
|
329
|
+
|
330
|
+
# `link_tree` links all the nested files in a directory.
|
331
|
+
# Its glob equivalent is `path/**/*`.
|
332
|
+
#
|
333
|
+
# //= link_tree "./images"
|
334
|
+
#
|
335
|
+
# Use caution when linking against JS or CSS assets. Include an explicit
|
336
|
+
# extension or content type in these cases.
|
337
|
+
#
|
338
|
+
# //= link_tree "./styles" .css
|
339
|
+
#
|
340
|
+
def process_link_tree_directive(path = ".", accept = nil)
|
341
|
+
path = expand_relative_dirname(:link_tree, path)
|
342
|
+
accept = expand_accept_shorthand(accept)
|
343
|
+
link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
|
344
|
+
end
|
345
|
+
|
346
|
+
private
|
347
|
+
def expand_accept_shorthand(accept)
|
348
|
+
if accept.nil?
|
349
|
+
nil
|
350
|
+
elsif accept.include?("/")
|
351
|
+
accept
|
352
|
+
elsif accept.start_with?(".")
|
353
|
+
@environment.mime_exts[accept]
|
354
|
+
else
|
355
|
+
@environment.mime_exts[".#{accept}"]
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def require_paths(paths, deps)
|
360
|
+
resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri|
|
361
|
+
@required << uri
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def link_paths(paths, deps, accept)
|
366
|
+
resolve_paths(paths, deps, accept: accept) do |uri|
|
367
|
+
@to_link << to_load(uri)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def resolve_paths(paths, deps, **kargs)
|
372
|
+
@dependencies.merge(deps)
|
373
|
+
paths.each do |subpath, stat|
|
374
|
+
next if subpath == @filename || stat.directory?
|
375
|
+
uri, deps = @environment.resolve(subpath, **kargs)
|
376
|
+
@dependencies.merge(deps)
|
377
|
+
yield uri if uri
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def expand_relative_dirname(directive, path)
|
382
|
+
if @environment.relative_path?(path)
|
383
|
+
path = File.expand_path(path, @dirname)
|
384
|
+
stat = @environment.stat(path)
|
385
|
+
|
386
|
+
if stat && stat.directory?
|
387
|
+
path
|
388
|
+
else
|
389
|
+
raise ArgumentError, "#{directive} argument must be a directory"
|
390
|
+
end
|
391
|
+
else
|
392
|
+
# The path must be relative and start with a `./`.
|
393
|
+
raise ArgumentError, "#{directive} argument must be a relative path"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def to_load(uri)
|
398
|
+
@to_load << uri
|
399
|
+
uri
|
400
|
+
end
|
401
|
+
|
402
|
+
def resolve(path, **kargs)
|
403
|
+
# Prevent absolute paths in directives
|
404
|
+
if @environment.absolute_path?(path)
|
405
|
+
raise FileOutsidePaths, "can't require absolute file: #{path}"
|
406
|
+
end
|
407
|
+
|
408
|
+
kargs[:base_path] = @dirname
|
409
|
+
uri, deps = @environment.resolve!(path, **kargs)
|
410
|
+
@dependencies.merge(deps)
|
411
|
+
uri
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|