sprockets 4.0.0.beta3 → 4.0.0.beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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