sprockets 2.6.0 → 4.2.2

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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +118 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +541 -289
  5. data/bin/sprockets +20 -7
  6. data/lib/rake/sprocketstask.rb +34 -17
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +158 -210
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +8 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  12. data/lib/sprockets/autoload/eco.rb +8 -0
  13. data/lib/sprockets/autoload/ejs.rb +8 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +8 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +8 -0
  18. data/lib/sprockets/autoload/yui.rb +8 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +16 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +89 -378
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache/file_store.rb +190 -14
  26. data/lib/sprockets/cache/memory_store.rb +84 -0
  27. data/lib/sprockets/cache/null_store.rb +54 -0
  28. data/lib/sprockets/cache.rb +271 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +166 -150
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +197 -0
  37. data/lib/sprockets/directive_processor.rb +241 -215
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +261 -0
  41. data/lib/sprockets/environment.rb +23 -64
  42. data/lib/sprockets/erb_processor.rb +43 -0
  43. data/lib/sprockets/errors.rb +5 -13
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +36 -19
  53. data/lib/sprockets/loader.rb +347 -0
  54. data/lib/sprockets/manifest.rb +228 -112
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +78 -31
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +43 -19
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +146 -164
  64. data/lib/sprockets/processor_utils.rb +170 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +20 -15
  67. data/lib/sprockets/sass_compressor.rb +55 -10
  68. data/lib/sprockets/sass_functions.rb +3 -70
  69. data/lib/sprockets/sass_importer.rb +3 -29
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +159 -91
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +194 -0
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +193 -52
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. data/lib/sprockets.rb +217 -75
  86. metadata +272 -117
  87. data/lib/sprockets/asset_attributes.rb +0 -131
  88. data/lib/sprockets/bundled_asset.rb +0 -80
  89. data/lib/sprockets/caching.rb +0 -96
  90. data/lib/sprockets/charset_normalizer.rb +0 -41
  91. data/lib/sprockets/eco_template.rb +0 -38
  92. data/lib/sprockets/ejs_template.rb +0 -37
  93. data/lib/sprockets/engines.rb +0 -74
  94. data/lib/sprockets/index.rb +0 -99
  95. data/lib/sprockets/processed_asset.rb +0 -152
  96. data/lib/sprockets/processor.rb +0 -32
  97. data/lib/sprockets/safety_colons.rb +0 -28
  98. data/lib/sprockets/sass_template.rb +0 -60
  99. data/lib/sprockets/scss_template.rb +0 -13
  100. data/lib/sprockets/static_asset.rb +0 -58
@@ -1,7 +1,6 @@
1
- require 'pathname'
1
+ # frozen_string_literal: true
2
+ require 'set'
2
3
  require 'shellwords'
3
- require 'tilt'
4
- require 'yaml'
5
4
 
6
5
  module Sprockets
7
6
  # The `DirectiveProcessor` is responsible for parsing and evaluating
@@ -20,10 +19,9 @@ module Sprockets
20
19
  # *= require "baz"
21
20
  # */
22
21
  #
23
- # The Processor is implemented as a `Tilt::Template` and is loosely
24
- # coupled to Sprockets. This makes it possible to disable or modify
25
- # the processor to do whatever you'd like. You could add your own
26
- # custom directives or invent your own directive syntax.
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.
27
25
  #
28
26
  # `Environment#processors` includes `DirectiveProcessor` by default.
29
27
  #
@@ -36,25 +34,7 @@ module Sprockets
36
34
  #
37
35
  # env.register_processor('text/css', MyProcessor)
38
36
  #
39
- class DirectiveProcessor < Tilt::Template
40
- # Directives will only be picked up if they are in the header
41
- # of the source file. C style (/* */), JavaScript (//), and
42
- # Ruby (#) comments are supported.
43
- #
44
- # Directives in comments after the first non-whitespace line
45
- # of code will not be processed.
46
- #
47
- HEADER_PATTERN = /
48
- \A (
49
- (?m:\s*) (
50
- (\/\* (?m:.*?) \*\/) |
51
- (\#\#\# (?m:.*?) \#\#\#) |
52
- (\/\/ .* \n?)+ |
53
- (\# .* \n?)+
54
- )
55
- )+
56
- /x
57
-
37
+ class DirectiveProcessor
58
38
  # Directives are denoted by a `=` followed by the name, then
59
39
  # argument list.
60
40
  #
@@ -68,74 +48,118 @@ module Sprockets
68
48
  ^ \W* = \s* (\w+.*?) (\*\/)? $
69
49
  /x
70
50
 
71
- attr_reader :pathname
72
- attr_reader :header, :body
51
+ def self.instance
52
+ # Default to C comment styles
53
+ @instance ||= new(comments: ["//", ["/*", "*/"]])
54
+ end
73
55
 
74
- def prepare
75
- @pathname = Pathname.new(file)
56
+ def self.call(input)
57
+ instance.call(input)
58
+ end
76
59
 
77
- @header = data[HEADER_PATTERN, 0] || ""
78
- @body = $' || data
79
- # Ensure body ends in a new line
80
- @body += "\n" if @body != "" && @body !~ /\n\Z/m
60
+ def initialize(comments: [])
61
+ @header_pattern = compile_header_pattern(Array(comments))
62
+ end
81
63
 
82
- @included_pathnames = []
83
- @compat = false
64
+ def call(input)
65
+ dup._call(input)
84
66
  end
85
67
 
86
- # Implemented for Tilt#render.
87
- #
88
- # `context` is a `Context` instance with methods that allow you to
89
- # access the environment and append to the bundle. See `Context`
90
- # for the complete API.
91
- def evaluate(context, locals, &block)
92
- @context = context
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
93
117
 
94
- @result = ""
95
- @has_written_body = false
118
+ def process_source(source)
119
+ header = source[@header_pattern, 0] || ""
120
+ body = $' || source
96
121
 
97
- process_directives
98
- process_source
122
+ header, directives = extract_directives(header)
99
123
 
100
- @result
101
- end
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"
102
130
 
103
- # Returns the header String with any directives stripped.
104
- def processed_header
105
- lineno = 0
106
- @processed_header ||= header.lines.map { |line|
107
- lineno += 1
108
- # Replace directive line with a clean break
109
- directives.assoc(lineno) ? "\n" : line
110
- }.join.chomp
111
- end
131
+ return data, directives
132
+ end
112
133
 
113
- # Returns the source String with any directives stripped.
114
- def processed_source
115
- @processed_source ||= processed_header + body
116
- end
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 = []
117
144
 
118
- # Returns an Array of directive structures. Each structure
119
- # is an Array with the line number as the first element, the
120
- # directive name as the second element, followed by any
121
- # arguments.
122
- #
123
- # [[1, "require", "foo"], [2, "require", "bar"]]
124
- #
125
- def directives
126
- @directives ||= header.lines.each_with_index.map { |line, index|
127
- if directive = line[DIRECTIVE_PATTERN, 1]
128
- name, *args = Shellwords.shellwords(directive)
129
- if respond_to?("process_#{name}_directive", true)
130
- [index + 1, name, *args]
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
131
153
  end
154
+ processed_header << line
132
155
  end
133
- }.compact
134
- end
135
156
 
136
- protected
137
- attr_reader :included_pathnames
138
- attr_reader :context
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
139
163
 
140
164
  # Gathers comment directives in the source and processes them.
141
165
  # Any directive method matching `process_*_directive` will
@@ -147,8 +171,8 @@ module Sprockets
147
171
  # `process_require_glob_directive`.
148
172
  #
149
173
  # class DirectiveProcessor < Sprockets::DirectiveProcessor
150
- # def process_require_glob_directive
151
- # Dir["#{pathname.dirname}/#{glob}"].sort.each do |filename|
174
+ # def process_require_glob_directive(glob)
175
+ # Dir["#{dirname}/#{glob}"].sort.each do |filename|
152
176
  # require(filename)
153
177
  # end
154
178
  # end
@@ -159,35 +183,20 @@ module Sprockets
159
183
  # env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
160
184
  # env.register_processor('text/css', DirectiveProcessor)
161
185
  #
162
- def process_directives
186
+ def process_directives(directives)
163
187
  directives.each do |line_number, name, *args|
164
- context.__LINE__ = line_number
165
- send("process_#{name}_directive", *args)
166
- context.__LINE__ = nil
167
- end
168
- end
169
-
170
- def process_source
171
- unless @has_written_body || processed_header.empty?
172
- @result << processed_header << "\n"
173
- end
174
-
175
- included_pathnames.each do |pathname|
176
- @result << context.evaluate(pathname)
177
- end
178
-
179
- unless @has_written_body
180
- @result << body
181
- end
182
-
183
- if compat? && constants.any?
184
- @result.gsub!(/<%=(.*?)%>/) { constants[$1.strip] }
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
185
194
  end
186
195
  end
187
196
 
188
197
  # The `require` directive functions similar to Ruby's own `require`.
189
198
  # It provides a way to declare a dependency on a file in your path
190
- # and ensures its only loaded once before the source file.
199
+ # and ensures it's only loaded once before the source file.
191
200
  #
192
201
  # `require` works with files in the environment path:
193
202
  #
@@ -204,22 +213,13 @@ module Sprockets
204
213
  # //= require "./bar"
205
214
  #
206
215
  def process_require_directive(path)
207
- if @compat
208
- if path =~ /<([^>]+)>/
209
- path = $1
210
- else
211
- path = "./#{path}" unless relative?(path)
212
- end
213
- end
214
-
215
- context.require_asset(path)
216
+ @required << resolve(path, accept: @content_type, pipeline: :self)
216
217
  end
217
218
 
218
- # `require_self` causes the body of the current file to be
219
- # inserted before any subsequent `require` or `include`
220
- # directives. Useful in CSS files, where it's common for the
221
- # index file to contain global styles that need to be defined
222
- # before other dependencies are loaded.
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
223
  #
224
224
  # /*= require "reset"
225
225
  # *= require_self
@@ -227,26 +227,10 @@ module Sprockets
227
227
  # */
228
228
  #
229
229
  def process_require_self_directive
230
- if @has_written_body
230
+ if @required.include?(@uri)
231
231
  raise ArgumentError, "require_self can only be called once per source file"
232
232
  end
233
-
234
- context.require_asset(pathname)
235
- process_source
236
- included_pathnames.clear
237
- @has_written_body = true
238
- end
239
-
240
- # The `include` directive works similar to `require` but
241
- # inserts the contents of the dependency even if it already
242
- # has been required.
243
- #
244
- # //= include "header"
245
- #
246
- def process_include_directive(path)
247
- pathname = context.resolve(path)
248
- context.depend_on_asset(pathname)
249
- included_pathnames << pathname
233
+ @required << @uri
250
234
  end
251
235
 
252
236
  # `require_directory` requires all the files inside a single
@@ -256,27 +240,8 @@ module Sprockets
256
240
  # //= require_directory "./javascripts"
257
241
  #
258
242
  def process_require_directory_directive(path = ".")
259
- if relative?(path)
260
- root = pathname.dirname.join(path).expand_path
261
-
262
- unless (stats = stat(root)) && stats.directory?
263
- raise ArgumentError, "require_directory argument must be a directory"
264
- end
265
-
266
- context.depend_on(root)
267
-
268
- entries(root).each do |pathname|
269
- pathname = root.join(pathname)
270
- if pathname.to_s == self.file
271
- next
272
- elsif context.asset_requirable?(pathname)
273
- context.require_asset(pathname)
274
- end
275
- end
276
- else
277
- # The path must be relative and start with a `./`.
278
- raise ArgumentError, "require_directory argument must be a relative path"
279
- end
243
+ path = expand_relative_dirname(:require_directory, path)
244
+ require_paths(*@environment.stat_directory_with_dependencies(path))
280
245
  end
281
246
 
282
247
  # `require_tree` requires all the nested files in a directory.
@@ -285,28 +250,8 @@ module Sprockets
285
250
  # //= require_tree "./public"
286
251
  #
287
252
  def process_require_tree_directive(path = ".")
288
- if relative?(path)
289
- root = pathname.dirname.join(path).expand_path
290
-
291
- unless (stats = stat(root)) && stats.directory?
292
- raise ArgumentError, "require_tree argument must be a directory"
293
- end
294
-
295
- context.depend_on(root)
296
-
297
- each_entry(root) do |pathname|
298
- if pathname.to_s == self.file
299
- next
300
- elsif stat(pathname).directory?
301
- context.depend_on(pathname)
302
- elsif context.asset_requirable?(pathname)
303
- context.require_asset(pathname)
304
- end
305
- end
306
- else
307
- # The path must be relative and start with a `./`.
308
- raise ArgumentError, "require_tree argument must be a relative path"
309
- end
253
+ path = expand_relative_dirname(:require_tree, path)
254
+ require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
310
255
  end
311
256
 
312
257
  # Allows you to state a dependency on a file without
@@ -322,22 +267,40 @@ module Sprockets
322
267
  # //= depend_on "foo.png"
323
268
  #
324
269
  def process_depend_on_directive(path)
325
- context.depend_on(path)
270
+ resolve(path)
326
271
  end
327
272
 
328
273
  # Allows you to state a dependency on an asset without including
329
274
  # it.
330
275
  #
331
276
  # This is used for caching purposes. Any changes that would
332
- # invalid the asset dependency will invalidate the cache our the
333
- # source file.
277
+ # invalidate the asset dependency will invalidate the cache of
278
+ # the source file.
334
279
  #
335
280
  # Unlike `depend_on`, the path must be a requirable asset.
336
281
  #
337
282
  # //= depend_on_asset "bar.js"
338
283
  #
339
284
  def process_depend_on_asset_directive(path)
340
- context.depend_on_asset(path)
285
+ to_load(resolve(path))
286
+ end
287
+
288
+ # Allows you to state a dependency on a relative directory
289
+ # without including it.
290
+ #
291
+ # This is used for caching purposes. Any changes made to
292
+ # the dependency directory will invalidate the cache of the
293
+ # source file.
294
+ #
295
+ # This is useful if you are using ERB and File.read to pull
296
+ # in contents from multiple files in a directory.
297
+ #
298
+ # //= depend_on_directory ./data
299
+ #
300
+ def process_depend_on_directory_directive(path = ".", accept = nil)
301
+ path = expand_relative_dirname(:depend_on_directory, path)
302
+ accept = expand_accept_shorthand(accept)
303
+ resolve_paths(*@environment.stat_directory_with_dependencies(path), accept: accept)
341
304
  end
342
305
 
343
306
  # Allows dependency to be excluded from the asset bundle.
@@ -349,58 +312,121 @@ module Sprockets
349
312
  # //= stub "jquery"
350
313
  #
351
314
  def process_stub_directive(path)
352
- context.stub_asset(path)
315
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
353
316
  end
354
317
 
355
- # Enable Sprockets 1.x compat mode.
318
+ # Declares a linked dependency on the target asset.
356
319
  #
357
- # Makes it possible to use the same JavaScript source
358
- # file in both Sprockets 1 and 2.
320
+ # The `path` must be a valid asset and should not already be part of the
321
+ # bundle. Any linked assets will automatically be compiled along with the
322
+ # current.
359
323
  #
360
- # //= compat
324
+ # /*= link "logo.png" */
361
325
  #
362
- def process_compat_directive
363
- @compat = true
326
+ def process_link_directive(path)
327
+ uri = to_load(resolve(path))
328
+ @to_link << uri
364
329
  end
365
330
 
366
- # Checks if Sprockets 1.x compat mode enabled
367
- def compat?
368
- @compat
331
+ # `link_directory` links all the files inside a single
332
+ # directory. It's similar to `path/*` since it does not follow
333
+ # nested directories.
334
+ #
335
+ # //= link_directory "./fonts"
336
+ #
337
+ # Use caution when linking against JS or CSS assets. Include an explicit
338
+ # extension or content type in these cases.
339
+ #
340
+ # //= link_directory "./scripts" .js
341
+ #
342
+ def process_link_directory_directive(path = ".", accept = nil)
343
+ path = expand_relative_dirname(:link_directory, path)
344
+ accept = expand_accept_shorthand(accept)
345
+ link_paths(*@environment.stat_directory_with_dependencies(path), accept)
369
346
  end
370
347
 
371
- # Sprockets 1.x allowed for constant interpolation if a
372
- # constants.yml was present. This is only available if
373
- # compat mode is on.
374
- def constants
375
- if compat?
376
- pathname = Pathname.new(context.root_path).join("constants.yml")
377
- stat(pathname) ? YAML.load_file(pathname) : {}
348
+ # `link_tree` links all the nested files in a directory.
349
+ # Its glob equivalent is `path/**/*`.
350
+ #
351
+ # //= link_tree "./images"
352
+ #
353
+ # Use caution when linking against JS or CSS assets. Include an explicit
354
+ # extension or content type in these cases.
355
+ #
356
+ # //= link_tree "./styles" .css
357
+ #
358
+ def process_link_tree_directive(path = ".", accept = nil)
359
+ path = expand_relative_dirname(:link_tree, path)
360
+ accept = expand_accept_shorthand(accept)
361
+ link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
362
+ end
363
+
364
+ private
365
+ def expand_accept_shorthand(accept)
366
+ if accept.nil?
367
+ nil
368
+ elsif accept.include?("/")
369
+ accept
370
+ elsif accept.start_with?(".")
371
+ @environment.mime_exts[accept]
378
372
  else
379
- {}
373
+ @environment.mime_exts[".#{accept}"]
380
374
  end
381
375
  end
382
376
 
383
- # `provide` is stubbed out for Sprockets 1.x compat.
384
- # Mutating the path when an asset is being built is
385
- # not permitted.
386
- def process_provide_directive(path)
377
+ def require_paths(paths, deps)
378
+ resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri|
379
+ @required << uri
380
+ end
387
381
  end
388
382
 
389
- private
390
- def relative?(path)
391
- path =~ /^\.($|\.?\/)/
383
+ def link_paths(paths, deps, accept)
384
+ resolve_paths(paths, deps, accept: accept) do |uri|
385
+ @to_link << to_load(uri)
386
+ end
387
+ end
388
+
389
+ def resolve_paths(paths, deps, **kargs)
390
+ @dependencies.merge(deps)
391
+ paths.each do |subpath, stat|
392
+ next if subpath == @filename || stat.directory?
393
+ uri, deps = @environment.resolve(subpath, **kargs)
394
+ @dependencies.merge(deps)
395
+ yield uri if uri && block_given?
396
+ end
392
397
  end
393
398
 
394
- def stat(path)
395
- context.environment.stat(path)
399
+ def expand_relative_dirname(directive, path)
400
+ if @environment.relative_path?(path)
401
+ path = File.expand_path(path, @dirname)
402
+ stat = @environment.stat(path)
403
+
404
+ if stat && stat.directory?
405
+ path
406
+ else
407
+ raise ArgumentError, "#{directive} argument must be a directory"
408
+ end
409
+ else
410
+ # The path must be relative and start with a `./`.
411
+ raise ArgumentError, "#{directive} argument must be a relative path"
412
+ end
396
413
  end
397
414
 
398
- def entries(path)
399
- context.environment.entries(path)
415
+ def to_load(uri)
416
+ @to_load << uri
417
+ uri
400
418
  end
401
419
 
402
- def each_entry(root, &block)
403
- context.environment.each_entry(root, &block)
420
+ def resolve(path, **kargs)
421
+ # Prevent absolute paths in directives
422
+ if @environment.absolute_path?(path)
423
+ raise FileOutsidePaths, "can't require absolute file: #{path}"
424
+ end
425
+
426
+ kargs[:base_path] = @dirname
427
+ uri, deps = @environment.resolve!(path, **kargs)
428
+ @dependencies.merge(deps)
429
+ uri
404
430
  end
405
431
  end
406
432
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+
4
+ module Sprockets
5
+ # Processor engine class for the Eco compiler. Depends on the `eco` gem.
6
+ #
7
+ # For more information see:
8
+ #
9
+ # https://github.com/sstephenson/ruby-eco
10
+ # https://github.com/sstephenson/eco
11
+ #
12
+ module EcoProcessor
13
+ VERSION = '1'
14
+
15
+ def self.cache_key
16
+ @cache_key ||= "#{name}:#{Autoload::Eco::Source::VERSION}:#{VERSION}".freeze
17
+ end
18
+
19
+ # Compile template data with Eco compiler.
20
+ #
21
+ # Returns a JS function definition String. The result should be
22
+ # assigned to a JS variable.
23
+ #
24
+ # # => "function(...) {...}"
25
+ #
26
+ def self.call(input)
27
+ data = input[:data]
28
+ input[:cache].fetch([cache_key, data]) do
29
+ Autoload::Eco.compile(data)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+
4
+ module Sprockets
5
+ # Processor engine class for the EJS compiler. Depends on the `ejs` gem.
6
+ #
7
+ # For more information see:
8
+ #
9
+ # https://github.com/sstephenson/ruby-ejs
10
+ #
11
+ module EjsProcessor
12
+ VERSION = '1'
13
+
14
+ def self.cache_key
15
+ @cache_key ||= "#{name}:#{VERSION}".freeze
16
+ end
17
+
18
+ # Compile template data with EJS compiler.
19
+ #
20
+ # Returns a JS function definition String. The result should be
21
+ # assigned to a JS variable.
22
+ #
23
+ # # => "function(obj){...}"
24
+ #
25
+ def self.call(input)
26
+ data = input[:data]
27
+ input[:cache].fetch([cache_key, data]) do
28
+ Autoload::EJS.compile(data)
29
+ end
30
+ end
31
+ end
32
+ end