sprockets 3.7.3 → 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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -259
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +527 -320
  5. data/bin/sprockets +11 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +39 -27
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +49 -12
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +25 -3
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +37 -2
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +1 -0
  31. data/lib/sprockets/coffee_script_processor.rb +19 -5
  32. data/lib/sprockets/compressing.rb +43 -3
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +2 -1
  36. data/lib/sprockets/digest_utils.rb +35 -18
  37. data/lib/sprockets/directive_processor.rb +64 -38
  38. data/lib/sprockets/eco_processor.rb +2 -1
  39. data/lib/sprockets/ejs_processor.rb +2 -1
  40. data/lib/sprockets/encoding_utils.rb +2 -2
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +33 -32
  43. data/lib/sprockets/errors.rb +1 -0
  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 +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +91 -69
  54. data/lib/sprockets/manifest.rb +67 -64
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -62
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +88 -8
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -62
  64. data/lib/sprockets/processor_utils.rb +28 -38
  65. data/lib/sprockets/resolve.rb +177 -93
  66. data/lib/sprockets/sass_cache_store.rb +2 -6
  67. data/lib/sprockets/sass_compressor.rb +13 -1
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +31 -10
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +63 -40
  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 +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +21 -11
  78. data/lib/sprockets/unloaded_asset.rb +13 -11
  79. data/lib/sprockets/uri_tar.rb +1 -0
  80. data/lib/sprockets/uri_utils.rb +19 -16
  81. data/lib/sprockets/utils/gzip.rb +46 -14
  82. data/lib/sprockets/utils.rb +64 -90
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +1 -0
  85. data/lib/sprockets.rb +102 -39
  86. metadata +148 -45
  87. data/lib/sprockets/coffee_script_template.rb +0 -17
  88. data/lib/sprockets/deprecation.rb +0 -90
  89. data/lib/sprockets/eco_template.rb +0 -17
  90. data/lib/sprockets/ejs_template.rb +0 -17
  91. data/lib/sprockets/engines.rb +0 -92
  92. data/lib/sprockets/erb_template.rb +0 -11
  93. data/lib/sprockets/legacy.rb +0 -330
  94. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  95. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  96. data/lib/sprockets/sass_template.rb +0 -19
data/bin/sprockets CHANGED
@@ -12,7 +12,7 @@ unless ARGV.delete("--noenv")
12
12
  end
13
13
  end
14
14
 
15
- filenames = []
15
+ paths = []
16
16
  environment = Sprockets::Environment.new(Dir.pwd)
17
17
  manifest = nil
18
18
 
@@ -52,6 +52,10 @@ OptionParser.new do |opts|
52
52
  opts.on("--noenv", "Disables .sprocketsrc file") do
53
53
  end
54
54
 
55
+ opts.on("--cache=DIRECTORY", "Enables the FileStore cache using the specified directory") do |directory|
56
+ environment.cache = Sprockets::Cache::FileStore.new(directory)
57
+ end
58
+
55
59
  opts.on_tail("-h", "--help", "Shows this help message") do
56
60
  opts.show_usage
57
61
  end
@@ -64,8 +68,8 @@ OptionParser.new do |opts|
64
68
  opts.show_usage if ARGV.empty?
65
69
 
66
70
  begin
67
- opts.order(ARGV) do |filename|
68
- filenames << File.expand_path(filename)
71
+ opts.order(ARGV) do |path|
72
+ paths << path
69
73
  end
70
74
  rescue OptionParser::ParseError => e
71
75
  opts.warn e.message
@@ -75,14 +79,14 @@ end
75
79
 
76
80
  if environment.paths.empty?
77
81
  warn "No load paths given"
78
- warn "Usage: sprockets -Ijavascripts/ filename"
82
+ warn "Usage: sprockets -Ijavascripts/ path"
79
83
  exit 1
80
84
  end
81
85
 
82
86
  if manifest
83
- manifest.compile(filenames)
84
- elsif filenames.length == 1
85
- puts environment.find_asset(filenames.first).to_s
87
+ manifest.compile(paths)
88
+ elsif paths.length == 1
89
+ puts environment.find_asset(paths.first).to_s
86
90
  else
87
91
  warn "Only one file can be compiled to stdout at a time"
88
92
  exit 1
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rake'
2
3
  require 'rake/tasklib'
3
4
 
@@ -68,9 +69,12 @@ module Rake
68
69
  #
69
70
  attr_accessor :assets
70
71
 
71
- # Number of old assets to keep.
72
+ # Minimum number of old assets to keep. See Sprockets::Manifest#clean for more information.
72
73
  attr_accessor :keep
73
74
 
75
+ # Assets created within this age will be kept. See Sprockets::Manifest#clean for more information.
76
+ attr_accessor :age
77
+
74
78
  # Logger to use during rake tasks. Defaults to using stderr.
75
79
  #
76
80
  # t.logger = Logger.new($stdout)
@@ -102,6 +106,7 @@ module Rake
102
106
  @logger = Logger.new($stderr)
103
107
  @logger.level = Logger::INFO
104
108
  @keep = 2
109
+ @age = 3600
105
110
 
106
111
  yield self if block_given?
107
112
 
@@ -124,16 +129,16 @@ module Rake
124
129
  end
125
130
  end
126
131
 
127
- task :clobber => ["clobber_#{name}"]
132
+ task clobber: ["clobber_#{name}"]
128
133
 
129
134
  desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
130
135
  task "clean_#{name}" do
131
136
  with_logger do
132
- manifest.clean(keep)
137
+ manifest.clean(keep, age)
133
138
  end
134
139
  end
135
140
 
136
- task :clean => ["clean_#{name}"]
141
+ task clean: ["clean_#{name}"]
137
142
  end
138
143
 
139
144
  private
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/uri_utils'
3
+ require 'sprockets/path_utils'
4
+
5
+ module Sprockets
6
+ # This is a processor designed to add a source map "comment"
7
+ # to the bottom of a css or JS file that is serving a source
8
+ # map. An example of a comment might look like this
9
+ #
10
+ # //# application.js-80af0efcc960fc2ac93eda2f7b12e3db40ab360bf6ea269ceed3bea3678326f9.map
11
+ #
12
+ # As an asset is built it gets source map information added
13
+ # to the `asset.to_hash[:metadata][:map]` key. This contains all the
14
+ # information that is needed to build a source map file.
15
+ #
16
+ # To add this comment we must have an asset we can link to.
17
+ # To do this we ensure that the original asset is loaded, then
18
+ # we use a use a special mime type. For example `application/js-sourcemap+json`
19
+ # for a JS source map.
20
+ #
21
+ # This will trigger a new asset to be loaded and generated by the
22
+ # `SourceMapProcessor` processor.
23
+ #
24
+ # Finally once we have that file, we can generate a link to it
25
+ # with it's full fingerprint. This is done and then
26
+ # added to the original asset as a comment at the bottom.
27
+ #
28
+ class AddSourceMapCommentToAssetProcessor
29
+ def self.call(input)
30
+
31
+ case input[:content_type]
32
+ when "application/javascript"
33
+ comment = "\n//# sourceMappingURL=%s"
34
+ map_type = "application/js-sourcemap+json"
35
+ when "text/css"
36
+ comment = "\n/*# sourceMappingURL=%s */"
37
+ map_type = "application/css-sourcemap+json"
38
+ else
39
+ fail input[:content_type]
40
+ end
41
+
42
+ env = input[:environment]
43
+
44
+ uri, _ = env.resolve!(input[:filename], accept: input[:content_type])
45
+ asset = env.load(uri)
46
+
47
+ uri, _ = env.resolve!(input[:filename], accept: map_type)
48
+ map = env.load(uri)
49
+
50
+ uri, params = URIUtils.parse_asset_uri(input[:uri])
51
+ uri = env.expand_from_root(params[:index_alias]) if params[:index_alias]
52
+ path = PathUtils.relative_path_from(PathUtils.split_subpath(input[:load_path], uri), map.digest_path)
53
+
54
+ asset.metadata.merge(
55
+ data: asset.source + (comment % path) + "\n",
56
+ links: asset.links + [asset.uri, map.uri]
57
+ )
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'fileutils'
2
3
  require 'sprockets/digest_utils'
3
4
 
@@ -5,7 +6,7 @@ module Sprockets
5
6
  class Asset
6
7
  attr_reader :logical_path
7
8
 
8
- # Private: Intialize Asset wrapper from attributes Hash.
9
+ # Private: Initialize Asset wrapper from attributes Hash.
9
10
  #
10
11
  # Asset wrappers should not be initialized directly, only
11
12
  # Environment#find_asset should vend them.
@@ -13,8 +14,7 @@ module Sprockets
13
14
  # attributes - Hash of ivars
14
15
  #
15
16
  # Returns Asset.
16
- def initialize(environment, attributes = {})
17
- @environment = environment
17
+ def initialize(attributes = {})
18
18
  @attributes = attributes
19
19
  @content_type = attributes[:content_type]
20
20
  @filename = attributes[:filename]
@@ -22,7 +22,6 @@ module Sprockets
22
22
  @load_path = attributes[:load_path]
23
23
  @logical_path = attributes[:logical_path]
24
24
  @metadata = attributes[:metadata]
25
- @mtime = attributes[:mtime]
26
25
  @name = attributes[:name]
27
26
  @source = attributes[:source]
28
27
  @uri = attributes[:uri]
@@ -39,7 +38,7 @@ module Sprockets
39
38
  #
40
39
  # The API status of the keys is dependent on the pipeline processors
41
40
  # itself. So some values maybe considered public and others internal.
42
- # See the pipeline proccessor documentation itself.
41
+ # See the pipeline processor documentation itself.
43
42
  #
44
43
  # Returns Hash.
45
44
  attr_reader :metadata
@@ -54,7 +53,7 @@ module Sprockets
54
53
 
55
54
  # Public: Internal URI to lookup asset by.
56
55
  #
57
- # NOT a publically accessible URL.
56
+ # NOT a publicly accessible URL.
58
57
  #
59
58
  # Returns URI.
60
59
  attr_reader :uri
@@ -65,7 +64,18 @@ module Sprockets
65
64
  #
66
65
  # Returns String.
67
66
  def digest_path
68
- logical_path.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
67
+ if DigestUtils.already_digested?(@name)
68
+ logical_path
69
+ else
70
+ logical_path.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
71
+ end
72
+ end
73
+
74
+ # Public: Return load path + logical path with digest spliced in.
75
+ #
76
+ # Returns String.
77
+ def full_digest_path
78
+ File.join(@load_path, digest_path)
69
79
  end
70
80
 
71
81
  # Public: Returns String MIME type of asset. Returns nil if type is unknown.
@@ -80,14 +90,6 @@ module Sprockets
80
90
  metadata[:links] || Set.new
81
91
  end
82
92
 
83
- # Public: Get all internally required assets that were concated into this
84
- # asset.
85
- #
86
- # Returns Array of String asset URIs.
87
- def included
88
- metadata[:included]
89
- end
90
-
91
93
  # Public: Return `String` of concatenated source.
92
94
  #
93
95
  # Returns String.
@@ -120,27 +122,40 @@ module Sprockets
120
122
  end
121
123
  alias_method :bytesize, :length
122
124
 
125
+ # Public: Returns String byte digest of source.
126
+ def digest
127
+ metadata[:digest]
128
+ end
129
+
130
+ # Private: Return the version of the environment where the asset was generated.
131
+ def environment_version
132
+ metadata[:environment_version]
133
+ end
134
+
123
135
  # Public: Returns String hexdigest of source.
124
136
  def hexdigest
125
- DigestUtils.pack_hexdigest(metadata[:digest])
137
+ DigestUtils.pack_hexdigest(digest)
126
138
  end
127
139
 
128
- # Deprecated: Returns String hexdigest of source.
129
- #
130
- # In 4.x this will be changed to return a raw Digest byte String.
131
- alias_method :digest, :hexdigest
140
+ # Public: ETag String of Asset.
141
+ def etag
142
+ version = environment_version
132
143
 
133
- # Pubic: ETag String of Asset.
134
- alias_method :etag, :hexdigest
144
+ if version && version != ""
145
+ DigestUtils.hexdigest(version + digest)
146
+ else
147
+ DigestUtils.pack_hexdigest(digest)
148
+ end
149
+ end
135
150
 
136
151
  # Public: Returns String base64 digest of source.
137
152
  def base64digest
138
- DigestUtils.pack_base64digest(metadata[:digest])
153
+ DigestUtils.pack_base64digest(digest)
139
154
  end
140
155
 
141
156
  # Public: A "named information" URL for subresource integrity.
142
157
  def integrity
143
- DigestUtils.integrity_uri(metadata[:digest])
158
+ DigestUtils.integrity_uri(digest)
144
159
  end
145
160
 
146
161
  # Public: Add enumerator to allow `Asset` instances to be used as Rack
@@ -166,9 +181,6 @@ module Sprockets
166
181
  f.write source
167
182
  end
168
183
 
169
- # Set mtime correctly
170
- File.utime(mtime, mtime, filename)
171
-
172
184
  nil
173
185
  end
174
186
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'babel/transpiler'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ Babel = ::Babel
7
+ end
8
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'closure-compiler'
2
3
 
3
4
  module Sprockets
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'coffee_script'
2
3
 
3
4
  module Sprockets
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'eco'
2
3
 
3
4
  module Sprockets
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'ejs'
2
3
 
3
4
  module Sprockets
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'jsminc'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ JSMinC = ::JSMinC
7
+ end
8
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sass'
2
3
 
3
4
  module Sprockets
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'sassc'
3
+
4
+ module Sprockets
5
+ module Autoload
6
+ SassC = ::SassC
7
+ end
8
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'uglifier'
2
3
 
3
4
  module Sprockets
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'yui/compressor'
2
3
 
3
4
  module Sprockets
@@ -0,0 +1,7 @@
1
+ require 'zopfli'
2
+
3
+ module Sprockets
4
+ module Autoload
5
+ Zopfli = ::Zopfli
6
+ end
7
+ end
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
3
  module Autoload
4
+ autoload :Babel, 'sprockets/autoload/babel'
3
5
  autoload :Closure, 'sprockets/autoload/closure'
4
6
  autoload :CoffeeScript, 'sprockets/autoload/coffee_script'
5
7
  autoload :Eco, 'sprockets/autoload/eco'
6
8
  autoload :EJS, 'sprockets/autoload/ejs'
9
+ autoload :JSMinC, 'sprockets/autoload/jsminc'
7
10
  autoload :Sass, 'sprockets/autoload/sass'
11
+ autoload :SassC, 'sprockets/autoload/sassc'
8
12
  autoload :Uglifier, 'sprockets/autoload/uglifier'
9
13
  autoload :YUI, 'sprockets/autoload/yui'
14
+ autoload :Zopfli, 'sprockets/autoload/zopfli'
10
15
  end
11
16
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/path_utils'
4
+ require 'sprockets/source_map_utils'
5
+ require 'json'
6
+
7
+ module Sprockets
8
+ class BabelProcessor
9
+ VERSION = '1'
10
+
11
+ def self.instance
12
+ @instance ||= new
13
+ end
14
+
15
+ def self.call(input)
16
+ instance.call(input)
17
+ end
18
+
19
+ def self.cache_key
20
+ instance.cache_key
21
+ end
22
+
23
+ attr_reader :cache_key
24
+
25
+ def initialize(options = {})
26
+ @options = options.merge({
27
+ 'blacklist' => (options['blacklist'] || []) + ['useStrict'],
28
+ 'sourceMap' => true
29
+ }).freeze
30
+
31
+ @cache_key = [
32
+ self.class.name,
33
+ Autoload::Babel::Transpiler::VERSION,
34
+ Autoload::Babel::Source::VERSION,
35
+ VERSION,
36
+ @options
37
+ ].freeze
38
+ end
39
+
40
+ def call(input)
41
+ data = input[:data]
42
+
43
+ result = input[:cache].fetch(@cache_key + [input[:filename]] + [data]) do
44
+ opts = {
45
+ 'moduleRoot' => nil,
46
+ 'filename' => input[:filename],
47
+ 'filenameRelative' => PathUtils.split_subpath(input[:load_path], input[:filename]),
48
+ 'sourceFileName' => File.basename(input[:filename]),
49
+ 'sourceMapTarget' => input[:filename]
50
+ }.merge(@options)
51
+
52
+ if opts['moduleIds'] && opts['moduleRoot']
53
+ opts['moduleId'] ||= File.join(opts['moduleRoot'], input[:name])
54
+ elsif opts['moduleIds']
55
+ opts['moduleId'] ||= input[:name]
56
+ end
57
+ Autoload::Babel::Transpiler.transform(data, opts)
58
+ end
59
+
60
+ map = SourceMapUtils.format_source_map(result["map"], input)
61
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
62
+
63
+ { data: result['code'], map: map }
64
+ end
65
+ end
66
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/asset'
2
3
  require 'sprockets/bower'
3
4
  require 'sprockets/cache'
@@ -5,22 +6,36 @@ require 'sprockets/configuration'
5
6
  require 'sprockets/digest_utils'
6
7
  require 'sprockets/errors'
7
8
  require 'sprockets/loader'
8
- require 'sprockets/path_digest_utils'
9
+ require 'sprockets/npm'
9
10
  require 'sprockets/path_dependency_utils'
11
+ require 'sprockets/path_digest_utils'
10
12
  require 'sprockets/path_utils'
11
13
  require 'sprockets/resolve'
12
14
  require 'sprockets/server'
13
- require 'sprockets/loader'
15
+ require 'sprockets/source_map_utils'
14
16
  require 'sprockets/uri_tar'
15
17
 
16
18
  module Sprockets
17
- # `Base` class for `Environment` and `Cached`.
19
+
20
+ class DoubleLinkError < Sprockets::Error
21
+ def initialize(parent_filename:, logical_path:, last_filename:, filename:)
22
+ super <<~MSG
23
+ Multiple files with the same output path cannot be linked (#{logical_path.inspect})
24
+ In #{parent_filename.inspect} these files were linked:
25
+ - #{last_filename}
26
+ - #{filename}
27
+ MSG
28
+ end
29
+ end
30
+
31
+ # `Base` class for `Environment` and `CachedEnvironment`.
18
32
  class Base
19
- include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils
33
+ include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils, SourceMapUtils
20
34
  include Configuration
21
35
  include Server
22
36
  include Resolve, Loader
23
37
  include Bower
38
+ include Npm
24
39
 
25
40
  # Get persistent cache store
26
41
  attr_reader :cache
@@ -34,7 +49,7 @@ module Sprockets
34
49
  @cache = Cache.new(cache, logger)
35
50
  end
36
51
 
37
- # Return an `Cached`. Must be implemented by the subclass.
52
+ # Return an `CachedEnvironment`. Must be implemented by the subclass.
38
53
  def cached
39
54
  raise NotImplementedError
40
55
  end
@@ -60,24 +75,36 @@ module Sprockets
60
75
  end
61
76
 
62
77
  # Find asset by logical path or expanded path.
63
- def find_asset(path, options = {})
64
- uri, _ = resolve(path, options.merge(compat: false))
78
+ def find_asset(*args, **options)
79
+ uri, _ = resolve(*args, **options)
65
80
  if uri
66
81
  load(uri)
67
82
  end
68
83
  end
69
84
 
70
- def find_all_linked_assets(path, options = {})
71
- return to_enum(__method__, path, options) unless block_given?
85
+ def find_all_linked_assets(*args)
86
+ return to_enum(__method__, *args) unless block_given?
72
87
 
73
- asset = find_asset(path, options)
88
+ parent_asset = asset = find_asset(*args)
74
89
  return unless asset
75
90
 
76
91
  yield asset
77
92
  stack = asset.links.to_a
93
+ linked_paths = {}
78
94
 
79
95
  while uri = stack.shift
80
96
  yield asset = load(uri)
97
+
98
+ last_filename = linked_paths[asset.logical_path]
99
+ if last_filename && last_filename != asset.filename
100
+ raise DoubleLinkError.new(
101
+ parent_filename: parent_asset.filename,
102
+ last_filename: last_filename,
103
+ logical_path: asset.logical_path,
104
+ filename: asset.filename
105
+ )
106
+ end
107
+ linked_paths[asset.logical_path] = asset.filename
81
108
  stack = asset.links.to_a + stack
82
109
  end
83
110
 
@@ -88,8 +115,18 @@ module Sprockets
88
115
  #
89
116
  # environment['application.js']
90
117
  #
91
- def [](*args)
92
- find_asset(*args)
118
+ def [](*args, **options)
119
+ find_asset(*args, **options)
120
+ end
121
+
122
+ # Find asset by logical path or expanded path.
123
+ #
124
+ # If the asset is not found an error will be raised.
125
+ def find_asset!(*args)
126
+ uri, _ = resolve!(*args)
127
+ if uri
128
+ load(uri)
129
+ end
93
130
  end
94
131
 
95
132
  # Pretty inspect
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
 
3
4
  module Sprockets
@@ -12,12 +13,12 @@ module Sprockets
12
13
  # load_path - String environment path
13
14
  # logical_path - String path relative to base
14
15
  #
15
- # Returns candiate filenames.
16
+ # Returns candidate filenames.
16
17
  def resolve_alternates(load_path, logical_path)
17
18
  candidates, deps = super
18
19
 
19
20
  # bower.json can only be nested one level deep
20
- if !logical_path.index('/')
21
+ if !logical_path.index('/'.freeze)
21
22
  dirname = File.join(load_path, logical_path)
22
23
 
23
24
  if directory?(dirname)
@@ -27,7 +28,9 @@ module Sprockets
27
28
  if filename
28
29
  deps << build_file_digest_uri(filename)
29
30
  read_bower_main(dirname, filename) do |path|
30
- candidates << path
31
+ if file?(path)
32
+ candidates << path
33
+ end
31
34
  end
32
35
  end
33
36
  end