sprockets 4.0.0.beta3 → 4.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 190eb10207d44db44036ce106c1dcc7947ffc52e
4
- data.tar.gz: 49cc56e570a817c030e35b7be44d4adee429285b
3
+ metadata.gz: 52d1dd6743105d82450959d44e27538e02130e7e
4
+ data.tar.gz: 02f58032703403898778cc33d6a56e0ca8bbee01
5
5
  SHA512:
6
- metadata.gz: 20c3b7704e5aa45a458001cfa1213aa04226f5616bb339a818b1333abfe33b0d3f58305165d5170888f8d947f936372f83e39639c40afdb68fc61a3cf6f98348
7
- data.tar.gz: 127f4dd25e9d3129b0a65859fc65e31442ca22021debee813d15abf48473a8bd606d862d985156358df406803135f5a2cf5ed363c6ce59010f21f5a8ef60cbdd
6
+ metadata.gz: ac089336d45577a2647ba6b329c5a62862613674be5969f4312e0d7afbb0cf3a9a06cbc7d9db7dd55649fac74c234da54d4482f83d091c4d2357269502006d99
7
+ data.tar.gz: 990363e0a8e9545ad6243279075cad733d7b403b45737b0c76b35d4647824e693c9f012c88b4220a431970d649dc7ad3582980311dc30c8e2b7b1871c1e5d611
@@ -2,6 +2,19 @@
2
2
 
3
3
  Get upgrade notes from Sprockets 3.x to 4.x at https://github.com/rails/sprockets/blob/master/UPGRADING.md
4
4
 
5
+ ## Master
6
+
7
+
8
+ ## 4.0.0.beta4
9
+
10
+ - Changing the version now busts the digest of all assets [#404]
11
+ - Exporter interface added [#386]
12
+ - Using ENV vars in templates will recompile templates when the env vars change. [#365]
13
+ - Source maps for imported sass files with sassc is now fixed [#391]
14
+ - Load paths now in error messages [#322]
15
+ - Cache key added to babel processor [#387]
16
+ - `Environment#find_asset!` can now be used to raise an exception when asset could not be found [#379]
17
+
5
18
  ## 4.0.0.beta3
6
19
 
7
20
  - Source Map fixes [#255] [#367]
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ $VERBOSE = nil
2
3
 
3
4
  require 'sprockets'
4
5
  require 'optparse'
@@ -34,8 +34,10 @@ module Sprockets
34
34
  registered_transformers: [].freeze,
35
35
  root: __dir__.dup.freeze,
36
36
  transformers: Hash.new { |h, k| {}.freeze }.freeze,
37
+ exporters: Hash.new { |h, k| Set.new.freeze }.freeze,
37
38
  version: "",
38
- gzip_enabled: true
39
+ gzip_enabled: true,
40
+ export_concurrent: true
39
41
  }.freeze
40
42
 
41
43
  @context_class = Context
@@ -126,6 +128,7 @@ module Sprockets
126
128
  register_bundle_metadata_reducer '*/*', :data, proc { String.new("") }, :concat
127
129
  register_bundle_metadata_reducer 'application/javascript', :data, proc { String.new("") }, Utils.method(:concat_javascript_sources)
128
130
  register_bundle_metadata_reducer '*/*', :links, :+
131
+ register_bundle_metadata_reducer '*/*', :sources, proc { [] }, :+
129
132
  register_bundle_metadata_reducer '*/*', :map, SourceMapUtils.method(:concat_source_maps)
130
133
 
131
134
  require 'sprockets/closure_compressor'
@@ -197,6 +200,11 @@ module Sprockets
197
200
  register_mime_type 'application/html+ruby', extensions: ['.html.erb', '.erb', '.rhtml'], charset: :html
198
201
  register_mime_type 'application/xml+ruby', extensions: ['.xml.erb', '.rxml']
199
202
 
203
+ require 'sprockets/exporters/file_exporter'
204
+ require 'sprockets/exporters/zlib_exporter'
205
+ require 'sprockets/exporters/zopfli_exporter'
206
+ register_exporter '*/*', Exporters::FileExporter
207
+ register_exporter '*/*', Exporters::ZlibExporter
200
208
 
201
209
  register_dependency_resolver 'environment-version' do |env|
202
210
  env.version
@@ -210,6 +218,10 @@ module Sprockets
210
218
  register_dependency_resolver 'processors' do |env, str|
211
219
  env.resolve_processors_cache_key_uri(str)
212
220
  end
221
+ register_dependency_resolver 'env' do |env, str|
222
+ _, var = str.split(':', 2)
223
+ ENV[var]
224
+ end
213
225
 
214
226
  depend_on 'environment-version'
215
227
  depend_on 'environment-paths'
@@ -67,6 +67,13 @@ module Sprockets
67
67
  logical_path.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
68
68
  end
69
69
 
70
+ # Public: Return load path + logical path with digest spliced in.
71
+ #
72
+ # Returns String.
73
+ def full_digest_path
74
+ File.join(@load_path, digest_path)
75
+ end
76
+
70
77
  # Public: Returns String MIME type of asset. Returns nil if type is unknown.
71
78
  attr_reader :content_type
72
79
 
@@ -6,10 +6,11 @@ module Sprockets
6
6
  autoload :CoffeeScript, 'sprockets/autoload/coffee_script'
7
7
  autoload :Eco, 'sprockets/autoload/eco'
8
8
  autoload :EJS, 'sprockets/autoload/ejs'
9
- autoload :JSMinC, 'sprockets/autoload/jsminc'
9
+ autoload :JSMinC, 'sprockets/autoload/jsminc'
10
10
  autoload :Sass, 'sprockets/autoload/sass'
11
11
  autoload :SassC, 'sprockets/autoload/sassc'
12
12
  autoload :Uglifier, 'sprockets/autoload/uglifier'
13
13
  autoload :YUI, 'sprockets/autoload/yui'
14
+ autoload :Zopfli, 'sprockets/autoload/zopfli'
14
15
  end
15
16
  end
@@ -0,0 +1,7 @@
1
+ require 'zopfli'
2
+
3
+ module Sprockets
4
+ module Autoload
5
+ Zopfli = ::Zopfli
6
+ end
7
+ end
@@ -16,6 +16,12 @@ module Sprockets
16
16
  instance.call(input)
17
17
  end
18
18
 
19
+ def self.cache_key
20
+ instance.cache_key
21
+ end
22
+
23
+ attr_reader :cache_key
24
+
19
25
  def initialize(options = {})
20
26
  @options = options.merge({
21
27
  'blacklist' => (options['blacklist'] || []) + ['useStrict'],
@@ -93,6 +93,16 @@ module Sprockets
93
93
  find_asset(*args)
94
94
  end
95
95
 
96
+ # Find asset by logical path or expanded path.
97
+ #
98
+ # If the asset is not found an error will be raised.
99
+ def find_asset!(*args)
100
+ uri, _ = resolve!(*args)
101
+ if uri
102
+ load(uri)
103
+ end
104
+ end
105
+
96
106
  # Pretty inspect
97
107
  def inspect
98
108
  "#<#{self.class}:0x#{object_id.to_s(16)} " +
@@ -102,12 +102,33 @@ module Sprockets
102
102
 
103
103
  # Public: Enable or disable the creation of Gzip files.
104
104
  #
105
- # Defaults to true.
105
+ # To disable gzip generation set to a falsey value:
106
106
  #
107
107
  # environment.gzip = false
108
108
  #
109
+ # To enable set to a truthy value. By default zlib wil
110
+ # be used to gzip assets. If you have the Zopfli gem
111
+ # installed you can specify the zopfli algorithm to be used
112
+ # instead:
113
+ #
114
+ # environment.gzip = :zopfli
115
+ #
109
116
  def gzip=(gzip)
110
117
  self.config = config.merge(gzip_enabled: gzip).freeze
118
+
119
+ case gzip
120
+ when false, nil
121
+ self.unregister_exporter Exporters::ZlibExporter
122
+ self.unregister_exporter Exporters::ZopfliExporter
123
+ when :zopfli
124
+ self.unregister_exporter Exporters::ZlibExporter
125
+ self.register_exporter '*/*', Exporters::ZopfliExporter
126
+ else
127
+ self.unregister_exporter Exporters::ZopfliExporter
128
+ self.register_exporter '*/*', Exporters::ZlibExporter
129
+ end
130
+
131
+ gzip
111
132
  end
112
133
  end
113
134
  end
@@ -4,12 +4,13 @@ require 'sprockets/dependencies'
4
4
  require 'sprockets/mime'
5
5
  require 'sprockets/paths'
6
6
  require 'sprockets/processing'
7
+ require 'sprockets/exporting'
7
8
  require 'sprockets/transformers'
8
9
  require 'sprockets/utils'
9
10
 
10
11
  module Sprockets
11
12
  module Configuration
12
- include Paths, Mime, Transformers, Processing, Compressing, Dependencies, Utils
13
+ include Paths, Mime, Transformers, Processing, Exporting, Compressing, Dependencies, Utils
13
14
 
14
15
  def initialize_configuration(parent)
15
16
  @config = parent.config
@@ -18,6 +18,24 @@ module Sprockets
18
18
  # The `Context` also collects dependencies declared by
19
19
  # assets. See `DirectiveProcessor` for an example of this.
20
20
  class Context
21
+ # Internal: Proxy for ENV that keeps track of the environment variables used
22
+ class ENVProxy < SimpleDelegator
23
+ def initialize(context)
24
+ @context = context
25
+ super(ENV)
26
+ end
27
+
28
+ def [](key)
29
+ @context.depend_on_env(key)
30
+ super
31
+ end
32
+
33
+ def fetch(key, *)
34
+ @context.depend_on_env(key)
35
+ super
36
+ end
37
+ end
38
+
21
39
  attr_reader :environment, :filename
22
40
 
23
41
  def initialize(input)
@@ -42,6 +60,10 @@ module Sprockets
42
60
  dependencies: @dependencies }
43
61
  end
44
62
 
63
+ def env_proxy
64
+ ENVProxy.new(self)
65
+ end
66
+
45
67
  # Returns the environment path that contains the file.
46
68
  #
47
69
  # If `app/javascripts` and `app/stylesheets` are in your path, and
@@ -122,6 +144,15 @@ module Sprockets
122
144
  load(resolve(path))
123
145
  end
124
146
 
147
+ # `depend_on_env` allows you to state a dependency on an environment
148
+ # variable.
149
+ #
150
+ # This is used for caching purposes. Any changes in the value of the
151
+ # environment variable will invalidate the cache of the source file.
152
+ def depend_on_env(key)
153
+ @dependencies << "env:#{key}"
154
+ end
155
+
125
156
  # `require_asset` declares `path` as a dependency of the file. The
126
157
  # dependency will be inserted before the file and will only be
127
158
  # included once.
@@ -45,12 +45,8 @@ module Sprockets
45
45
  digest << 'Symbol'.freeze
46
46
  digest << val.to_s
47
47
  },
48
- Fixnum => ->(val, digest) {
49
- digest << 'Fixnum'.freeze
50
- digest << val.to_s
51
- },
52
- Bignum => ->(val, digest) {
53
- digest << 'Bignum'.freeze
48
+ Integer => ->(val, digest) {
49
+ digest << 'Integer'.freeze
54
50
  digest << val.to_s
55
51
  },
56
52
  Array => ->(val, digest) {
@@ -74,6 +70,16 @@ module Sprockets
74
70
  digest << val.name
75
71
  },
76
72
  }
73
+ if 0.class != Integer # Ruby < 2.4
74
+ ADD_VALUE_TO_DIGEST[Fixnum] = ->(val, digest) {
75
+ digest << 'Integer'.freeze
76
+ digest << val.to_s
77
+ }
78
+ ADD_VALUE_TO_DIGEST[Bignum] = ->(val, digest) {
79
+ digest << 'Integer'.freeze
80
+ digest << val.to_s
81
+ }
82
+ end
77
83
  ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
78
84
  raise TypeError, "couldn't digest #{ val }"
79
85
  }
@@ -31,6 +31,10 @@ module Sprockets
31
31
  cached.find_asset(*args)
32
32
  end
33
33
 
34
+ def find_asset!(*args)
35
+ cached.find_asset!(*args)
36
+ end
37
+
34
38
  def find_all_linked_assets(*args, &block)
35
39
  cached.find_all_linked_assets(*args, &block)
36
40
  end
@@ -1,31 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
  require 'erb'
3
3
 
4
- module Sprockets
5
- class ERBProcessor
6
- # Public: Return singleton instance with default options.
7
- #
8
- # Returns ERBProcessor object.
9
- def self.instance
10
- @instance ||= new
11
- end
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
12
19
 
13
- def self.call(input)
14
- instance.call(input)
15
- end
20
+ def call(input)
21
+ engine = ::ERB.new(input[:data], nil, '<>')
22
+ engine.filename = input[:filename]
16
23
 
17
- def initialize(&block)
18
- @block = block
19
- end
24
+ context = input[:environment].context_class.new(input)
25
+ klass = (class << context; self; end)
26
+ klass.const_set(:ENV, context.env_proxy)
27
+ klass.class_eval(&@block) if @block
20
28
 
21
- def call(input)
22
- engine = ::ERB.new(input[:data], nil, '<>')
23
- context = input[:environment].context_class.new(input)
24
- klass = (class << context; self; end)
25
- klass.class_eval(&@block) if @block
26
- engine.def_method(klass, :_evaluate_template, input[:filename])
27
- data = context._evaluate_template
28
- context.metadata.merge(data: data)
29
- end
29
+ data = engine.result(context.instance_eval('binding'))
30
+ context.metadata.merge(data: data)
30
31
  end
31
32
  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
@@ -163,7 +163,7 @@ module Sprockets
163
163
  source = result.delete(:data)
164
164
  metadata = result
165
165
  metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
166
- metadata[:digest] = digest(source)
166
+ metadata[:digest] = digest(self.version + source)
167
167
  metadata[:length] = source.bytesize
168
168
  else
169
169
  dependencies << build_file_digest_uri(unloaded.filename)
@@ -5,7 +5,6 @@ require 'time'
5
5
  require 'concurrent'
6
6
 
7
7
  require 'sprockets/manifest_utils'
8
- require 'sprockets/utils/gzip'
9
8
 
10
9
  module Sprockets
11
10
  # The Manifest logs the contents of assets compiled to a single directory. It
@@ -147,7 +146,7 @@ module Sprockets
147
146
  end
148
147
  end
149
148
 
150
- # Compile and write asset to directory. The asset is written to a
149
+ # Compile asset to directory. The asset is written to a
151
150
  # fingerprinted filename like
152
151
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
153
152
  # also inserted into the manifest file.
@@ -159,14 +158,15 @@ module Sprockets
159
158
  raise Error, "manifest requires environment for compilation"
160
159
  end
161
160
 
162
- filenames = []
163
- concurrent_compressors = []
164
- concurrent_writers = []
161
+ filenames = []
162
+ concurrent_exporters = []
163
+ executor = Concurrent::FixedThreadPool.new(Concurrent.processor_count)
165
164
 
166
165
  find(*args) do |asset|
166
+ mtime = Time.now.iso8601
167
167
  files[asset.digest_path] = {
168
168
  'logical_path' => asset.logical_path,
169
- 'mtime' => Time.now.iso8601,
169
+ 'mtime' => mtime,
170
170
  'size' => asset.bytesize,
171
171
  'digest' => asset.hexdigest,
172
172
 
@@ -177,34 +177,28 @@ module Sprockets
177
177
  }
178
178
  assets[asset.logical_path] = asset.digest_path
179
179
 
180
- target = File.join(dir, asset.digest_path)
181
-
182
- if File.exist?(target)
183
- logger.debug "Skipping #{target}, already exists"
184
- else
185
- logger.info "Writing #{target}"
186
- write_file = Concurrent::Future.execute { asset.write_to target }
187
- concurrent_writers << write_file
188
- end
189
180
  filenames << asset.filename
190
181
 
191
- next if environment.skip_gzip?
192
- gzip = Utils::Gzip.new(asset)
193
- next if gzip.cannot_compress?(environment.mime_types)
182
+ promise = nil
183
+ exporters_for_asset(asset) do |exporter|
184
+ next if exporter.skip?(logger)
194
185
 
195
- if File.exist?("#{target}.gz")
196
- logger.debug "Skipping #{target}.gz, already exists"
197
- else
198
- logger.info "Writing #{target}.gz"
199
- concurrent_compressors << Concurrent::Future.execute do
200
- write_file.wait! if write_file
201
- gzip.compress(target)
186
+ if !environment.export_concurrent
187
+ exporter.call
188
+ next
202
189
  end
203
- end
204
190
 
191
+ if promise.nil?
192
+ promise = Concurrent::Promise.new(executor: executor) { exporter.call }
193
+ concurrent_exporters << promise.execute
194
+ else
195
+ concurrent_exporters << promise.then { exporter.call }
196
+ end
197
+ end
205
198
  end
206
- concurrent_writers.each(&:wait!)
207
- concurrent_compressors.each(&:wait!)
199
+
200
+ # make sure all exporters have finished before returning the main thread
201
+ concurrent_exporters.each(&:wait!)
208
202
  save
209
203
 
210
204
  filenames
@@ -284,6 +278,35 @@ module Sprockets
284
278
  end
285
279
 
286
280
  private
281
+
282
+ # Given an asset, finds all exporters that
283
+ # match its mime-type.
284
+ #
285
+ # Will yield each expoter to the passed in block.
286
+ #
287
+ # array = []
288
+ # puts asset.content_type # => "application/javascript"
289
+ # exporters_for_asset(asset) do |exporter|
290
+ # array << exporter
291
+ # end
292
+ # # puts array => [Exporters::FileExporter, Exporters::ZlibExporter]
293
+ def exporters_for_asset(asset)
294
+ exporters = [Exporters::FileExporter]
295
+
296
+ environment.exporters.each do |mime_type, exporter_list|
297
+ next unless environment.match_mime_type? asset.content_type, mime_type
298
+ exporter_list.each do |exporter|
299
+ exporters << exporter
300
+ end
301
+ end
302
+
303
+ exporters.uniq!
304
+
305
+ exporters.each do |exporter|
306
+ yield exporter.new(asset: asset, environment: environment, directory: dir)
307
+ end
308
+ end
309
+
287
310
  def json_decode(obj)
288
311
  JSON.parse(obj, create_additions: false)
289
312
  end
@@ -6,6 +6,7 @@ module Sprockets
6
6
  # when code actually wants to reference ::FileUtils.
7
7
  module PathUtils
8
8
  extend self
9
+ require 'pathname'
9
10
 
10
11
  # Public: Like `File.stat`.
11
12
  #
@@ -73,8 +74,6 @@ module Sprockets
73
74
  #
74
75
  # Returns true if path is absolute, otherwise false.
75
76
  if File::ALT_SEPARATOR
76
- require 'pathname'
77
-
78
77
  # On Windows, ALT_SEPARATOR is \
79
78
  # Delegate to Pathname since the logic gets complex.
80
79
  def absolute_path?(path)
@@ -102,6 +101,33 @@ module Sprockets
102
101
  path =~ /^\.\.?($|#{SEPARATOR_PATTERN})/ ? true : false
103
102
  end
104
103
 
104
+ # Public: Get relative path from `start` to `dest`.
105
+ #
106
+ # start - String start path (file or dir)
107
+ # dest - String destination path
108
+ #
109
+ # Returns relative String path from `start` to `dest`
110
+ def relative_path_from(start, dest)
111
+ start, dest = Pathname.new(start), Pathname.new(dest)
112
+ start = start.dirname unless start.directory?
113
+ dest.relative_path_from(start).to_s
114
+ end
115
+
116
+ # Public: Joins path to base path.
117
+ #
118
+ # base - Root path
119
+ # path - Extending path
120
+ #
121
+ # Example
122
+ #
123
+ # join('base/path/', '../file.js')
124
+ # # => 'base/file.js'
125
+ #
126
+ # Returns string path starting from base and ending at path
127
+ def join(base, path)
128
+ (Pathname.new(base) + path).to_s
129
+ end
130
+
105
131
  # Internal: Get relative path for root path and subpath.
106
132
  #
107
133
  # path - String path
@@ -218,9 +218,9 @@ module Sprockets
218
218
  end
219
219
  end
220
220
 
221
- def unregister_config_processor(type, mime_type, proccessor)
221
+ def unregister_config_processor(type, mime_type, processor)
222
222
  self.config = hash_reassoc(config, type, mime_type) do |processors|
223
- processors.delete(proccessor)
223
+ processors.delete_if { |p| p == processor || p.class == processor }
224
224
  processors
225
225
  end
226
226
  end
@@ -116,12 +116,10 @@ module Sprockets
116
116
  VALID_METADATA_VALUE_TYPES = Set.new([
117
117
  String,
118
118
  Symbol,
119
- Fixnum,
120
- Bignum,
121
119
  TrueClass,
122
120
  FalseClass,
123
121
  NilClass
124
- ]).freeze
122
+ ] + (0.class == Integer ? [Integer] : [Bignum, Fixnum])).freeze
125
123
 
126
124
  # Internal: Set of all nested compound metadata types that can nest values.
127
125
  VALID_METADATA_COMPOUND_TYPES = Set.new([
@@ -56,6 +56,9 @@ module Sprockets
56
56
 
57
57
  message << " with type '#{kargs[:accept]}'" if kargs[:accept]
58
58
 
59
+ load_paths = kargs[:load_paths] || config[:paths]
60
+ message << "\nChecked in these paths: \n #{ load_paths.join("\n ") }"
61
+
59
62
  raise FileNotFound, message
60
63
  end
61
64
 
@@ -100,11 +100,14 @@ module Sprockets
100
100
 
101
101
  private
102
102
 
103
+ def expand_source(source, env)
104
+ uri, _ = env.resolve!(source, pipeline: :source)
105
+ env.load(uri).digest_path
106
+ end
107
+
103
108
  def expand_map_sources(mapping, env)
104
109
  mapping.each do |map|
105
- uri, _ = env.resolve!(map[:source], pipeline: :source)
106
- source_path = env.load(uri).digest_path
107
- map[:source] = source_path
110
+ map[:source] = expand_source(map[:source], env)
108
111
  end
109
112
  end
110
113
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'sprockets/sass_processor'
3
+ require 'sprockets/path_utils'
3
4
  require 'base64'
4
5
 
5
6
  module Sprockets
@@ -22,39 +23,42 @@ module Sprockets
22
23
  options = engine_options(input, context)
23
24
  engine = Autoload::SassC::Engine.new(input[:data], options)
24
25
 
25
- data = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do
26
- engine.render
26
+ css = Utils.module_include(Autoload::SassC::Script::Functions, @functions) do
27
+ engine.render.sub(/^\n^\/\*# sourceMappingURL=.*\*\/$/m, '')
27
28
  end
28
29
 
29
- match_data = data.match(/(.*)\n\/\*# sourceMappingURL=data:application\/json;base64,(.+) \*\//m)
30
- css, map = match_data[1], Base64.decode64(match_data[2])
30
+ map = SourceMapUtils.decode_json_source_map(engine.source_map)
31
+ sources = map['sources'].map do |s|
32
+ expand_source(PathUtils.join(File.dirname(input[:filename]), s), input[:environment])
33
+ end
34
+
35
+ map = map["mappings"].each do |m|
36
+ m[:source] = PathUtils.join(File.dirname(input[:filename]), m[:source])
37
+ end
31
38
 
32
39
  map = SourceMapUtils.combine_source_maps(
33
40
  input[:metadata][:map],
34
- change_source(SourceMapUtils.decode_json_source_map(map)["mappings"], input[:source_path])
41
+ expand_map_sources(map, input[:environment])
35
42
  )
36
43
 
37
44
  engine.dependencies.each do |dependency|
38
45
  context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.filename)
39
46
  end
40
47
 
41
- context.metadata.merge(data: css, map: map)
48
+ context.metadata.merge(data: css, map: map, sources: sources)
42
49
  end
43
50
 
44
51
  private
45
52
 
46
- def change_source(mappings, source)
47
- mappings.each { |m| m[:source] = source }
48
- end
49
-
50
53
  def engine_options(input, context)
51
54
  merge_options({
52
55
  filename: input[:filename],
53
56
  syntax: self.class.syntax,
54
57
  load_paths: input[:environment].paths,
55
58
  importer: @importer_class,
56
- source_map_embed: true,
57
- source_map_file: '.',
59
+ source_map_contents: true,
60
+ source_map_file: "#{input[:filename]}.map",
61
+ omit_source_map_url: true,
58
62
  sprockets: {
59
63
  context: context,
60
64
  environment: input[:environment],
@@ -21,8 +21,10 @@ module Sprockets
21
21
  uri, _ = env.resolve!(input[:filename], accept: map_type)
22
22
  map = env.load(uri)
23
23
 
24
+ path = PathUtils.relative_path_from(input[:filename], map.full_digest_path)
25
+
24
26
  asset.metadata.merge(
25
- data: asset.source + (comment % map.digest_path),
27
+ data: asset.source + (comment % path),
26
28
  links: asset.links + [asset.uri, map.uri]
27
29
  )
28
30
  end
@@ -17,9 +17,10 @@ module Sprockets
17
17
 
18
18
  env = input[:environment]
19
19
 
20
- uri, _ = env.resolve!(input[:filename], accept: accept)
21
- asset = env.load(uri)
22
- map = asset.metadata[:map] || []
20
+ uri, _ = env.resolve!(input[:filename], accept: accept)
21
+ asset = env.load(uri)
22
+ map = asset.metadata[:map] || []
23
+ sources = asset.metadata[:sources]
23
24
 
24
25
  # TODO: Because of the default piplene hack we have to apply dependencies
25
26
  # from compiled asset to the source map, otherwise the source map cache
@@ -39,7 +40,7 @@ module Sprockets
39
40
  links << uri
40
41
  end
41
42
 
42
- json = env.encode_json_source_map(map, filename: asset.logical_path)
43
+ json = env.encode_json_source_map(map, sources: sources, filename: asset.logical_path)
43
44
 
44
45
  { data: json, links: links, dependencies: dependencies }
45
46
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'json'
3
+ require 'sprockets/path_utils'
3
4
 
4
5
  module Sprockets
5
6
  module SourceMapUtils
@@ -136,7 +137,11 @@ module Sprockets
136
137
  case mappings
137
138
  when String
138
139
  when Array
139
- sources ||= mappings.map { |m| m[:source] }.uniq.compact
140
+ mappings.each do |m|
141
+ m[:source] = PathUtils.relative_path_from(filename, m[:source])
142
+ end if filename
143
+ sources = sources.map { |s| PathUtils.relative_path_from(filename, s) } if filename && sources
144
+ sources = (Array(sources) + mappings.map { |m| m[:source] }).uniq.compact
140
145
  names ||= mappings.map { |m| m[:name] }.uniq.compact
141
146
  mappings = encode_vlq_mappings(mappings, sources: sources, names: names)
142
147
  else
@@ -2,11 +2,49 @@
2
2
  module Sprockets
3
3
  module Utils
4
4
  class Gzip
5
+ # Private: Generates a gzipped file based off of reference asset.
6
+ #
7
+ # ZlibArchiver.call(file, source, mtime)
8
+ #
9
+ # Compresses a given `source` using stdlib Zlib algorithm
10
+ # writes contents to the `file` passed in. Sets `mtime` of
11
+ # written file to passed in `mtime`
12
+ module ZlibArchiver
13
+ def self.call(file, source, mtime)
14
+ gz = Zlib::GzipWriter.new(file, Zlib::BEST_COMPRESSION)
15
+ gz.mtime = mtime
16
+ gz.write(source)
17
+ gz.close
18
+
19
+ File.utime(mtime, mtime, file.path)
20
+ end
21
+ end
22
+
23
+ # Private: Generates a gzipped file based off of reference asset.
24
+ #
25
+ # ZopfliArchiver.call(file, source, mtime)
26
+ #
27
+ # Compresses a given `source` using the zopfli gem
28
+ # writes contents to the `file` passed in. Sets `mtime` of
29
+ # written file to passed in `mtime`
30
+ module ZopfliArchiver
31
+ def self.call(file, source, mtime)
32
+ compressed_source = Autoload::Zopfli.deflate(source, format: :gzip, mtime: mtime)
33
+ file.write(compressed_source)
34
+ file.close
35
+
36
+ nil
37
+ end
38
+ end
39
+
40
+ attr_reader :content_type, :source, :charset, :archiver
41
+
5
42
  # Private: Generates a gzipped file based off of reference file.
6
- def initialize(asset)
43
+ def initialize(asset, archiver: ZlibArchiver)
7
44
  @content_type = asset.content_type
8
45
  @source = asset.source
9
46
  @charset = asset.charset
47
+ @archiver = archiver
10
48
  end
11
49
 
12
50
  # What non-text mime types should we compress? This list comes from:
@@ -27,7 +65,7 @@ module Sprockets
27
65
  # through a compression algorithm would make them larger.
28
66
  #
29
67
  # Return Boolean.
30
- def can_compress?(mime_types)
68
+ def can_compress?
31
69
  # The "charset" of a mime type is present if the value is
32
70
  # encoded text. We can check this value to see if the asset
33
71
  # can be compressed.
@@ -39,8 +77,8 @@ module Sprockets
39
77
  # Private: Opposite of `can_compress?`.
40
78
  #
41
79
  # Returns Boolean.
42
- def cannot_compress?(mime_types)
43
- !can_compress?(mime_types)
80
+ def cannot_compress?
81
+ !can_compress?
44
82
  end
45
83
 
46
84
  # Private: Generates a gzipped file based off of reference asset.
@@ -50,16 +88,9 @@ module Sprockets
50
88
  # Does not modify the target asset.
51
89
  #
52
90
  # Returns nothing.
53
- def compress(target)
54
- mtime = PathUtils.stat(target).mtime
55
- PathUtils.atomic_write("#{target}.gz") do |f|
56
- gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
57
- gz.mtime = mtime
58
- gz.write(@source)
59
- gz.close
60
-
61
- File.utime(mtime, mtime, f.path)
62
- end
91
+ def compress(file, target)
92
+ mtime = Sprockets::PathUtils.stat(target).mtime
93
+ archiver.call(file, source, mtime)
63
94
 
64
95
  nil
65
96
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sprockets
3
- VERSION = "4.0.0.beta3"
3
+ VERSION = "4.0.0.beta4"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sprockets
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.beta3
4
+ version: 4.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Stephenson
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-08-25 00:00:00.000000000 Z
12
+ date: 2016-10-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -231,16 +231,22 @@ dependencies:
231
231
  name: sassc
232
232
  requirement: !ruby/object:Gem::Requirement
233
233
  requirements:
234
- - - "~>"
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: 1.10.1
237
+ - - "<"
235
238
  - !ruby/object:Gem::Version
236
- version: '1.7'
239
+ version: '2.0'
237
240
  type: :development
238
241
  prerelease: false
239
242
  version_requirements: !ruby/object:Gem::Requirement
240
243
  requirements:
241
- - - "~>"
244
+ - - ">="
242
245
  - !ruby/object:Gem::Version
243
- version: '1.7'
246
+ version: 1.10.1
247
+ - - "<"
248
+ - !ruby/object:Gem::Version
249
+ version: '2.0'
244
250
  - !ruby/object:Gem::Dependency
245
251
  name: uglifier
246
252
  requirement: !ruby/object:Gem::Requirement
@@ -269,6 +275,20 @@ dependencies:
269
275
  - - "~>"
270
276
  - !ruby/object:Gem::Version
271
277
  version: '0.12'
278
+ - !ruby/object:Gem::Dependency
279
+ name: zopfli
280
+ requirement: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - "~>"
283
+ - !ruby/object:Gem::Version
284
+ version: 0.0.4
285
+ type: :development
286
+ prerelease: false
287
+ version_requirements: !ruby/object:Gem::Requirement
288
+ requirements:
289
+ - - "~>"
290
+ - !ruby/object:Gem::Version
291
+ version: 0.0.4
272
292
  description: Sprockets is a Rack-based asset packaging system that concatenates and
273
293
  serves JavaScript, CoffeeScript, CSS, Sass, and SCSS.
274
294
  email:
@@ -296,6 +316,7 @@ files:
296
316
  - lib/sprockets/autoload/sassc.rb
297
317
  - lib/sprockets/autoload/uglifier.rb
298
318
  - lib/sprockets/autoload/yui.rb
319
+ - lib/sprockets/autoload/zopfli.rb
299
320
  - lib/sprockets/babel_processor.rb
300
321
  - lib/sprockets/base.rb
301
322
  - lib/sprockets/bower.rb
@@ -319,6 +340,11 @@ files:
319
340
  - lib/sprockets/environment.rb
320
341
  - lib/sprockets/erb_processor.rb
321
342
  - lib/sprockets/errors.rb
343
+ - lib/sprockets/exporters/base.rb
344
+ - lib/sprockets/exporters/file_exporter.rb
345
+ - lib/sprockets/exporters/zlib_exporter.rb
346
+ - lib/sprockets/exporters/zopfli_exporter.rb
347
+ - lib/sprockets/exporting.rb
322
348
  - lib/sprockets/file_reader.rb
323
349
  - lib/sprockets/http_utils.rb
324
350
  - lib/sprockets/jsminc_compressor.rb