turbo-sprockets-rails3-envaware 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +117 -0
  4. data/Rakefile +28 -0
  5. data/lib/sprockets/asset_with_dependencies.rb +134 -0
  6. data/lib/sprockets/static_non_digest_generator.rb +106 -0
  7. data/lib/sprockets/unprocessed_asset.rb +39 -0
  8. data/lib/turbo-sprockets-rails3.rb +15 -0
  9. data/lib/turbo-sprockets/helpers.rb +9 -0
  10. data/lib/turbo-sprockets/railtie.rb +26 -0
  11. data/lib/turbo-sprockets/sprockets_overrides/asset.rb +31 -0
  12. data/lib/turbo-sprockets/sprockets_overrides/base.rb +38 -0
  13. data/lib/turbo-sprockets/sprockets_overrides/bundled_asset.rb +33 -0
  14. data/lib/turbo-sprockets/sprockets_overrides/environment.rb +21 -0
  15. data/lib/turbo-sprockets/sprockets_overrides/index.rb +30 -0
  16. data/lib/turbo-sprockets/sprockets_overrides/processed_asset.rb +29 -0
  17. data/lib/turbo-sprockets/sprockets_overrides/static_compiler.rb +94 -0
  18. data/lib/turbo-sprockets/tasks/assets.rake +204 -0
  19. data/lib/turbo-sprockets/version.rb +3 -0
  20. data/test/abstract_unit.rb +143 -0
  21. data/test/assets_debugging_test.rb +65 -0
  22. data/test/assets_test.rb +542 -0
  23. data/test/fixtures/alternate/stylesheets/style.css +1 -0
  24. data/test/fixtures/app/fonts/dir/font.ttf +0 -0
  25. data/test/fixtures/app/fonts/font.ttf +0 -0
  26. data/test/fixtures/app/images/logo.png +0 -0
  27. data/test/fixtures/app/javascripts/application.js +1 -0
  28. data/test/fixtures/app/javascripts/dir/xmlhr.js +0 -0
  29. data/test/fixtures/app/javascripts/extra.js +0 -0
  30. data/test/fixtures/app/javascripts/foo.min.js +0 -0
  31. data/test/fixtures/app/javascripts/xmlhr.js +0 -0
  32. data/test/fixtures/app/stylesheets/application.css +1 -0
  33. data/test/fixtures/app/stylesheets/dir/style.css +0 -0
  34. data/test/fixtures/app/stylesheets/extra.css +0 -0
  35. data/test/fixtures/app/stylesheets/style.css +0 -0
  36. data/test/fixtures/app/stylesheets/style.ext +0 -0
  37. data/test/fixtures/app/stylesheets/style.min.css +0 -0
  38. data/test/sprockets_helper_test.rb +363 -0
  39. data/test/sprockets_helper_with_routes_test.rb +57 -0
  40. data/test/sprockets_helpers_abstract_unit.rb +358 -0
  41. metadata +139 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 976e13e4c0895d50de8bcba8cae88e4a22bd18c8
4
+ data.tar.gz: 79b97c9d2816c9ed57b949d239a861888699975e
5
+ SHA512:
6
+ metadata.gz: d00789a4955348cb975af9a9af1cbe60ab72f74d3c1481a485e8d7d0d242df442722909d97f8bc76c1fa29767c2f6db89d75991279515947517e4b9faf3c832f
7
+ data.tar.gz: 25dc178a73ff9ed5c7965ffa1defc45ce14b41c3ae6e4b9873af0da5b55d4207bc51c56a770e45467c02c37f7b43c22c5b1fce2a218fbcb5fca24bffd776a88a
@@ -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.
@@ -0,0 +1,117 @@
1
+ # Turbo Sprockets for Rails 3.2.x
2
+
3
+ [![Build Status](https://secure.travis-ci.org/ndbroadbent/turbo-sprockets-rails3.png)](http://travis-ci.org/ndbroadbent/turbo-sprockets-rails3)
4
+
5
+ * Speeds up your Rails 3 `rake assets:precompile` by only recompiling changed assets, based on a hash of their source files
6
+ * Only compiles once to generate both fingerprinted and non-fingerprinted assets
7
+
8
+
9
+ ### Disclaimer
10
+
11
+ `turbo-sprockets-rails3` can now be considered relatively stable. A lot of compatibility issues and bugs have been solved, so you shouldn't run into any problems.
12
+ However, please do test it out 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).
13
+
14
+
15
+ ## Usage
16
+
17
+ Just drop the gem in your `Gemfile`, under the `:assets` group:
18
+
19
+ ```ruby
20
+ group :assets do
21
+ ...
22
+ gem 'turbo-sprockets-rails3'
23
+ end
24
+ ```
25
+
26
+ Run `bundle` to install the gem, and you're done!
27
+
28
+ Test it out by running `rake assets:precompile`. When it's finished, you should see a new file at `public/assets/sources_manifest.yml`, which includes the source fingerprints for your assets.
29
+
30
+ Go on, run `rake assets:precompile` again, and it should be a whole lot faster than before.
31
+
32
+ Enjoy your lightning fast deploys!
33
+
34
+ ## Removing Expired Assets
35
+
36
+ `turbo-sprockets-rails3` provides a Rake task called `assets:clean_expired`. You can run this task after `assets:precompile` to remove outdated assets.
37
+
38
+ If you use this rake task, you must set `config.assets.handle_expiration` to true in `config/environments/production.rb`. This makes sure that asset modification times
39
+ are updated properly before `assets:precompile`, so that the `clean_expired` task knows which assets are safe to remove.
40
+
41
+ An asset will be deleted if it is no longer referenced by `manifest.yml`, and hasn't been actively deployed for more than a day (default).
42
+
43
+ You can configure the expiry time by setting `config.assets.expire_after` in `config/environments/production.rb`.
44
+ An expiry time of 2 weeks could be configured with the following code:
45
+
46
+ ```ruby
47
+ config.assets.expire_after 2.weeks
48
+ ```
49
+
50
+ ## Versioning
51
+
52
+ The gem needs to support multiple changes to Rails and sprockets, so the following versioning is used:
53
+
54
+ * Rails 3.2.0 to 3.2.8 uses `turbo-sprockets-rails3` `0.2.x`
55
+ * Rails 3.2.9 and higher uses `turbo-sprockets-rails3` `0.3.x`
56
+
57
+
58
+ ## Compatibility
59
+
60
+ ### [asset_sync](https://github.com/rumblelabs/asset_sync)
61
+
62
+ Fully compatible.
63
+
64
+ ### [wicked_pdf](https://github.com/mileszs/wicked_pdf)
65
+
66
+ Fully compatible starting from version `0.8.0`.
67
+
68
+ ```ruby
69
+ gem 'wicked_pdf', '>= 0.8.0'
70
+ ```
71
+
72
+ <hr/>
73
+
74
+ Please let me know if you have any problems with other gems, and I will either fix it, or make a note of the problem here.
75
+
76
+ ## Deployments
77
+
78
+ ### Capistrano
79
+
80
+ `turbo-sprockets-rails3` should work out of the box with the latest version of Capistrano.
81
+
82
+ ### Heroku
83
+
84
+ I've created a Heroku Buildpack for `turbo-sprockets-rails3` that keeps your assets cached between deploys, so you only need to recompile changed assets. It will automatically expire old assets that are no longer referenced by `manifest.yml` after 7 days, so your `public/assets` folder won't grow out of control.
85
+
86
+ To create a new application on Heroku using this buildpack, you can run:
87
+
88
+ ```bash
89
+ heroku create --buildpack https://github.com/ndbroadbent/heroku-buildpack-turbo-sprockets.git
90
+ ```
91
+
92
+ To add the buildpack to an existing app, you can run:
93
+
94
+ ```bash
95
+ heroku config:add BUILDPACK_URL=https://github.com/ndbroadbent/heroku-buildpack-turbo-sprockets.git
96
+ ```
97
+
98
+ #### Compiling Assets on Your Local Machine
99
+
100
+ You can also compile assets on your local machine, and commit the compiled assets. You might want to do this if your local machine is a lot faster than the Heroku VM, or if you also want to generate other files, such as static pages. When you push compiled assets to Heroku, it will automatically skip the `assets:precompile` task.
101
+
102
+ I've automated this process in a Rake task for my own projects. The task creates a deployment repo at `tmp/heroku_deploy` so that you can keep working while deploying, and it also rebases and amends the assets commit to keep your repo's history from growing out of control. You can find the deploy task in a gist at https://gist.github.com/3802355. Save this file to `lib/tasks/deploy.rake`, and make sure you have added a `heroku` remote to your repo. You will now be able to run `rake deploy` to deploy your app to Heroku.
103
+
104
+ #### Compiling Assets for a specific RAILS_ENV
105
+
106
+ Supplying an RAILS_ENV to the precompile task will generate (and read from) an env specific sources manifest
107
+
108
+ ## Debugging
109
+
110
+ If you would like to view debugging information in your terminal during the `assets:precompile` task, add the following lines to the bottom of `config/environments/production.rb`:
111
+
112
+ ```ruby
113
+ config.log_level = :debug
114
+ config.logger = Logger.new(STDOUT)
115
+ ```
116
+
117
+
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ require 'rake/testtask'
3
+ ENV["TEST_CORES"] = "1"
4
+
5
+ require 'bundler'
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ namespace :test do
9
+ task :isolated do
10
+ Dir["test/assets*_test.rb"].each do |file|
11
+ dash_i = [
12
+ 'test',
13
+ 'lib',
14
+ ]
15
+ ruby "-I#{dash_i.join ':'}", file
16
+ end
17
+ end
18
+ end
19
+
20
+ Rake::TestTask.new("test:regular") do |t|
21
+ t.libs << 'lib'
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/sprockets*_test.rb'
24
+ t.verbose = true
25
+ end
26
+
27
+ task :test => ['test:isolated', 'test:regular']
28
+ 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,106 @@
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
+ @digests = options.fetch(:digests, {})
15
+
16
+ # Parse digests from digests hash
17
+ @asset_digests = Hash[*@digests.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
+ if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
31
+ logical_path.sub!(/\/index\./, '.')
32
+ end
33
+ next unless compile_path?(logical_path)
34
+
35
+ if digest_path = @digests[logical_path]
36
+ abs_digest_path = "#{@target}/#{digest_path}"
37
+ abs_logical_path = "#{@target}/#{logical_path}"
38
+
39
+ # Remove known digests from css & js
40
+ if abs_digest_path.match(/\.(?:js|css)$/)
41
+ mtime = File.mtime(abs_digest_path)
42
+
43
+ asset_body = File.read(abs_digest_path)
44
+
45
+ # Find all hashes in the asset body with a leading '-'
46
+ asset_body.gsub!(DIGEST_REGEX) do |match|
47
+ # Only remove if known digest
48
+ $1.in?(@asset_digests.values) ? '' : match
49
+ end
50
+
51
+ # Write non-digest file
52
+ File.open abs_logical_path, 'w' do |f|
53
+ f.write asset_body
54
+ end
55
+ # Set modification and access times
56
+ File.utime(File.atime(abs_digest_path), mtime, abs_logical_path)
57
+
58
+ # Also write gzipped asset
59
+ File.open("#{abs_logical_path}.gz", 'wb') do |f|
60
+ gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
61
+ gz.mtime = mtime.to_i
62
+ gz.write asset_body
63
+ gz.close
64
+ end
65
+
66
+ env.logger.debug "Stripped digests, copied to #{logical_path}, and created gzipped asset"
67
+
68
+ else
69
+ # Otherwise, treat file as binary and copy it.
70
+ # Ignore paths that have no digests, such as READMEs
71
+ unless !File.exist?(abs_digest_path) || abs_digest_path == abs_logical_path
72
+ FileUtils.cp_r abs_digest_path, abs_logical_path, :remove_destination => true
73
+ env.logger.debug "Copied binary asset to #{logical_path}"
74
+
75
+ # Copy gzipped asset if exists
76
+ if File.exist? "#{abs_digest_path}.gz"
77
+ FileUtils.cp_r "#{abs_digest_path}.gz", "#{abs_logical_path}.gz", :remove_destination => true
78
+ env.logger.debug "Copied gzipped asset to #{logical_path}.gz"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+ elapsed_time = ((Time.now.to_f - start_time) * 1000).to_i
87
+ env.logger.debug "Generated non-digest assets in #{elapsed_time}ms"
88
+ end
89
+
90
+ private
91
+
92
+ def compile_path?(logical_path)
93
+ paths.each do |path|
94
+ case path
95
+ when Regexp
96
+ return true if path.match(logical_path)
97
+ when Proc
98
+ return true if path.call(logical_path)
99
+ else
100
+ return true if File.fnmatch(path.to_s, logical_path)
101
+ end
102
+ end
103
+ false
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,39 @@
1
+ module Sprockets
2
+ class UnprocessedAsset < AssetWithDependencies
3
+ def initialize(environment, logical_path, pathname)
4
+ super
5
+
6
+ @environment = environment
7
+ context = environment.context_class.new(environment, logical_path, pathname)
8
+ attributes = environment.attributes_for(pathname)
9
+ preprocessors = environment.preprocessors(content_type)
10
+
11
+ # Remove all postprocessors and engines except ERB to return unprocessed source file
12
+ allowed_engines = [Tilt::ERBTemplate]
13
+ processors = preprocessors + (allowed_engines & attributes.engines)
14
+
15
+ @source = context.evaluate(pathname, :processors => processors)
16
+
17
+ # If the source contains any '@import' directives, add Sass and Less processors.
18
+ # (@import is quite difficult to handle properly without processing.)
19
+ if @source.include?("@import")
20
+ # Process Sass and Less files, since @import is too tricky to handle properly.
21
+ allowed_engines << Sass::Rails::ScssTemplate if defined?(Sass::Rails::ScssTemplate)
22
+ allowed_engines << Sass::Rails::SassTemplate if defined?(Sass::Rails::SassTemplate)
23
+ allowed_engines << Less::Rails::LessTemplate if defined?(Less::Rails::LessTemplate)
24
+ processors = preprocessors + (allowed_engines & attributes.engines)
25
+ @source = context.evaluate(pathname, :processors => processors)
26
+ end
27
+
28
+ build_required_assets(environment, context, :process => false)
29
+ build_dependency_paths(environment, context, :process => false)
30
+
31
+ @dependency_digest = compute_dependency_digest(environment)
32
+ end
33
+ end
34
+
35
+ # Return unprocessed dependencies when initializing asset from serialized hash
36
+ def init_with(environment, coder)
37
+ super(environment, coder, :process => false)
38
+ end
39
+ end