turbo-sprockets-rails3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +36 -0
- data/Rakefile +25 -0
- data/lib/sprockets/asset_with_dependencies.rb +134 -0
- data/lib/sprockets/static_cleaner.rb +41 -0
- data/lib/sprockets/static_non_digest_generator.rb +99 -0
- data/lib/sprockets/unprocessed_asset.rb +28 -0
- data/lib/turbo-sprockets/railtie.rb +30 -0
- data/lib/turbo-sprockets/sprockets_overrides/asset.rb +29 -0
- data/lib/turbo-sprockets/sprockets_overrides/base.rb +37 -0
- data/lib/turbo-sprockets/sprockets_overrides/bundled_asset.rb +31 -0
- data/lib/turbo-sprockets/sprockets_overrides/environment.rb +19 -0
- data/lib/turbo-sprockets/sprockets_overrides/helpers/rails_helper.rb +42 -0
- data/lib/turbo-sprockets/sprockets_overrides/index.rb +28 -0
- data/lib/turbo-sprockets/sprockets_overrides/processed_asset.rb +30 -0
- data/lib/turbo-sprockets/sprockets_overrides/static_compiler.rb +68 -0
- data/lib/turbo-sprockets/tasks/assets.rake +128 -0
- data/lib/turbo-sprockets/version.rb +3 -0
- data/lib/turbo-sprockets-rails3.rb +5 -0
- metadata +96 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Nathan D. Broadbent
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Turbo Sprockets for Rails 3
|
2
|
+
|
3
|
+
* Speeds up the Rails 3 asset pipeline by only recompiling changed assets
|
4
|
+
* Generates non-digest assets from precompiled assets - Only compile once!
|
5
|
+
|
6
|
+
This is a backport of the work I've done for Rails 4.0.0, released as
|
7
|
+
a gem for Rails 3.2.x. (See [sprockets-rails #21](https://github.com/rails/sprockets-rails/pull/21) and [sprockets #367](https://github.com/sstephenson/sprockets/pull/367) for the Rails 4 pull requests.)
|
8
|
+
|
9
|
+
### Disclaimer
|
10
|
+
|
11
|
+
Please test this out thoroughly on your local machine before deploying to a production site, and open an issue on GitHub if you have any problems. By using this software you agree to the terms and conditions in the [MIT license](https://github.com/ndbroadbent/turbo-sprockets-rails3/blob/master/MIT-LICENSE).
|
12
|
+
|
13
|
+
### Dependencies
|
14
|
+
|
15
|
+
* sprockets `~> 2.1.3`
|
16
|
+
* railties `~> 3.2.0`
|
17
|
+
|
18
|
+
### Usage
|
19
|
+
|
20
|
+
Just drop the gem in your `Gemfile`, in the `:assets` group:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
group :assets do
|
24
|
+
...
|
25
|
+
|
26
|
+
gem 'turbo-sprockets-rails3'
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Run `bundle`, and you're done!
|
31
|
+
|
32
|
+
Test it out by running `rake assets:precompile`. When it's finished, your `public/assets/manifest.yml` file should include a `:source_digests` hash for your assets.
|
33
|
+
|
34
|
+
Go on, run `rake assets:precompile` again, and it should be a whole lot faster than before.
|
35
|
+
|
36
|
+
Enjoy your lightning fast deploys!
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'rake/testtask'
|
3
|
+
ENV["TEST_CORES"] = "1"
|
4
|
+
|
5
|
+
namespace :test do
|
6
|
+
task :isolated do
|
7
|
+
Dir["test/assets*_test.rb"].each do |file|
|
8
|
+
dash_i = [
|
9
|
+
'test',
|
10
|
+
'lib',
|
11
|
+
]
|
12
|
+
ruby "-I#{dash_i.join ':'}", file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Rake::TestTask.new("test:regular") do |t|
|
18
|
+
t.libs << 'lib'
|
19
|
+
t.libs << 'test'
|
20
|
+
t.pattern = 'test/**/sprockets*_test.rb'
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
task :test => ['test:isolated', 'test:regular']
|
25
|
+
task :default => :test
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'sprockets/asset'
|
2
|
+
require 'sprockets/utils'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
# `AssetWithDependencies` is the base class for `ProcessedAsset` and `UnprocessedAsset`.
|
6
|
+
class AssetWithDependencies < Asset
|
7
|
+
|
8
|
+
# :dependency_digest is used internally to check equality
|
9
|
+
attr_reader :dependency_digest, :source
|
10
|
+
|
11
|
+
|
12
|
+
# Initialize asset from serialized hash
|
13
|
+
def init_with(environment, coder, asset_options = {})
|
14
|
+
asset_options[:bundle] = false
|
15
|
+
|
16
|
+
super(environment, coder)
|
17
|
+
|
18
|
+
@source = coder['source']
|
19
|
+
@dependency_digest = coder['dependency_digest']
|
20
|
+
|
21
|
+
@required_assets = coder['required_paths'].map { |p|
|
22
|
+
p = expand_root_path(p)
|
23
|
+
|
24
|
+
unless environment.paths.detect { |path| p[path] }
|
25
|
+
raise UnserializeError, "#{p} isn't in paths"
|
26
|
+
end
|
27
|
+
|
28
|
+
p == pathname.to_s ? self : environment.find_asset(p, asset_options)
|
29
|
+
}
|
30
|
+
@dependency_paths = coder['dependency_paths'].map { |h|
|
31
|
+
DependencyFile.new(expand_root_path(h['path']), h['mtime'], h['digest'])
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Serialize custom attributes.
|
36
|
+
def encode_with(coder)
|
37
|
+
super
|
38
|
+
|
39
|
+
coder['source'] = source
|
40
|
+
coder['dependency_digest'] = dependency_digest
|
41
|
+
|
42
|
+
coder['required_paths'] = required_assets.map { |a|
|
43
|
+
relativize_root_path(a.pathname).to_s
|
44
|
+
}
|
45
|
+
coder['dependency_paths'] = dependency_paths.map { |d|
|
46
|
+
{ 'path' => relativize_root_path(d.pathname).to_s,
|
47
|
+
'mtime' => d.mtime.iso8601,
|
48
|
+
'digest' => d.digest }
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Checks if Asset is stale by comparing the actual mtime and
|
53
|
+
# digest to the inmemory model.
|
54
|
+
def fresh?(environment)
|
55
|
+
# Check freshness of all declared dependencies
|
56
|
+
@dependency_paths.all? { |dep| dependency_fresh?(environment, dep) }
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
class DependencyFile < Struct.new(:pathname, :mtime, :digest)
|
61
|
+
def initialize(pathname, mtime, digest)
|
62
|
+
pathname = Pathname.new(pathname) unless pathname.is_a?(Pathname)
|
63
|
+
mtime = Time.parse(mtime) if mtime.is_a?(String)
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def eql?(other)
|
68
|
+
other.is_a?(DependencyFile) &&
|
69
|
+
pathname.eql?(other.pathname) &&
|
70
|
+
mtime.eql?(other.mtime) &&
|
71
|
+
digest.eql?(other.digest)
|
72
|
+
end
|
73
|
+
|
74
|
+
def hash
|
75
|
+
pathname.to_s.hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def build_required_assets(environment, context, asset_options = {})
|
81
|
+
asset_options[:bundle] = false
|
82
|
+
@required_assets = []
|
83
|
+
required_assets_cache = {}
|
84
|
+
|
85
|
+
(context._required_paths + [pathname.to_s]).each do |path|
|
86
|
+
if path == self.pathname.to_s
|
87
|
+
unless required_assets_cache[self]
|
88
|
+
required_assets_cache[self] = true
|
89
|
+
@required_assets << self
|
90
|
+
end
|
91
|
+
elsif asset = environment.find_asset(path, asset_options)
|
92
|
+
asset.required_assets.each do |asset_dependency|
|
93
|
+
unless required_assets_cache[asset_dependency]
|
94
|
+
required_assets_cache[asset_dependency] = true
|
95
|
+
@required_assets << asset_dependency
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
required_assets_cache.clear
|
102
|
+
required_assets_cache = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_dependency_paths(environment, context, asset_options = {})
|
106
|
+
asset_options[:bundle] = false
|
107
|
+
dependency_paths = {}
|
108
|
+
|
109
|
+
context._dependency_paths.each do |path|
|
110
|
+
dep = DependencyFile.new(path, environment.stat(path).mtime, environment.file_digest(path).hexdigest)
|
111
|
+
dependency_paths[dep] = true
|
112
|
+
end
|
113
|
+
|
114
|
+
context._dependency_assets.each do |path|
|
115
|
+
if path == self.pathname.to_s
|
116
|
+
dep = DependencyFile.new(pathname, environment.stat(path).mtime, environment.file_digest(path).hexdigest)
|
117
|
+
dependency_paths[dep] = true
|
118
|
+
elsif asset = environment.find_asset(path, asset_options)
|
119
|
+
asset.dependency_paths.each do |d|
|
120
|
+
dependency_paths[d] = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
@dependency_paths = dependency_paths.keys
|
126
|
+
end
|
127
|
+
|
128
|
+
def compute_dependency_digest(environment)
|
129
|
+
required_assets.inject(environment.digest) { |digest, asset|
|
130
|
+
digest.update asset.digest
|
131
|
+
}.hexdigest
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class StaticCleaner
|
5
|
+
attr_accessor :env, :target
|
6
|
+
|
7
|
+
def initialize(env, target, digest_files)
|
8
|
+
@env = env
|
9
|
+
@target = File.join(target, '') # Make sure target ends with trailing /
|
10
|
+
@digest_files = digest_files
|
11
|
+
end
|
12
|
+
|
13
|
+
# Remove all files from `config.assets.prefix` that are not found in manifest.yml
|
14
|
+
def remove_old_assets!
|
15
|
+
known_files = @digest_files.flatten
|
16
|
+
known_files += known_files.map {|f| "#{f}.gz" } # Recognize gzipped files
|
17
|
+
known_files << 'manifest.yml'
|
18
|
+
|
19
|
+
assets_prefix = ::Rails.application.config.assets.prefix
|
20
|
+
|
21
|
+
Dir[File.join(target, "**/*")].each do |path|
|
22
|
+
unless File.directory?(path)
|
23
|
+
logical_path = path.sub(target, '')
|
24
|
+
unless logical_path.in? known_files
|
25
|
+
FileUtils.rm path
|
26
|
+
env.logger.debug "Deleted old asset at public#{assets_prefix}/#{logical_path}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Remove empty directories (reversed to delete top-level empty dirs first)
|
32
|
+
Dir[File.join(target, "**/*")].reverse.each do |path|
|
33
|
+
if File.exists?(path) && File.directory?(path) && (Dir.entries(path) - %w(. ..)).empty?
|
34
|
+
FileUtils.rmdir path
|
35
|
+
logical_path = path.sub(target, '')
|
36
|
+
env.logger.debug "Deleted empty directory at public#{assets_prefix}/#{logical_path}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class StaticNonDigestGenerator
|
5
|
+
|
6
|
+
DIGEST_REGEX = /-([0-9a-f]{32})/
|
7
|
+
|
8
|
+
attr_accessor :env, :target, :paths
|
9
|
+
|
10
|
+
def initialize(env, target, paths, options = {})
|
11
|
+
@env = env
|
12
|
+
@target = target
|
13
|
+
@paths = paths
|
14
|
+
@digest_files = options.fetch(:digest_files, {})
|
15
|
+
|
16
|
+
# Parse digests from digest_files hash
|
17
|
+
@asset_digests = Hash[*@digest_files.map {|file, digest_file|
|
18
|
+
[file, digest_file[DIGEST_REGEX, 1]]
|
19
|
+
}.flatten]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Generate non-digest assets by making a copy of the digest asset,
|
24
|
+
# with digests stripped from js and css. The new files are also gzipped.
|
25
|
+
# Other assets are copied verbatim.
|
26
|
+
def generate
|
27
|
+
start_time = Time.now.to_f
|
28
|
+
|
29
|
+
env.each_logical_path do |logical_path|
|
30
|
+
next unless compile_path?(logical_path)
|
31
|
+
|
32
|
+
digest_path = @digest_files[logical_path]
|
33
|
+
abs_digest_path = "#{@target}/#{digest_path}"
|
34
|
+
abs_logical_path = "#{@target}/#{logical_path}"
|
35
|
+
|
36
|
+
mtime = File.mtime(abs_digest_path)
|
37
|
+
|
38
|
+
# Remove known digests from css & js
|
39
|
+
if abs_digest_path.match(/\.(?:js|css)$/)
|
40
|
+
asset_body = File.read(abs_digest_path)
|
41
|
+
|
42
|
+
# Find all hashes in the asset body with a leading '-'
|
43
|
+
asset_body.gsub!(DIGEST_REGEX) do |match|
|
44
|
+
# Only remove if known digest
|
45
|
+
$1.in?(@asset_digests.values) ? '' : match
|
46
|
+
end
|
47
|
+
|
48
|
+
# Write non-digest file
|
49
|
+
File.open abs_logical_path, 'w' do |f|
|
50
|
+
f.write asset_body
|
51
|
+
end
|
52
|
+
# Set modification and access times
|
53
|
+
File.utime(File.atime(abs_digest_path), mtime, abs_logical_path)
|
54
|
+
|
55
|
+
# Also write gzipped asset
|
56
|
+
File.open("#{abs_logical_path}.gz", 'wb') do |f|
|
57
|
+
gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
|
58
|
+
gz.mtime = mtime.to_i
|
59
|
+
gz.write asset_body
|
60
|
+
gz.close
|
61
|
+
end
|
62
|
+
|
63
|
+
env.logger.debug "Stripped digests, copied to #{logical_path}, and created gzipped asset"
|
64
|
+
|
65
|
+
else
|
66
|
+
# Otherwise, treat file as binary and copy it
|
67
|
+
FileUtils.cp_r abs_digest_path, abs_logical_path, :remove_destination => true
|
68
|
+
env.logger.debug "Copied binary asset to #{logical_path}"
|
69
|
+
|
70
|
+
# Copy gzipped asset if exists
|
71
|
+
if File.exist? "#{abs_digest_path}.gz"
|
72
|
+
FileUtils.cp_r "#{abs_digest_path}.gz", "#{abs_logical_path}.gz", :remove_destination => true
|
73
|
+
env.logger.debug "Copied gzipped asset to #{logical_path}.gz"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
elapsed_time = ((Time.now.to_f - start_time) * 1000).to_i
|
80
|
+
env.logger.debug "Generated non-digest assets in #{elapsed_time}ms"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def compile_path?(logical_path)
|
86
|
+
paths.each do |path|
|
87
|
+
case path
|
88
|
+
when Regexp
|
89
|
+
return true if path.match(logical_path)
|
90
|
+
when Proc
|
91
|
+
return true if path.call(logical_path)
|
92
|
+
else
|
93
|
+
return true if File.fnmatch(path.to_s, logical_path)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'sprockets/asset_with_dependencies'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class UnprocessedAsset < AssetWithDependencies
|
5
|
+
def initialize(environment, logical_path, pathname)
|
6
|
+
super
|
7
|
+
|
8
|
+
context = environment.context_class.new(environment, logical_path, pathname)
|
9
|
+
attributes = environment.attributes_for(pathname)
|
10
|
+
processors = attributes.processors
|
11
|
+
|
12
|
+
# Remove all engine processors except ERB to return unprocessed source file
|
13
|
+
processors -= (attributes.engines - [Tilt::ERBTemplate])
|
14
|
+
|
15
|
+
@source = context.evaluate(pathname, :processors => processors)
|
16
|
+
|
17
|
+
build_required_assets(environment, context, :process => false)
|
18
|
+
build_dependency_paths(environment, context, :process => false)
|
19
|
+
|
20
|
+
@dependency_digest = compute_dependency_digest(environment)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return unprocessed dependencies when initializing asset from serialized hash
|
25
|
+
def init_with(environment, coder)
|
26
|
+
super(environment, coder, :process => false)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "action_controller/railtie"
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
autoload :StaticCleaner, "sprockets/static_cleaner"
|
5
|
+
autoload :StaticNonDigestGenerator, "sprockets/static_non_digest_generator"
|
6
|
+
end
|
7
|
+
|
8
|
+
module TurboSprockets
|
9
|
+
class Railtie < ::Rails::Railtie
|
10
|
+
rake_tasks do
|
11
|
+
load "turbo-sprockets/tasks/assets.rake"
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer "turbo-sprockets.environment", :after => "sprockets.environment", :group => :all do |app|
|
15
|
+
config = app.config
|
16
|
+
|
17
|
+
if config.assets.manifest
|
18
|
+
manifest_path = File.join(config.assets.manifest, "manifest.yml")
|
19
|
+
else
|
20
|
+
manifest_path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
|
21
|
+
end
|
22
|
+
|
23
|
+
if File.exist?(manifest_path)
|
24
|
+
manifest = YAML.load_file(manifest_path)
|
25
|
+
config.assets.digest_files = manifest[:digest_files] || {}
|
26
|
+
config.assets.source_digests = manifest[:source_digests] || {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'sprockets/asset'
|
2
|
+
|
3
|
+
Sprockets::Asset.class_eval do
|
4
|
+
# Internal initializer to load `Asset` from serialized `Hash`.
|
5
|
+
def self.from_hash(environment, hash)
|
6
|
+
return unless hash.is_a?(Hash)
|
7
|
+
|
8
|
+
klass = case hash['class']
|
9
|
+
when 'BundledAsset'
|
10
|
+
BundledAsset
|
11
|
+
when 'ProcessedAsset'
|
12
|
+
ProcessedAsset
|
13
|
+
when 'UnprocessedAsset'
|
14
|
+
UnprocessedAsset
|
15
|
+
when 'StaticAsset'
|
16
|
+
StaticAsset
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
if klass
|
22
|
+
asset = klass.allocate
|
23
|
+
asset.init_with(environment, hash)
|
24
|
+
asset
|
25
|
+
end
|
26
|
+
rescue UnserializeError
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'sprockets/base'
|
2
|
+
require 'sprockets/unprocessed_asset'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
Base.class_eval do
|
6
|
+
protected
|
7
|
+
|
8
|
+
def build_asset(logical_path, pathname, options)
|
9
|
+
pathname = Pathname.new(pathname)
|
10
|
+
|
11
|
+
# If there are any processors to run on the pathname, use
|
12
|
+
# `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.
|
13
|
+
if attributes_for(pathname).processors.any?
|
14
|
+
if options[:bundle] == false
|
15
|
+
circular_call_protection(pathname.to_s) do
|
16
|
+
if options[:process] == false
|
17
|
+
UnprocessedAsset.new(index, logical_path, pathname)
|
18
|
+
else
|
19
|
+
ProcessedAsset.new(index, logical_path, pathname)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
BundledAsset.new(index, logical_path, pathname, options)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
StaticAsset.new(index, logical_path, pathname)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def cache_key_for(path, options)
|
33
|
+
options[:process] = true unless options.key?(:process)
|
34
|
+
"#{path}:#{options[:bundle] ? '1' : '0'}:#{options[:process] ? '1' : '0'}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'sprockets/bundled_asset'
|
2
|
+
|
3
|
+
Sprockets::BundledAsset.class_eval do
|
4
|
+
|
5
|
+
# Adds :process options
|
6
|
+
|
7
|
+
def initialize(environment, logical_path, pathname, options = {})
|
8
|
+
super(environment, logical_path, pathname)
|
9
|
+
@process = options.fetch(:process, true)
|
10
|
+
|
11
|
+
@processed_asset = environment.find_asset(pathname, :bundle => false, :process => @process)
|
12
|
+
@required_assets = @processed_asset.required_assets
|
13
|
+
@dependency_paths = @processed_asset.dependency_paths
|
14
|
+
|
15
|
+
@source = ""
|
16
|
+
|
17
|
+
# Explode Asset into parts and gather the dependency bodies
|
18
|
+
to_a.each { |dependency| @source << dependency.to_s }
|
19
|
+
|
20
|
+
if @process
|
21
|
+
# Run bundle processors on concatenated source
|
22
|
+
context = environment.context_class.new(environment, logical_path, pathname)
|
23
|
+
@source = context.evaluate(pathname, :data => @source,
|
24
|
+
:processors => environment.bundle_processors(content_type))
|
25
|
+
end
|
26
|
+
|
27
|
+
@mtime = to_a.map(&:mtime).max
|
28
|
+
@length = Rack::Utils.bytesize(source)
|
29
|
+
@digest = environment.digest.update(source).hexdigest
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'sprockets/environment'
|
2
|
+
|
3
|
+
Sprockets::Environment.class_eval do
|
4
|
+
|
5
|
+
# Adds :process options
|
6
|
+
|
7
|
+
def find_asset(path, options = {})
|
8
|
+
options[:bundle] = true unless options.key?(:bundle)
|
9
|
+
options[:process] = true unless options.key?(:process)
|
10
|
+
|
11
|
+
# Ensure in-memory cached assets are still fresh on every lookup
|
12
|
+
if (asset = @assets[cache_key_for(path, options)]) && asset.fresh?(self)
|
13
|
+
asset
|
14
|
+
elsif asset = index.find_asset(path, options)
|
15
|
+
# Cache is pushed upstream by Index#find_asset
|
16
|
+
asset
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sprockets/helpers/rails_helper'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
module Helpers
|
5
|
+
RailsHelper.module_eval do
|
6
|
+
def asset_paths
|
7
|
+
@asset_paths ||= begin
|
8
|
+
paths = RailsHelper::AssetPaths.new(config, controller)
|
9
|
+
paths.asset_environment = asset_environment
|
10
|
+
paths.digest_files = digest_files
|
11
|
+
paths.compile_assets = compile_assets?
|
12
|
+
paths.digest_assets = digest_assets?
|
13
|
+
paths
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def digest_files
|
19
|
+
Rails.application.config.assets.digest_files
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RailsHelper::AssetPaths.class_eval do
|
24
|
+
attr_accessor :digest_files
|
25
|
+
|
26
|
+
def digest_for(logical_path)
|
27
|
+
if digest_assets && digest_files && (digest = digest_files[logical_path])
|
28
|
+
return digest
|
29
|
+
end
|
30
|
+
|
31
|
+
if compile_assets
|
32
|
+
if digest_assets && asset = asset_environment[logical_path]
|
33
|
+
return asset.digest_path
|
34
|
+
end
|
35
|
+
return logical_path
|
36
|
+
else
|
37
|
+
raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'sprockets/index'
|
2
|
+
|
3
|
+
Sprockets::Index.class_eval do
|
4
|
+
|
5
|
+
# Adds :process options
|
6
|
+
|
7
|
+
def find_asset(path, options = {})
|
8
|
+
options[:bundle] = true unless options.key?(:bundle)
|
9
|
+
options[:process] = true unless options.key?(:process)
|
10
|
+
|
11
|
+
if asset = @assets[cache_key_for(path, options)]
|
12
|
+
asset
|
13
|
+
elsif asset = super
|
14
|
+
logical_path_cache_key = cache_key_for(path, options)
|
15
|
+
full_path_cache_key = cache_key_for(asset.pathname, options)
|
16
|
+
|
17
|
+
# Cache on Index
|
18
|
+
@assets[logical_path_cache_key] = @assets[full_path_cache_key] = asset
|
19
|
+
|
20
|
+
# Push cache upstream to Environment
|
21
|
+
@environment.instance_eval do
|
22
|
+
@assets[logical_path_cache_key] = @assets[full_path_cache_key] = asset
|
23
|
+
end
|
24
|
+
|
25
|
+
asset
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sprockets/processed_asset'
|
2
|
+
require 'sprockets/asset_with_dependencies'
|
3
|
+
|
4
|
+
module Sprockets
|
5
|
+
|
6
|
+
# Remove and redefine ProcessedAsset to inherit from AssetWithDependencies
|
7
|
+
|
8
|
+
remove_const :ProcessedAsset
|
9
|
+
|
10
|
+
class ProcessedAsset < AssetWithDependencies
|
11
|
+
def initialize(environment, logical_path, pathname)
|
12
|
+
super
|
13
|
+
|
14
|
+
start_time = Time.now.to_f
|
15
|
+
|
16
|
+
context = environment.context_class.new(environment, logical_path, pathname)
|
17
|
+
@source = context.evaluate(pathname)
|
18
|
+
@length = Rack::Utils.bytesize(source)
|
19
|
+
@digest = environment.digest.update(source).hexdigest
|
20
|
+
|
21
|
+
build_required_assets(environment, context)
|
22
|
+
build_dependency_paths(environment, context)
|
23
|
+
|
24
|
+
@dependency_digest = compute_dependency_digest(environment)
|
25
|
+
|
26
|
+
elapsed_time = ((Time.now.to_f - start_time) * 1000).to_i
|
27
|
+
environment.logger.info "Compiled #{logical_path} (#{elapsed_time}ms) (pid #{Process.pid})"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'sprockets/static_compiler'
|
2
|
+
|
3
|
+
Sprockets::StaticCompiler.class_eval do
|
4
|
+
def initialize(env, target, paths, options = {})
|
5
|
+
@env = env
|
6
|
+
@target = target
|
7
|
+
@paths = paths
|
8
|
+
@digest = options.fetch(:digest, true)
|
9
|
+
@manifest = options.fetch(:manifest, true)
|
10
|
+
@manifest_path = options.delete(:manifest_path) || target
|
11
|
+
@zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/
|
12
|
+
|
13
|
+
@current_source_digests = options.fetch(:source_digests, {})
|
14
|
+
@current_digest_files = options.fetch(:digest_files, {})
|
15
|
+
|
16
|
+
@digest_files = {}
|
17
|
+
@source_digests = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
start_time = Time.now.to_f
|
22
|
+
|
23
|
+
env.each_logical_path do |logical_path|
|
24
|
+
if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
|
25
|
+
logical_path.sub!(/\/index\./, '.')
|
26
|
+
end
|
27
|
+
next unless compile_path?(logical_path)
|
28
|
+
|
29
|
+
# Fetch asset without any processing or compression,
|
30
|
+
# to calculate a digest of the concatenated source files
|
31
|
+
asset = env.find_asset(logical_path, :process => false)
|
32
|
+
|
33
|
+
# Force digest to UTF-8, otherwise YAML dumps ASCII-8BIT as !binary
|
34
|
+
@source_digests[logical_path] = asset.digest.force_encoding("UTF-8")
|
35
|
+
|
36
|
+
# Recompile if digest has changed or compiled digest file is missing
|
37
|
+
current_digest_file = @current_digest_files[logical_path]
|
38
|
+
|
39
|
+
if @source_digests[logical_path] != @current_source_digests[logical_path] ||
|
40
|
+
!(current_digest_file && File.exists?("#{@target}/#{current_digest_file}"))
|
41
|
+
|
42
|
+
if asset = env.find_asset(logical_path)
|
43
|
+
@digest_files[logical_path] = write_asset(asset)
|
44
|
+
end
|
45
|
+
|
46
|
+
else
|
47
|
+
# Set asset file from manifest.yml
|
48
|
+
digest_file = @current_digest_files[logical_path]
|
49
|
+
@digest_files[logical_path] = digest_file
|
50
|
+
|
51
|
+
env.logger.debug "Not compiling #{logical_path}, sources digest has not changed " <<
|
52
|
+
"(#{@source_digests[logical_path][0...7]})"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if @manifest
|
57
|
+
write_manifest(source_digests: @source_digests, digest_files: @digest_files)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Store digests in Rails config. (Important if non-digest is run after primary)
|
61
|
+
config = ::Rails.application.config
|
62
|
+
config.assets.digest_files = @digest_files
|
63
|
+
config.assets.source_digests = @source_digests
|
64
|
+
|
65
|
+
elapsed_time = ((Time.now.to_f - start_time) * 1000).to_i
|
66
|
+
env.logger.debug "Processed #{'non-' unless @digest}digest assets in #{elapsed_time}ms"
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
# Clear all assets tasks from sprockets railtie
|
4
|
+
Rake::Task.tasks.each do |task|
|
5
|
+
if task.name.starts_with? 'assets:'
|
6
|
+
task.clear_prerequisites.clear_actions
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Replace with our extended assets tasks
|
11
|
+
namespace :assets do
|
12
|
+
def ruby_rake_task(task, fork = true)
|
13
|
+
env = ENV['RAILS_ENV'] || 'production'
|
14
|
+
groups = ENV['RAILS_GROUPS'] || 'assets'
|
15
|
+
args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
|
16
|
+
args << "--trace" if Rake.application.options.trace
|
17
|
+
if $0 =~ /rake\.bat\Z/i
|
18
|
+
Kernel.exec $0, *args
|
19
|
+
else
|
20
|
+
fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# We are currently running with no explicit bundler group
|
25
|
+
# and/or no explicit environment - we have to reinvoke rake to
|
26
|
+
# execute this task.
|
27
|
+
def invoke_or_reboot_rake_task(task)
|
28
|
+
if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty?
|
29
|
+
ruby_rake_task task
|
30
|
+
else
|
31
|
+
Rake::Task[task].invoke
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Compile all the assets named in config.assets.precompile"
|
36
|
+
task :precompile do
|
37
|
+
invoke_or_reboot_rake_task "assets:precompile:all"
|
38
|
+
end
|
39
|
+
|
40
|
+
namespace :precompile do
|
41
|
+
def internal_precompile(digest=nil)
|
42
|
+
unless Rails.application.config.assets.enabled
|
43
|
+
warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true"
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
|
47
|
+
# Ensure that action view is loaded and the appropriate
|
48
|
+
# sprockets hooks get executed
|
49
|
+
_ = ActionView::Base
|
50
|
+
|
51
|
+
config = Rails.application.config
|
52
|
+
config.assets.compile = true
|
53
|
+
config.assets.digest = digest unless digest.nil?
|
54
|
+
config.assets.digest_files ||= {}
|
55
|
+
config.assets.source_digests ||= {}
|
56
|
+
|
57
|
+
env = Rails.application.assets
|
58
|
+
target = File.join(::Rails.public_path, config.assets.prefix)
|
59
|
+
|
60
|
+
# If processing non-digest assets, and compiled digest files are
|
61
|
+
# present, then generate non-digest assets from existing assets.
|
62
|
+
# It is assumed that `assets:precompile:nondigest` won't be run manually
|
63
|
+
# if assets have been previously compiled with digests.
|
64
|
+
if !config.assets.digest && config.assets.digest_files.any?
|
65
|
+
generator = Sprockets::StaticNonDigestGenerator.new(env, target, config.assets.precompile,
|
66
|
+
:digest_files => config.assets.digest_files,
|
67
|
+
:source_digests => config.assets.source_digests
|
68
|
+
)
|
69
|
+
generator.generate
|
70
|
+
else
|
71
|
+
compiler = Sprockets::StaticCompiler.new(env, target, config.assets.precompile,
|
72
|
+
:digest => config.assets.digest,
|
73
|
+
:manifest => digest.nil?,
|
74
|
+
:manifest_path => config.assets.manifest,
|
75
|
+
:digest_files => config.assets.digest_files,
|
76
|
+
:source_digests => config.assets.source_digests
|
77
|
+
)
|
78
|
+
compiler.compile
|
79
|
+
end
|
80
|
+
|
81
|
+
unless config.assets.clean_after_precompile == false
|
82
|
+
cleaner = Sprockets::StaticCleaner.new(env, target, config.assets.digest_files)
|
83
|
+
cleaner.remove_old_assets!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
task :all => ["assets:cache:clean"] do
|
88
|
+
internal_precompile
|
89
|
+
internal_precompile(false) if ::Rails.application.config.assets.digest
|
90
|
+
end
|
91
|
+
|
92
|
+
task :primary => ["assets:cache:clean"] do
|
93
|
+
internal_precompile
|
94
|
+
end
|
95
|
+
|
96
|
+
task :nondigest => ["assets:cache:clean"] do
|
97
|
+
internal_precompile(false)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "Remove compiled assets"
|
102
|
+
task :clean do
|
103
|
+
invoke_or_reboot_rake_task "assets:clean:all"
|
104
|
+
end
|
105
|
+
|
106
|
+
namespace :clean do
|
107
|
+
task :all => ["assets:cache:clean"] do
|
108
|
+
config = ::Rails.application.config
|
109
|
+
public_asset_path = File.join(::Rails.public_path, config.assets.prefix)
|
110
|
+
rm_rf public_asset_path, :secure => true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
namespace :cache do
|
115
|
+
task :clean => ['tmp:create', "assets:environment"] do
|
116
|
+
::Rails.application.assets.cache.clear
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
task :environment do
|
121
|
+
if ::Rails.application.config.assets.initialize_on_precompile
|
122
|
+
Rake::Task["environment"].invoke
|
123
|
+
else
|
124
|
+
::Rails.application.initialize!(:assets)
|
125
|
+
Sprockets::Bootstrap.new(Rails.application).run
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: turbo-sprockets-rails3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathan Broadbent
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sprockets
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.1.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.1.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: railties
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.2.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 3.2.0
|
46
|
+
description: Speeds up the Rails 3 asset pipeline by only recompiling changed assets
|
47
|
+
email:
|
48
|
+
- nathan.f77@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/turbo-sprockets-rails3.rb
|
54
|
+
- lib/turbo-sprockets/version.rb
|
55
|
+
- lib/turbo-sprockets/tasks/assets.rake
|
56
|
+
- lib/turbo-sprockets/railtie.rb
|
57
|
+
- lib/turbo-sprockets/sprockets_overrides/helpers/rails_helper.rb
|
58
|
+
- lib/turbo-sprockets/sprockets_overrides/static_compiler.rb
|
59
|
+
- lib/turbo-sprockets/sprockets_overrides/base.rb
|
60
|
+
- lib/turbo-sprockets/sprockets_overrides/bundled_asset.rb
|
61
|
+
- lib/turbo-sprockets/sprockets_overrides/index.rb
|
62
|
+
- lib/turbo-sprockets/sprockets_overrides/asset.rb
|
63
|
+
- lib/turbo-sprockets/sprockets_overrides/environment.rb
|
64
|
+
- lib/turbo-sprockets/sprockets_overrides/processed_asset.rb
|
65
|
+
- lib/sprockets/static_non_digest_generator.rb
|
66
|
+
- lib/sprockets/asset_with_dependencies.rb
|
67
|
+
- lib/sprockets/static_cleaner.rb
|
68
|
+
- lib/sprockets/unprocessed_asset.rb
|
69
|
+
- MIT-LICENSE
|
70
|
+
- Rakefile
|
71
|
+
- README.md
|
72
|
+
homepage: https://github.com/ndbroadbent/turbo-sprockets-rails3
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.24
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Supercharge your Rails 3 asset pipeline
|
96
|
+
test_files: []
|