sprockets 2.12.5 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sprockets might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -2
  3. data/README.md +61 -34
  4. data/lib/rake/sprocketstask.rb +5 -4
  5. data/lib/sprockets.rb +123 -85
  6. data/lib/sprockets/asset.rb +161 -200
  7. data/lib/sprockets/asset_uri.rb +64 -0
  8. data/lib/sprockets/base.rb +138 -373
  9. data/lib/sprockets/bower.rb +56 -0
  10. data/lib/sprockets/bundle.rb +32 -0
  11. data/lib/sprockets/cache.rb +220 -0
  12. data/lib/sprockets/cache/file_store.rb +145 -13
  13. data/lib/sprockets/cache/memory_store.rb +66 -0
  14. data/lib/sprockets/cache/null_store.rb +46 -0
  15. data/lib/sprockets/cached_environment.rb +103 -0
  16. data/lib/sprockets/closure_compressor.rb +30 -12
  17. data/lib/sprockets/coffee_script_template.rb +23 -0
  18. data/lib/sprockets/compressing.rb +20 -25
  19. data/lib/sprockets/configuration.rb +95 -0
  20. data/lib/sprockets/context.rb +68 -131
  21. data/lib/sprockets/directive_processor.rb +138 -179
  22. data/lib/sprockets/eco_template.rb +10 -19
  23. data/lib/sprockets/ejs_template.rb +10 -19
  24. data/lib/sprockets/encoding_utils.rb +246 -0
  25. data/lib/sprockets/engines.rb +40 -29
  26. data/lib/sprockets/environment.rb +10 -66
  27. data/lib/sprockets/erb_template.rb +23 -0
  28. data/lib/sprockets/errors.rb +5 -13
  29. data/lib/sprockets/http_utils.rb +97 -0
  30. data/lib/sprockets/jst_processor.rb +28 -15
  31. data/lib/sprockets/lazy_processor.rb +15 -0
  32. data/lib/sprockets/legacy.rb +23 -0
  33. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  34. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  35. data/lib/sprockets/manifest.rb +128 -99
  36. data/lib/sprockets/mime.rb +114 -33
  37. data/lib/sprockets/path_utils.rb +179 -0
  38. data/lib/sprockets/paths.rb +13 -26
  39. data/lib/sprockets/processing.rb +198 -107
  40. data/lib/sprockets/resolve.rb +289 -0
  41. data/lib/sprockets/sass_compressor.rb +36 -17
  42. data/lib/sprockets/sass_template.rb +269 -46
  43. data/lib/sprockets/server.rb +113 -83
  44. data/lib/sprockets/transformers.rb +69 -0
  45. data/lib/sprockets/uglifier_compressor.rb +36 -15
  46. data/lib/sprockets/utils.rb +161 -44
  47. data/lib/sprockets/version.rb +1 -1
  48. data/lib/sprockets/yui_compressor.rb +37 -12
  49. metadata +64 -106
  50. data/lib/sprockets/asset_attributes.rb +0 -137
  51. data/lib/sprockets/bundled_asset.rb +0 -78
  52. data/lib/sprockets/caching.rb +0 -96
  53. data/lib/sprockets/charset_normalizer.rb +0 -41
  54. data/lib/sprockets/index.rb +0 -100
  55. data/lib/sprockets/processed_asset.rb +0 -152
  56. data/lib/sprockets/processor.rb +0 -32
  57. data/lib/sprockets/safety_colons.rb +0 -28
  58. data/lib/sprockets/sass_cache_store.rb +0 -29
  59. data/lib/sprockets/sass_functions.rb +0 -70
  60. data/lib/sprockets/sass_importer.rb +0 -30
  61. data/lib/sprockets/scss_template.rb +0 -13
  62. data/lib/sprockets/static_asset.rb +0 -60
@@ -0,0 +1,66 @@
1
+ module Sprockets
2
+ class Cache
3
+ # Public: Basic in memory LRU cache.
4
+ #
5
+ # Assign the instance to the Environment#cache.
6
+ #
7
+ # environment.cache = Sprockets::Cache::MemoryStore.new(1000)
8
+ #
9
+ # See Also
10
+ #
11
+ # ActiveSupport::Cache::MemoryStore
12
+ #
13
+ class MemoryStore
14
+ # Internal: Default key limit for store.
15
+ DEFAULT_MAX_SIZE = 1000
16
+
17
+ # Public: Initialize the cache store.
18
+ #
19
+ # max_size - A Integer of the maximum number of keys the store will hold.
20
+ # (default: 1000).
21
+ def initialize(max_size = DEFAULT_MAX_SIZE)
22
+ @max_size = max_size
23
+ @cache = {}
24
+ end
25
+
26
+ # Public: Retrieve value from cache.
27
+ #
28
+ # This API should not be used directly, but via the Cache wrapper API.
29
+ #
30
+ # key - String cache key.
31
+ #
32
+ # Returns Object or nil or the value is not set.
33
+ def get(key)
34
+ exists = true
35
+ value = @cache.delete(key) { exists = false }
36
+ if exists
37
+ @cache[key] = value
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ # Public: Set a key and value in the cache.
44
+ #
45
+ # This API should not be used directly, but via the Cache wrapper API.
46
+ #
47
+ # key - String cache key.
48
+ # value - Object value.
49
+ #
50
+ # Returns Object value.
51
+ def set(key, value)
52
+ @cache.delete(key)
53
+ @cache[key] = value
54
+ @cache.shift if @cache.size > @max_size
55
+ value
56
+ end
57
+
58
+ # Public: Pretty inspect
59
+ #
60
+ # Returns String.
61
+ def inspect
62
+ "#<#{self.class} size=#{@cache.size}/#{@max_size}>"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,46 @@
1
+ module Sprockets
2
+ class Cache
3
+ # Public: A compatible cache store that doesn't store anything. Used by
4
+ # default when no Environment#cache is configured.
5
+ #
6
+ # Assign the instance to the Environment#cache.
7
+ #
8
+ # environment.cache = Sprockets::Cache::NullStore.new
9
+ #
10
+ # See Also
11
+ #
12
+ # ActiveSupport::Cache::NullStore
13
+ #
14
+ class NullStore
15
+ # Public: Simulate a cache miss.
16
+ #
17
+ # This API should not be used directly, but via the Cache wrapper API.
18
+ #
19
+ # key - String cache key.
20
+ #
21
+ # Returns nil.
22
+ def get(key)
23
+ nil
24
+ end
25
+
26
+ # Public: Simulate setting a value in the cache.
27
+ #
28
+ # This API should not be used directly, but via the Cache wrapper API.
29
+ #
30
+ # key - String cache key.
31
+ # value - Object value.
32
+ #
33
+ # Returns Object value.
34
+ def set(key, value)
35
+ value
36
+ end
37
+
38
+ # Public: Pretty inspect
39
+ #
40
+ # Returns String.
41
+ def inspect
42
+ "#<#{self.class}>"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,103 @@
1
+ require 'sprockets/base'
2
+
3
+ module Sprockets
4
+ # `Cached` is a special cached version of `Environment`.
5
+ #
6
+ # The expection is that all of its file system methods are cached
7
+ # for the instances lifetime. This makes `Cached` much faster. This
8
+ # behavior is ideal in production environments where the file system
9
+ # is immutable.
10
+ #
11
+ # `Cached` should not be initialized directly. Instead use
12
+ # `Environment#cached`.
13
+ class CachedEnvironment < Base
14
+ def initialize(environment)
15
+ initialize_configuration(environment)
16
+
17
+ @cache = environment.cache
18
+ @stats = Hash.new { |h, k| h[k] = _stat(k) }
19
+ @entries = Hash.new { |h, k| h[k] = _entries(k) }
20
+ @hexdigests = Hash.new { |h, k| h[k] = _file_hexdigest(k) }
21
+ end
22
+
23
+ # No-op return self as cached environment.
24
+ def cached
25
+ self
26
+ end
27
+ alias_method :index, :cached
28
+
29
+ # Internal: Cache Environment#entries
30
+ alias_method :_entries, :entries
31
+ def entries(path)
32
+ @entries[path]
33
+ end
34
+
35
+ # Internal: Cache Environment#stat
36
+ alias_method :_stat, :stat
37
+ def stat(path)
38
+ @stats[path]
39
+ end
40
+
41
+ # Internal: Cache Environment#file_hexdigest
42
+ alias_method :_file_hexdigest, :file_hexdigest
43
+ def file_hexdigest(path)
44
+ @hexdigests[path]
45
+ end
46
+
47
+ protected
48
+ def asset_dependency_graph_cache_key(uri)
49
+ filename, _ = AssetURI.parse(uri)
50
+ [
51
+ 'asset-uri-dep-graph',
52
+ VERSION,
53
+ self.version,
54
+ self.paths,
55
+ uri,
56
+ file_hexdigest(filename)
57
+ ]
58
+ end
59
+
60
+ def asset_digest_uri_cache_key(uri)
61
+ [
62
+ 'asset-digest-uri',
63
+ VERSION,
64
+ self.version,
65
+ uri
66
+ ]
67
+ end
68
+
69
+ def build_asset_by_digest_uri(uri)
70
+ cache.fetch(asset_digest_uri_cache_key(uri)) do
71
+ super
72
+ end
73
+ end
74
+
75
+ def build_asset_by_uri(uri)
76
+ dep_graph_key = asset_dependency_graph_cache_key(uri)
77
+
78
+ dependency_paths, dependency_digest, digest_uri = cache._get(dep_graph_key)
79
+ if dependency_paths && dependency_digest && digest_uri
80
+ if dependencies_hexdigest(dependency_paths) == dependency_digest
81
+ if asset = cache._get(asset_digest_uri_cache_key(digest_uri))
82
+ return asset
83
+ end
84
+ end
85
+ end
86
+
87
+ asset = super
88
+
89
+ dependency_digest, dependency_paths = asset[:metadata].values_at(:dependency_digest, :dependency_paths)
90
+ cache._set(dep_graph_key, [dependency_paths, dependency_digest, asset[:uri]])
91
+ cache.fetch(asset_digest_uri_cache_key(asset[:uri])) { asset }
92
+
93
+ asset
94
+ end
95
+
96
+ private
97
+ # Cache is immutable, any methods that try to clear the cache
98
+ # should bomb.
99
+ def mutate_config(*args)
100
+ raise RuntimeError, "can't modify immutable cached environment"
101
+ end
102
+ end
103
+ end
@@ -1,22 +1,40 @@
1
- require 'tilt'
1
+ require 'closure-compiler'
2
2
 
3
3
  module Sprockets
4
- class ClosureCompressor < Tilt::Template
5
- self.default_mime_type = 'application/javascript'
4
+ # Public: Closure Compiler minifier.
5
+ #
6
+ # To accept the default options
7
+ #
8
+ # environment.register_bundle_processor 'application/javascript',
9
+ # Sprockets::ClosureCompressor
10
+ #
11
+ # Or to pass options to the Closure::Compiler class.
12
+ #
13
+ # environment.register_bundle_processor 'application/javascript',
14
+ # Sprockets::ClosureCompressor.new({ ... })
15
+ #
16
+ class ClosureCompressor
17
+ VERSION = '1'
6
18
 
7
- def self.engine_initialized?
8
- defined?(::Closure::Compiler)
19
+ def self.call(*args)
20
+ new.call(*args)
9
21
  end
10
22
 
11
- def initialize_engine
12
- require_template_library 'closure-compiler'
23
+ def initialize(options = {})
24
+ @compiler = ::Closure::Compiler.new(options)
25
+ @cache_key = [
26
+ 'ClosureCompressor',
27
+ ::Closure::VERSION,
28
+ ::Closure::COMPILER_VERSION,
29
+ VERSION,
30
+ options
31
+ ]
13
32
  end
14
33
 
15
- def prepare
16
- end
17
-
18
- def evaluate(context, locals, &block)
19
- Closure::Compiler.new.compile(data)
34
+ def call(input)
35
+ input[:cache].fetch(@cache_key + [input[:data]]) do
36
+ @compiler.compile(input[:data])
37
+ end
20
38
  end
21
39
  end
22
40
  end
@@ -0,0 +1,23 @@
1
+ require 'coffee_script'
2
+
3
+ module Sprockets
4
+ # Template engine class for the CoffeeScript compiler.
5
+ # Depends on the `coffee-script` and `coffee-script-source` gems.
6
+ #
7
+ # For more infomation see:
8
+ #
9
+ # https://github.com/josh/ruby-coffee-script
10
+ #
11
+ module CoffeeScriptTemplate
12
+ VERSION = '1'
13
+ SOURCE_VERSION = ::CoffeeScript::Source.version
14
+
15
+ def self.call(input)
16
+ data = input[:data]
17
+ key = ['CoffeeScriptTemplate', SOURCE_VERSION, VERSION, data]
18
+ input[:cache].fetch(key) do
19
+ ::CoffeeScript.compile(data)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,37 +1,35 @@
1
1
  module Sprockets
2
2
  # `Compressing` is an internal mixin whose public methods are exposed on
3
- # the `Environment` and `Index` classes.
3
+ # the `Environment` and `CachedEnvironment` classes.
4
4
  module Compressing
5
- def compressors
6
- deep_copy_hash(@compressors)
7
- end
5
+ attr_reader :compressors
8
6
 
9
7
  def register_compressor(mime_type, sym, klass)
10
- @compressors[mime_type][sym] = klass
8
+ mutate_hash_config(:compressors, mime_type) do |compressors|
9
+ compressors[sym] = klass
10
+ compressors
11
+ end
11
12
  end
12
13
 
13
14
  # Return CSS compressor or nil if none is set
14
15
  def css_compressor
15
- @css_compressor if defined? @css_compressor
16
+ if defined? @css_compressor
17
+ unwrap_processor(@css_compressor)
18
+ end
16
19
  end
17
20
 
18
21
  # Assign a compressor to run on `text/css` assets.
19
22
  #
20
23
  # The compressor object must respond to `compress`.
21
24
  def css_compressor=(compressor)
22
- unregister_bundle_processor 'text/css', css_compressor if css_compressor
25
+ unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor
23
26
  @css_compressor = nil
24
27
  return unless compressor
25
28
 
26
29
  if compressor.is_a?(Symbol)
27
- compressor = compressors['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
28
- end
29
-
30
- if compressor.respond_to?(:compress)
31
- klass = Class.new(Processor) do
32
- @name = "css_compressor"
33
- @processor = proc { |context, data| compressor.compress(data) }
34
- end
30
+ @css_compressor = klass = compressors['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
31
+ elsif compressor.respond_to?(:compress)
32
+ klass = LegacyProcProcessor.new(:css_compressor, proc { |context, data| compressor.compress(data) })
35
33
  @css_compressor = :css_compressor
36
34
  else
37
35
  @css_compressor = klass = compressor
@@ -42,26 +40,23 @@ module Sprockets
42
40
 
43
41
  # Return JS compressor or nil if none is set
44
42
  def js_compressor
45
- @js_compressor if defined? @js_compressor
43
+ if defined? @js_compressor
44
+ unwrap_processor(@js_compressor)
45
+ end
46
46
  end
47
47
 
48
48
  # Assign a compressor to run on `application/javascript` assets.
49
49
  #
50
50
  # The compressor object must respond to `compress`.
51
51
  def js_compressor=(compressor)
52
- unregister_bundle_processor 'application/javascript', js_compressor if js_compressor
52
+ unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor
53
53
  @js_compressor = nil
54
54
  return unless compressor
55
55
 
56
56
  if compressor.is_a?(Symbol)
57
- compressor = compressors['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
58
- end
59
-
60
- if compressor.respond_to?(:compress)
61
- klass = Class.new(Processor) do
62
- @name = "js_compressor"
63
- @processor = proc { |context, data| compressor.compress(data) }
64
- end
57
+ @js_compressor = klass = compressors['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
58
+ elsif compressor.respond_to?(:compress)
59
+ klass = LegacyProcProcessor.new(:js_compressor, proc { |context, data| compressor.compress(data) })
65
60
  @js_compressor = :js_compressor
66
61
  else
67
62
  @js_compressor = klass = compressor
@@ -0,0 +1,95 @@
1
+ require 'sprockets/compressing'
2
+ require 'sprockets/engines'
3
+ require 'sprockets/mime'
4
+ require 'sprockets/paths'
5
+ require 'sprockets/processing'
6
+ require 'sprockets/transformers'
7
+
8
+ module Sprockets
9
+ module Configuration
10
+ include Paths, Mime, Engines, Transformers, Processing, Compressing
11
+
12
+ def initialize_configuration(parent)
13
+ @logger = parent.logger
14
+ @version = parent.version
15
+ @digest_class = parent.digest_class
16
+ @context_class = Class.new(parent.context_class)
17
+ @root = parent.root
18
+ @paths = parent.paths
19
+ @mime_types = parent.mime_types
20
+ @mime_exts = parent.mime_exts
21
+ @encodings = parent.encodings
22
+ @engines = parent.engines
23
+ @engine_mime_types = parent.engine_mime_types
24
+ @transformers = parent.transformers
25
+ @preprocessors = parent.preprocessors
26
+ @postprocessors = parent.postprocessors
27
+ @bundle_reducers = parent.bundle_reducers
28
+ @bundle_processors = parent.bundle_processors
29
+ @compressors = parent.compressors
30
+ end
31
+
32
+ # Get and set `Logger` instance.
33
+ attr_accessor :logger
34
+
35
+ # The `Environment#version` is a custom value used for manually
36
+ # expiring all asset caches.
37
+ #
38
+ # Sprockets is able to track most file and directory changes and
39
+ # will take care of expiring the cache for you. However, its
40
+ # impossible to know when any custom helpers change that you mix
41
+ # into the `Context`.
42
+ #
43
+ # It would be wise to increment this value anytime you make a
44
+ # configuration change to the `Environment` object.
45
+ attr_reader :version
46
+
47
+ # Assign an environment version.
48
+ #
49
+ # environment.version = '2.0'
50
+ #
51
+ def version=(version)
52
+ mutate_config(:version) { version.dup }
53
+ end
54
+
55
+ # Returns a `Digest` implementation class.
56
+ #
57
+ # Defaults to `Digest::SHA1`.
58
+ attr_reader :digest_class
59
+
60
+ # Assign a `Digest` implementation class. This maybe any Ruby
61
+ # `Digest::` implementation such as `Digest::SHA1` or
62
+ # `Digest::MD5`.
63
+ #
64
+ # environment.digest_class = Digest::MD5
65
+ #
66
+ def digest_class=(klass)
67
+ @digest_class = klass
68
+ end
69
+
70
+ # Deprecated: Get `Context` class.
71
+ #
72
+ # This class maybe mutated and mixed in with custom helpers.
73
+ #
74
+ # environment.context_class.instance_eval do
75
+ # include MyHelpers
76
+ # def asset_url; end
77
+ # end
78
+ #
79
+ attr_reader :context_class
80
+
81
+ private
82
+ def mutate_config(sym)
83
+ obj = yield self.instance_variable_get("@#{sym}").dup
84
+ self.instance_variable_set("@#{sym}", obj.freeze)
85
+ end
86
+
87
+ def mutate_hash_config(sym, key)
88
+ mutate_config(sym) do |hash|
89
+ obj = yield hash[key].dup
90
+ hash[key] = obj.freeze
91
+ hash
92
+ end
93
+ end
94
+ end
95
+ end