sprockets 2.2.3 → 4.0.0
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 +5 -5
- data/CHANGELOG.md +68 -0
- data/README.md +482 -255
- data/bin/sprockets +20 -7
- data/lib/rake/sprocketstask.rb +28 -15
- data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
- data/lib/sprockets/asset.rb +142 -207
- 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/autoload.rb +16 -0
- data/lib/sprockets/babel_processor.rb +66 -0
- data/lib/sprockets/base.rb +89 -249
- data/lib/sprockets/bower.rb +61 -0
- data/lib/sprockets/bundle.rb +105 -0
- data/lib/sprockets/cache/file_store.rb +190 -14
- data/lib/sprockets/cache/memory_store.rb +75 -0
- data/lib/sprockets/cache/null_store.rb +54 -0
- data/lib/sprockets/cache.rb +271 -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 +204 -135
- data/lib/sprockets/dependencies.rb +74 -0
- data/lib/sprockets/digest_utils.rb +200 -0
- data/lib/sprockets/directive_processor.rb +224 -216
- 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 +23 -68
- data/lib/sprockets/erb_processor.rb +37 -0
- data/lib/sprockets/errors.rb +6 -13
- data/lib/sprockets/exporters/base.rb +72 -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 +36 -19
- data/lib/sprockets/loader.rb +343 -0
- data/lib/sprockets/manifest.rb +231 -96
- data/lib/sprockets/manifest_utils.rb +48 -0
- data/lib/sprockets/mime.rb +80 -32
- 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 +140 -192
- 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 +138 -90
- 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/gzip.rb +99 -0
- data/lib/sprockets/utils.rb +186 -53
- data/lib/sprockets/version.rb +2 -1
- data/lib/sprockets/yui_compressor.rb +56 -0
- data/lib/sprockets.rb +217 -52
- metadata +250 -59
- data/LICENSE +0 -21
- data/lib/sprockets/asset_attributes.rb +0 -126
- data/lib/sprockets/bundled_asset.rb +0 -79
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/eco_template.rb +0 -38
- data/lib/sprockets/ejs_template.rb +0 -37
- data/lib/sprockets/engines.rb +0 -74
- data/lib/sprockets/index.rb +0 -99
- 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/static_asset.rb +0 -57
- data/lib/sprockets/trail.rb +0 -90
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'base64'
|
3
|
+
require 'stringio'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
module Sprockets
|
7
|
+
# Internal: HTTP transport encoding and charset detecting related functions.
|
8
|
+
# Mixed into Environment.
|
9
|
+
module EncodingUtils
|
10
|
+
extend self
|
11
|
+
|
12
|
+
## Binary encodings ##
|
13
|
+
|
14
|
+
# Public: Use deflate to compress data.
|
15
|
+
#
|
16
|
+
# str - String data
|
17
|
+
#
|
18
|
+
# Returns a compressed String
|
19
|
+
def deflate(str)
|
20
|
+
deflater = Zlib::Deflate.new(
|
21
|
+
Zlib::BEST_COMPRESSION,
|
22
|
+
-Zlib::MAX_WBITS,
|
23
|
+
Zlib::MAX_MEM_LEVEL,
|
24
|
+
Zlib::DEFAULT_STRATEGY
|
25
|
+
)
|
26
|
+
deflater << str
|
27
|
+
deflater.finish
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Unmarshal optionally deflated data.
|
31
|
+
#
|
32
|
+
# Checks leading marshal header to see if the bytes are uncompressed
|
33
|
+
# otherwise inflate the data an unmarshal.
|
34
|
+
#
|
35
|
+
# str - Marshaled String
|
36
|
+
# window_bits - Integer deflate window size. See ZLib::Inflate.new()
|
37
|
+
#
|
38
|
+
# Returns unmarshaled Object or raises an Exception.
|
39
|
+
def unmarshaled_deflated(str, window_bits = -Zlib::MAX_WBITS)
|
40
|
+
major, minor = str[0], str[1]
|
41
|
+
if major && major.ord == Marshal::MAJOR_VERSION &&
|
42
|
+
minor && minor.ord <= Marshal::MINOR_VERSION
|
43
|
+
marshaled = str
|
44
|
+
else
|
45
|
+
begin
|
46
|
+
marshaled = Zlib::Inflate.new(window_bits).inflate(str)
|
47
|
+
rescue Zlib::DataError
|
48
|
+
marshaled = str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
Marshal.load(marshaled)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Use gzip to compress data.
|
55
|
+
#
|
56
|
+
# str - String data
|
57
|
+
#
|
58
|
+
# Returns a compressed String
|
59
|
+
def gzip(str)
|
60
|
+
io = StringIO.new
|
61
|
+
gz = Zlib::GzipWriter.new(io, Zlib::BEST_COMPRESSION)
|
62
|
+
gz.mtime = 1
|
63
|
+
gz << str
|
64
|
+
gz.finish
|
65
|
+
io.string
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Use base64 to encode data.
|
69
|
+
#
|
70
|
+
# str - String data
|
71
|
+
#
|
72
|
+
# Returns a encoded String
|
73
|
+
def base64(str)
|
74
|
+
Base64.strict_encode64(str)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
## Charset encodings ##
|
79
|
+
|
80
|
+
# Internal: Shorthand aliases for detecter functions.
|
81
|
+
CHARSET_DETECT = {}
|
82
|
+
|
83
|
+
# Internal: Mapping unicode encodings to byte order markers.
|
84
|
+
BOM = {
|
85
|
+
Encoding::UTF_32LE => [0xFF, 0xFE, 0x00, 0x00],
|
86
|
+
Encoding::UTF_32BE => [0x00, 0x00, 0xFE, 0xFF],
|
87
|
+
Encoding::UTF_8 => [0xEF, 0xBB, 0xBF],
|
88
|
+
Encoding::UTF_16LE => [0xFF, 0xFE],
|
89
|
+
Encoding::UTF_16BE => [0xFE, 0xFF]
|
90
|
+
}
|
91
|
+
|
92
|
+
# Public: Basic string detecter.
|
93
|
+
#
|
94
|
+
# Attempts to parse any Unicode BOM otherwise falls back to the
|
95
|
+
# environment's external encoding.
|
96
|
+
#
|
97
|
+
# str - ASCII-8BIT encoded String
|
98
|
+
#
|
99
|
+
# Returns encoded String.
|
100
|
+
def detect(str)
|
101
|
+
str = detect_unicode_bom(str)
|
102
|
+
|
103
|
+
# Attempt Charlock detection
|
104
|
+
if str.encoding == Encoding::BINARY
|
105
|
+
charlock_detect(str)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fallback to environment's external encoding
|
109
|
+
if str.encoding == Encoding::BINARY
|
110
|
+
str.force_encoding(Encoding.default_external)
|
111
|
+
end
|
112
|
+
|
113
|
+
str
|
114
|
+
end
|
115
|
+
CHARSET_DETECT[:default] = method(:detect)
|
116
|
+
|
117
|
+
# Internal: Use Charlock Holmes to detect encoding.
|
118
|
+
#
|
119
|
+
# To enable this code path, require 'charlock_holmes'
|
120
|
+
#
|
121
|
+
# Returns encoded String.
|
122
|
+
def charlock_detect(str)
|
123
|
+
if defined? CharlockHolmes::EncodingDetector
|
124
|
+
if detected = CharlockHolmes::EncodingDetector.detect(str)
|
125
|
+
str.force_encoding(detected[:encoding]) if detected[:encoding]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
str
|
130
|
+
end
|
131
|
+
|
132
|
+
# Public: Detect Unicode string.
|
133
|
+
#
|
134
|
+
# Attempts to parse Unicode BOM and falls back to UTF-8.
|
135
|
+
#
|
136
|
+
# str - ASCII-8BIT encoded String
|
137
|
+
#
|
138
|
+
# Returns encoded String.
|
139
|
+
def detect_unicode(str)
|
140
|
+
str = detect_unicode_bom(str)
|
141
|
+
|
142
|
+
# Fallback to UTF-8
|
143
|
+
if str.encoding == Encoding::BINARY
|
144
|
+
str.force_encoding(Encoding::UTF_8)
|
145
|
+
end
|
146
|
+
|
147
|
+
str
|
148
|
+
end
|
149
|
+
CHARSET_DETECT[:unicode] = method(:detect_unicode)
|
150
|
+
|
151
|
+
# Public: Detect and strip BOM from possible unicode string.
|
152
|
+
#
|
153
|
+
# str - ASCII-8BIT encoded String
|
154
|
+
#
|
155
|
+
# Returns UTF 8/16/32 encoded String without BOM or the original String if
|
156
|
+
# no BOM was present.
|
157
|
+
def detect_unicode_bom(str)
|
158
|
+
bom_bytes = str.byteslice(0, 4).bytes.to_a
|
159
|
+
|
160
|
+
BOM.each do |encoding, bytes|
|
161
|
+
if bom_bytes[0, bytes.size] == bytes
|
162
|
+
str = str.dup
|
163
|
+
str.force_encoding(Encoding::BINARY)
|
164
|
+
str.slice!(0, bytes.size)
|
165
|
+
str.force_encoding(encoding)
|
166
|
+
return str
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
return str
|
171
|
+
end
|
172
|
+
|
173
|
+
# Public: Detect and strip @charset from CSS style sheet.
|
174
|
+
#
|
175
|
+
# str - String.
|
176
|
+
#
|
177
|
+
# Returns a encoded String.
|
178
|
+
def detect_css(str)
|
179
|
+
str = detect_unicode_bom(str)
|
180
|
+
|
181
|
+
if name = scan_css_charset(str)
|
182
|
+
encoding = Encoding.find(name)
|
183
|
+
str = str.dup
|
184
|
+
str.force_encoding(encoding)
|
185
|
+
len = "@charset \"#{name}\";".encode(encoding).size
|
186
|
+
str.slice!(0, len)
|
187
|
+
str
|
188
|
+
end
|
189
|
+
|
190
|
+
# Fallback to UTF-8
|
191
|
+
if str.encoding == Encoding::BINARY
|
192
|
+
str.force_encoding(Encoding::UTF_8)
|
193
|
+
end
|
194
|
+
|
195
|
+
str
|
196
|
+
end
|
197
|
+
CHARSET_DETECT[:css] = method(:detect_css)
|
198
|
+
|
199
|
+
# Internal: @charset bytes
|
200
|
+
CHARSET_START = [0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22]
|
201
|
+
CHARSET_SIZE = CHARSET_START.size
|
202
|
+
|
203
|
+
# Internal: Scan binary CSS string for @charset encoding name.
|
204
|
+
#
|
205
|
+
# str - ASCII-8BIT encoded String
|
206
|
+
#
|
207
|
+
# Returns encoding String name or nil.
|
208
|
+
def scan_css_charset(str)
|
209
|
+
buf = []
|
210
|
+
i = 0
|
211
|
+
|
212
|
+
str.each_byte.each do |byte|
|
213
|
+
# Halt on line breaks
|
214
|
+
break if byte == 0x0A || byte == 0x0D
|
215
|
+
|
216
|
+
# Only ascii bytes
|
217
|
+
next unless 0x0 < byte && byte <= 0xFF
|
218
|
+
|
219
|
+
if i < CHARSET_SIZE
|
220
|
+
elsif i == CHARSET_SIZE
|
221
|
+
if buf == CHARSET_START
|
222
|
+
buf = []
|
223
|
+
else
|
224
|
+
break
|
225
|
+
end
|
226
|
+
elsif byte == 0x22
|
227
|
+
return buf.pack('C*')
|
228
|
+
end
|
229
|
+
|
230
|
+
buf << byte
|
231
|
+
i += 1
|
232
|
+
end
|
233
|
+
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
|
237
|
+
# Public: Detect charset from HTML document.
|
238
|
+
#
|
239
|
+
# Attempts to parse any Unicode BOM otherwise attempt Charlock detection
|
240
|
+
# and finally falls back to the environment's external encoding.
|
241
|
+
#
|
242
|
+
# str - String.
|
243
|
+
#
|
244
|
+
# Returns a encoded String.
|
245
|
+
def detect_html(str)
|
246
|
+
str = detect_unicode_bom(str)
|
247
|
+
|
248
|
+
# Attempt Charlock detection
|
249
|
+
if str.encoding == Encoding::BINARY
|
250
|
+
charlock_detect(str)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Fallback to environment's external encoding
|
254
|
+
if str.encoding == Encoding::BINARY
|
255
|
+
str.force_encoding(Encoding.default_external)
|
256
|
+
end
|
257
|
+
|
258
|
+
str
|
259
|
+
end
|
260
|
+
CHARSET_DETECT[:html] = method(:detect_html)
|
261
|
+
end
|
262
|
+
end
|
@@ -1,91 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'sprockets/base'
|
2
|
-
require 'sprockets/
|
3
|
-
require 'sprockets/
|
4
|
-
require 'sprockets/directive_processor'
|
5
|
-
require 'sprockets/index'
|
6
|
-
require 'sprockets/safety_colons'
|
7
|
-
|
8
|
-
require 'hike'
|
9
|
-
require 'logger'
|
10
|
-
require 'pathname'
|
11
|
-
require 'tilt'
|
3
|
+
require 'sprockets/cache/memory_store'
|
4
|
+
require 'sprockets/cached_environment'
|
12
5
|
|
13
6
|
module Sprockets
|
14
7
|
class Environment < Base
|
15
|
-
# `Environment` should initialized with your application's root
|
8
|
+
# `Environment` should be initialized with your application's root
|
16
9
|
# directory. This should be the same as your Rails or Rack root.
|
17
10
|
#
|
18
11
|
# env = Environment.new(Rails.root)
|
19
12
|
#
|
20
13
|
def initialize(root = ".")
|
21
|
-
|
22
|
-
|
23
|
-
self.
|
24
|
-
self.logger.level = Logger::FATAL
|
25
|
-
|
26
|
-
if respond_to?(:default_external_encoding)
|
27
|
-
self.default_external_encoding = Encoding::UTF_8
|
28
|
-
end
|
29
|
-
|
30
|
-
# Create a safe `Context` subclass to mutate
|
31
|
-
@context_class = Class.new(Context)
|
32
|
-
|
33
|
-
# Set MD5 as the default digest
|
34
|
-
require 'digest/md5'
|
35
|
-
@digest_class = ::Digest::MD5
|
36
|
-
@version = ''
|
37
|
-
|
38
|
-
@mime_types = {}
|
39
|
-
@engines = Sprockets.engines
|
40
|
-
@preprocessors = Hash.new { |h, k| h[k] = [] }
|
41
|
-
@postprocessors = Hash.new { |h, k| h[k] = [] }
|
42
|
-
@bundle_processors = Hash.new { |h, k| h[k] = [] }
|
43
|
-
|
44
|
-
@engines.each do |ext, klass|
|
45
|
-
add_engine_to_trail(ext, klass)
|
46
|
-
end
|
47
|
-
|
48
|
-
register_mime_type 'text/css', '.css'
|
49
|
-
register_mime_type 'application/javascript', '.js'
|
50
|
-
|
51
|
-
register_preprocessor 'text/css', DirectiveProcessor
|
52
|
-
register_preprocessor 'application/javascript', DirectiveProcessor
|
53
|
-
|
54
|
-
register_postprocessor 'application/javascript', SafetyColons
|
55
|
-
register_bundle_processor 'text/css', CharsetNormalizer
|
56
|
-
|
57
|
-
expire_index!
|
58
|
-
|
14
|
+
initialize_configuration(Sprockets)
|
15
|
+
self.root = root
|
16
|
+
self.cache = Cache::MemoryStore.new
|
59
17
|
yield self if block_given?
|
60
18
|
end
|
61
19
|
|
62
20
|
# Returns a cached version of the environment.
|
63
21
|
#
|
64
|
-
# All its file system calls are cached which makes `
|
22
|
+
# All of its file system calls are cached which makes `cached` much
|
65
23
|
# faster. This behavior is ideal in production since the file
|
66
24
|
# system only changes between deploys.
|
67
|
-
def
|
68
|
-
|
25
|
+
def cached
|
26
|
+
CachedEnvironment.new(self)
|
69
27
|
end
|
28
|
+
alias_method :index, :cached
|
70
29
|
|
71
|
-
|
72
|
-
|
73
|
-
|
30
|
+
def find_asset(*args, **options)
|
31
|
+
cached.find_asset(*args, **options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_asset!(*args)
|
35
|
+
cached.find_asset!(*args)
|
36
|
+
end
|
74
37
|
|
75
|
-
|
76
|
-
|
77
|
-
asset
|
78
|
-
elsif asset = index.find_asset(path, options)
|
79
|
-
# Cache is pushed upstream by Index#find_asset
|
80
|
-
asset
|
81
|
-
end
|
38
|
+
def find_all_linked_assets(*args, &block)
|
39
|
+
cached.find_all_linked_assets(*args, &block)
|
82
40
|
end
|
83
41
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
@digest = nil
|
88
|
-
@assets = {}
|
89
|
-
end
|
42
|
+
def load(*args)
|
43
|
+
cached.load(*args)
|
44
|
+
end
|
90
45
|
end
|
91
46
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
class Sprockets::ERBProcessor
|
5
|
+
# Public: Return singleton instance with default options.
|
6
|
+
#
|
7
|
+
# Returns ERBProcessor object.
|
8
|
+
def self.instance
|
9
|
+
@instance ||= new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.call(input)
|
13
|
+
instance.call(input)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(&block)
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(input)
|
21
|
+
match = ERB.version.match(/\Aerb\.rb \[(?<version>[^ ]+) /)
|
22
|
+
if match && match[:version] >= "2.2.0" # Ruby 2.6+
|
23
|
+
engine = ::ERB.new(input[:data], trim_mode: '<>')
|
24
|
+
else
|
25
|
+
engine = ::ERB.new(input[:data], nil, '<>')
|
26
|
+
end
|
27
|
+
engine.filename = input[:filename]
|
28
|
+
|
29
|
+
context = input[:environment].context_class.new(input)
|
30
|
+
klass = (class << context; self; end)
|
31
|
+
klass.const_set(:ENV, context.env_proxy)
|
32
|
+
klass.class_eval(&@block) if @block
|
33
|
+
|
34
|
+
data = engine.result(context.instance_eval('binding'))
|
35
|
+
context.metadata.merge(data: data)
|
36
|
+
end
|
37
|
+
end
|
data/lib/sprockets/errors.rb
CHANGED
@@ -1,19 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# Define some basic Sprockets error classes
|
2
3
|
module Sprockets
|
3
4
|
class Error < StandardError; end
|
4
5
|
class ArgumentError < Error; end
|
5
|
-
class CircularDependencyError < Error; end
|
6
6
|
class ContentTypeMismatch < Error; end
|
7
|
-
class
|
8
|
-
class
|
9
|
-
class
|
10
|
-
class
|
11
|
-
|
12
|
-
module EngineError
|
13
|
-
attr_accessor :sprockets_annotation
|
14
|
-
|
15
|
-
def message
|
16
|
-
[super, sprockets_annotation].compact.join("\n")
|
17
|
-
end
|
18
|
-
end
|
7
|
+
class NotImplementedError < Error; end
|
8
|
+
class NotFound < Error; end
|
9
|
+
class ConversionError < NotFound; end
|
10
|
+
class FileNotFound < NotFound; end
|
11
|
+
class FileOutsidePaths < NotFound; end
|
19
12
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Sprockets
|
2
|
+
module Exporters
|
3
|
+
# Convienence class for all exporters to inherit from
|
4
|
+
#
|
5
|
+
# An exporter is responsible for exporting a Sprockets::Asset
|
6
|
+
# to a file system. For example the Exporters::File class
|
7
|
+
# writes the asset to it's destination. The Exporters::Zlib class
|
8
|
+
# writes a gzip copy of the asset to disk.
|
9
|
+
class Base
|
10
|
+
attr_reader :asset, :environment, :directory, :target
|
11
|
+
|
12
|
+
# Public: Creates new instance
|
13
|
+
#
|
14
|
+
# Initialize will be called with
|
15
|
+
# keyword arguments:
|
16
|
+
#
|
17
|
+
# - asset: An instance of Sprockets::Asset.
|
18
|
+
# - environment: An instance of Sprockets::Environment.
|
19
|
+
# - directory: String representing the target directory to write to.
|
20
|
+
#
|
21
|
+
# These will all be stored as accessible values. In addition a
|
22
|
+
# +target+ will be available which is the target directory and
|
23
|
+
# the asset's digest path combined.
|
24
|
+
def initialize(asset: nil, environment: nil, directory: nil)
|
25
|
+
@asset = asset
|
26
|
+
@environment = environment
|
27
|
+
@directory = directory
|
28
|
+
@target = ::File.join(directory, asset.digest_path)
|
29
|
+
setup
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Callback that is executed after intialization
|
33
|
+
#
|
34
|
+
# Any setup that needs to be done can be performed in the +setup+
|
35
|
+
# method. It will be called immediately after initialization.
|
36
|
+
def setup
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Handles logic for skipping exporter and notifying logger
|
40
|
+
#
|
41
|
+
# The `skip?` will be called before anything will be written.
|
42
|
+
# If `skip?` returns truthy it will not continue. This method
|
43
|
+
# takes a `logger` that responds to +debug+ and +info+. The `skip?`
|
44
|
+
# method is the only place expected to write to a logger, any other
|
45
|
+
# messages may produce jumbled logs.
|
46
|
+
def skip?(logger)
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Contains logic for writing "exporting" asset to disk
|
51
|
+
#
|
52
|
+
# If the exporter is not skipped it then Sprockets will execute it's
|
53
|
+
# `call` method. This method takes no arguments and should only use
|
54
|
+
# elements passed in via initialize or stored in `setup`.
|
55
|
+
def call
|
56
|
+
raise "Must subclass and implement call"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Yields a file that can be written to with the input
|
60
|
+
#
|
61
|
+
# `filename`. Defaults to the `target`. Method
|
62
|
+
# is safe to use in forked or threaded environments.
|
63
|
+
def write(filename = target)
|
64
|
+
FileUtils.mkdir_p File.dirname(filename)
|
65
|
+
PathUtils.atomic_write(filename) do |f|
|
66
|
+
yield f
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'sprockets/exporters/base'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
module Exporters
|
5
|
+
# Writes a an asset file to disk
|
6
|
+
class FileExporter < Exporters::Base
|
7
|
+
def skip?(logger)
|
8
|
+
if ::File.exist?(target)
|
9
|
+
logger.debug "Skipping #{ target }, already exists"
|
10
|
+
true
|
11
|
+
else
|
12
|
+
logger.info "Writing #{ target }"
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
write(target) do |file|
|
19
|
+
file.write(asset.source)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sprockets/exporters/base'
|
2
|
+
require 'sprockets/utils/gzip'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
module Exporters
|
6
|
+
# Generates a `.gz` file using the zlib algorithm built into
|
7
|
+
# Ruby's standard library.
|
8
|
+
class ZlibExporter < Exporters::Base
|
9
|
+
def setup
|
10
|
+
@gzip_target = "#{ target }.gz"
|
11
|
+
@gzip = Sprockets::Utils::Gzip.new(asset, archiver: Utils::Gzip::ZlibArchiver)
|
12
|
+
end
|
13
|
+
|
14
|
+
def skip?(logger)
|
15
|
+
return true if environment.skip_gzip?
|
16
|
+
return true if @gzip.cannot_compress?
|
17
|
+
if ::File.exist?(@gzip_target)
|
18
|
+
logger.debug "Skipping #{ @gzip_target }, already exists"
|
19
|
+
true
|
20
|
+
else
|
21
|
+
logger.info "Writing #{ @gzip_target }"
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
write(@gzip_target) do |file|
|
28
|
+
@gzip.compress(file, target)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sprockets/exporters/zlib_exporter'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
module Exporters
|
5
|
+
# Generates a `.gz` file using the zopfli algorithm from the
|
6
|
+
# Zopfli gem.
|
7
|
+
class ZopfliExporter < ZlibExporter
|
8
|
+
def setup
|
9
|
+
@gzip_target = "#{ target }.gz"
|
10
|
+
@gzip = Sprockets::Utils::Gzip.new(asset, archiver: Utils::Gzip::ZopfliArchiver)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sprockets
|
2
|
+
# `Exporting` is an internal mixin whose public methods are exposed on
|
3
|
+
# the `Environment` and `CachedEnvironment` classes.
|
4
|
+
module Exporting
|
5
|
+
# Exporters are ran on the assets:precompile task
|
6
|
+
def exporters
|
7
|
+
config[:exporters]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Registers a new Exporter `klass` for `mime_type`.
|
11
|
+
#
|
12
|
+
# If your exporter depends on one or more other exporters you can
|
13
|
+
# specify this via the `depend_on` keyword.
|
14
|
+
#
|
15
|
+
# register_exporter '*/*', Sprockets::Exporters::ZlibExporter
|
16
|
+
#
|
17
|
+
# This ensures that `Sprockets::Exporters::File` will always execute before
|
18
|
+
# `Sprockets::Exporters::Zlib`
|
19
|
+
def register_exporter(mime_types, klass = nil)
|
20
|
+
mime_types = Array(mime_types)
|
21
|
+
|
22
|
+
mime_types.each do |mime_type|
|
23
|
+
self.config = hash_reassoc(config, :exporters, mime_type) do |_exporters|
|
24
|
+
_exporters << klass
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Remove Exporting processor `klass` for `mime_type`.
|
30
|
+
#
|
31
|
+
# environment.unregister_exporter '*/*', Sprockets::Exporters::Zlib
|
32
|
+
#
|
33
|
+
# Can be called without a mime type
|
34
|
+
#
|
35
|
+
# environment.unregister_exporter Sprockets::Exporters::Zlib
|
36
|
+
#
|
37
|
+
# Does not remove any exporters that depend on `klass`.
|
38
|
+
def unregister_exporter(mime_types, exporter = nil)
|
39
|
+
unless mime_types.is_a? Array
|
40
|
+
if mime_types.is_a? String
|
41
|
+
mime_types = [mime_types]
|
42
|
+
else # called with no mime type
|
43
|
+
exporter = mime_types
|
44
|
+
mime_types = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
self.config = hash_reassoc(config, :exporters) do |_exporters|
|
49
|
+
_exporters.each do |mime_type, exporters_array|
|
50
|
+
next if mime_types && !mime_types.include?(mime_type)
|
51
|
+
if exporters_array.include? exporter
|
52
|
+
_exporters[mime_type] = exporters_array.dup.delete exporter
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Checks if concurrent exporting is allowed
|
59
|
+
def export_concurrent
|
60
|
+
config[:export_concurrent]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Enable or disable the concurrently exporting files
|
64
|
+
#
|
65
|
+
# Defaults to true.
|
66
|
+
#
|
67
|
+
# environment.export_concurrent = false
|
68
|
+
#
|
69
|
+
def export_concurrent=(export_concurrent)
|
70
|
+
self.config = config.merge(export_concurrent: export_concurrent).freeze
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# Internal: The first processor in the pipeline that reads the file into
|
6
|
+
# memory and passes it along as `input[:data]`.
|
7
|
+
class FileReader
|
8
|
+
def self.call(input)
|
9
|
+
env = input[:environment]
|
10
|
+
data = env.read_file(input[:filename], input[:content_type])
|
11
|
+
dependencies = Set.new(input[:metadata][:dependencies])
|
12
|
+
dependencies += [env.build_file_digest_uri(input[:filename])]
|
13
|
+
{ data: data, dependencies: dependencies }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|