tension 0.3 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 067f889277f3fb790d4fb64e6b196754f2ca9b92
4
+ data.tar.gz: ac1afed7e475d2e99cc72c330d224c1611951f13
5
+ SHA512:
6
+ metadata.gz: 2124f18b0a5c85ac50891d6ac74d6fa3f528afbc08dbee97177b66553ef357e8a05c8a1ad5b57f2154cd1eb0585be6ce33529c7da8660ad9c8b742d8bd9393d7
7
+ data.tar.gz: 107bb478074ab4b6c502217b29a5b21db0a44ebb22cdd422887c9261788db40e5178a6a8ee7111390a65a63bc983a3a5e3282f53a10959ce98ad71155ed69ce0
data/README.md CHANGED
@@ -3,4 +3,40 @@ Tension
3
3
  _Tighten up Rails's asset pipeline for CSS & JS._
4
4
 
5
5
  Rails's asset pipeline is smart and well–implemented under the hood, but it can
6
- be a challenge to organize your CSS and JavaScript so that
6
+ be a challenge to organize your CSS and JavaScript so that you don't have to
7
+ struggle to maintain which files get compiled, where they live, and how they're
8
+ included in views and templates.
9
+
10
+ _Tension_ helps you out. It takes Rails's existing controller/view file structure
11
+ and applies it to JavaScript and CSS assets as well. Let's take a sample Rails app:
12
+
13
+ app
14
+ + assets
15
+ + controllers
16
+ + account
17
+ + api
18
+ + blog
19
+ + posts_controller.rb
20
+ + models
21
+ + views
22
+ + account
23
+ + api
24
+ + blog
25
+ + posts
26
+ + index.html.erb
27
+ + layouts
28
+ + blog.html.erb
29
+
30
+ The standard structure Rails enforces is __module__ → __controller__ →
31
+ __action__. For the PostsController, the __action__ logic is tucked away alongside
32
+ other actions in `posts_controller.rb`, but its view is a separate file namespaced
33
+ under the _blog_ module and _posts_ controller.
34
+
35
+ This is a logical, intuitive structure. Our assets should follow it too.
36
+
37
+ Using Rails's asset pipeline you can drop an asset anywhere within the `assets`
38
+ directory and Rails will compile and serve it, but Rails enforces no logical
39
+ structure on your files. This leads to messy, hard-to-maintain code, and it gets
40
+ worse when you precompile assets in production.
41
+
42
+ ...
@@ -0,0 +1,167 @@
1
+ module Tension
2
+
3
+ # Tension::Environment exposes `asset_paths`, which describes the assets that
4
+ # can be automatically included in templates.
5
+ #
6
+ # It's used automatically on application load (see Tension::Railtie) to populate
7
+ # the asset pipeline's list of assets to precompile, and caches the result in-
8
+ # process for use by Tension::Tagger when templates are rendered.
9
+ #
10
+ class Environment
11
+
12
+ class << self
13
+
14
+ GLOBAL_KEY = "::globals".freeze
15
+ COMMON_SUFFIX = "_common".freeze
16
+ ASSET_TYPES = [{ container: "javascripts", extension: "js" },
17
+ { container: "stylesheets", extension: "css" }].freeze
18
+
19
+ # This method collects all asset paths for the asset pipeline to
20
+ # precompile. It determines which additional assets to include in the
21
+ # pipeline's precompilation process, so they can be automatically added
22
+ # to templates later (see Tension::Tagger).
23
+ #
24
+ # If an application's routes include `resources :people`, `asset_map`
25
+ # will attempt to locate the following assets:
26
+ #
27
+ # + people_common.{js,css}
28
+ # + people/index.{js,css}
29
+ # + people/show.{js,css}
30
+ # + people/new.{js,css}
31
+ # + people/edit.{js,css}
32
+ #
33
+ # Note that Tension ignores Sprockets' interpretation of `index.{js,css}`
34
+ # as a shared file, and therefore requires the `_common` suffix for
35
+ # controller-wide assets.
36
+ #
37
+ # Returns: an Hash of controller paths, each containing that controller's
38
+ # routed GET actions mapped to each action's best matching asset. E.g.
39
+ #
40
+ # { "people" => {
41
+ # "index" => {
42
+ # "js" => "javascripts/people/index.js",
43
+ # "css" => "javascripts/people_common.css"
44
+ # },
45
+ # "show" => {
46
+ # "js" => "javascripts/application.js",
47
+ # "css" => "javascripts/people_common.css"
48
+ # }
49
+ # }
50
+ #
51
+ def asset_map
52
+ if @asset_map.nil?
53
+ @asset_map = Hash.new
54
+ globals = Hash.new
55
+
56
+ ASSET_TYPES.each do |type|
57
+ # Find and store the global asset for this type.
58
+ global_asset = valid_asset( "application.#{ type[:extension] }" )
59
+ globals.store( type[:extension], global_asset )
60
+ end
61
+
62
+ # TODO: add support for looking up the tree...
63
+ search_paths.each_pair do |controller_path, actions|
64
+ next unless local_controller?( controller_path )
65
+
66
+ @asset_map.store( controller_path, Hash.new )
67
+
68
+ ASSET_TYPES.each do |type|
69
+ # Attempt to locate a common asset for this controller.
70
+ common_path = "#{ controller_path }#{ COMMON_SUFFIX }.#{ type[:extension] }"
71
+ common_asset = valid_asset( common_path ) || globals.fetch( type[:extension] )
72
+
73
+ actions.each do |action|
74
+ if @asset_map[ controller_path ][ action ].nil?
75
+ @asset_map.fetch( controller_path )
76
+ .store( action, Hash.new )
77
+ end
78
+
79
+ action_asset = valid_asset( "#{ [ controller_path, action ].join("/") }.#{ type[:extension] }" )
80
+ @asset_map.fetch( controller_path )
81
+ .fetch( action )
82
+ .store( type[:extension], action_asset || common_asset )
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ @asset_map
90
+ end
91
+
92
+ # All unique, existing asset paths.
93
+ def asset_paths
94
+ @asset_paths ||= Set.new.merge( extract_paths(asset_map) ).to_a
95
+ end
96
+
97
+
98
+ private
99
+
100
+ # Recursively extracts all paths from the `asset_map`.
101
+ def extract_paths hash
102
+ paths = Array.new
103
+
104
+ hash.each_pair do |key, value|
105
+ if value.is_a?(Hash)
106
+ paths.concat( extract_paths(value) )
107
+ else
108
+ paths << value.pathname.to_s
109
+ end
110
+ end
111
+
112
+ paths
113
+ end
114
+
115
+ # A hash of controller paths mapped to action names. These controller/action
116
+ # pairs correspond to configured routes for which assets may be required.
117
+ #
118
+ # e.g. { "blog" => [ "index", "show" ],
119
+ # "admin/blog" => [ "index", "show", "edit" ] }
120
+ #
121
+ def search_paths
122
+ @search_paths ||= configured_route_defaults.reduce( Hash.new ) do |accum, route_default|
123
+ accum[ route_default[:controller] ] ||= Array.new
124
+ accum[ route_default[:controller] ].push( route_default[:action] )
125
+
126
+ accum
127
+ end
128
+ end
129
+
130
+ # Routing defaults (including controller path and action name) for all
131
+ # configured GET routes.
132
+ #
133
+ def configured_route_defaults
134
+ if @configured_route_defaults.nil?
135
+ get_routes = Rails.application.routes.routes.find_all do |route|
136
+ route.verb.match("GET")
137
+ end
138
+
139
+ @configured_route_defaults = get_routes.map do |route|
140
+ route.defaults unless route.defaults.empty?
141
+ end
142
+
143
+ @configured_route_defaults.compact!
144
+ end
145
+
146
+ @configured_route_defaults
147
+ end
148
+
149
+ # Returns: a real BundledAsset present in the Sprockets index, or nil
150
+ # if no asset was found.
151
+ #
152
+ def valid_asset asset_path
153
+ Rails.application.assets.find_asset( asset_path )
154
+ end
155
+
156
+ # Returns: true if the controller targeted in the routes is present in this
157
+ # app's local code rather than in a gem. Gems are responsible for ensuring
158
+ # availability of their own assets.
159
+ #
160
+ def local_controller? controller_path
161
+ File.exists?("#{ Rails.root }/app/controllers/#{ controller_path }_controller.rb")
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+ end
@@ -4,14 +4,19 @@ module Tension
4
4
  require "rails"
5
5
 
6
6
  class Railtie < Rails::Railtie
7
+ initializer "tension.add_assets_to_precompile_list" do |app|
8
+ ActiveSupport.on_load :after_initialize do
7
9
 
8
- initializer "tension.asset_pipeline" do |app|
9
- ActiveSupport.on_load :rails do
10
- if !Rails.env.development? && !Rails.env.test?
11
- Rails.application.config.assets.precompile += Tension::Collector.collect_assets
10
+ Rails.application.reload_routes!
11
+ Tension.load_assets!
12
+
13
+ ApplicationHelper.send(:include, Tension::TensionHelper)
14
+
15
+ Rails.application.config.assets.precompile << lambda do |path, filename|
16
+ Tension::Environment.asset_paths.include?( filename )
12
17
  end
18
+
13
19
  end
14
20
  end
15
-
16
21
  end
17
22
  end
@@ -0,0 +1,26 @@
1
+ module Tension
2
+
3
+ # Tagger is included in ActionView::Helpers so it can be called from
4
+ # templates and layouts.
5
+ module TensionHelper
6
+ extend ActiveSupport::Concern
7
+
8
+ # Just call
9
+ def asset_for type, request = request
10
+ asset = Tension::Environment.asset_map
11
+ .fetch( request.params[:controller] )
12
+ .fetch( request.params[:action] )
13
+ .fetch( type.to_s )
14
+
15
+ include_method = case type
16
+ when :js
17
+ :javascript_include_tag
18
+ when :css
19
+ :stylesheet_link_tag
20
+ end
21
+
22
+ send( include_method, asset.logical_path )
23
+ end
24
+ end
25
+
26
+ end
data/lib/tension.rb CHANGED
@@ -0,0 +1,21 @@
1
+ require 'active_support/ordered_options'
2
+
3
+ require 'tension/environment'
4
+ require 'tension/railtie' if defined?(Rails)
5
+ require 'tension/tension_helper'
6
+
7
+ module Tension
8
+ def self.load_assets!
9
+ !!Tension::Environment.asset_map
10
+ end
11
+
12
+ # def self.config
13
+ # @config ||= begin
14
+ # config = ActiveSupport::OrderedOptions.new
15
+
16
+ # config.enabled = !Rails.env.development? && !Rails.env.test?
17
+
18
+ # config
19
+ # end
20
+ # end
21
+ end
data/tension.gemspec CHANGED
@@ -1,11 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "tension"
3
3
 
4
- s.version = "0.3"
5
- s.date = "2013-02-22"
4
+ s.version = "0.9"
5
+ s.date = "2013-03-21"
6
+ s.license = "MIT"
6
7
 
7
8
  s.summary = "Tighten up Rails's asset pipeline for CSS & JS."
8
- s.description = "Tension brings some sanity to CSS & JS organization for modern front–end development."
9
+ s.description = "Tension brings some sanity to Rails's CSS & JS organization for modern front–end development."
9
10
 
10
11
  s.authors = [ "Piers Mainwaring" ]
11
12
  s.email = "piers@impossibly.org"
@@ -13,6 +14,5 @@ Gem::Specification.new do |s|
13
14
  s.homepage = "https://github.com/piersadrian/tension"
14
15
  s.require_paths = [ "lib" ]
15
16
 
16
- s.add_dependency "activerecord", "~> 3.2.0"
17
- s.add_dependency "activesupport", "~> 3.2.0"
17
+ s.add_dependency "rails", ">= 3.2"
18
18
  end
metadata CHANGED
@@ -1,50 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tension
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
5
- prerelease:
4
+ version: '0.9'
6
5
  platform: ruby
7
6
  authors:
8
7
  - Piers Mainwaring
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-02-22 00:00:00.000000000 Z
11
+ date: 2013-03-21 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: activerecord
16
- prerelease: false
14
+ name: rails
17
15
  requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
- - - ~>
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
- version: 3.2.0
22
- none: false
19
+ version: '3.2'
23
20
  type: :runtime
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ~>
27
- - !ruby/object:Gem::Version
28
- version: 3.2.0
29
- none: false
30
- - !ruby/object:Gem::Dependency
31
- name: activesupport
32
21
  prerelease: false
33
- requirement: !ruby/object:Gem::Requirement
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: 3.2.0
38
- none: false
39
- type: :runtime
40
22
  version_requirements: !ruby/object:Gem::Requirement
41
23
  requirements:
42
- - - ~>
24
+ - - '>='
43
25
  - !ruby/object:Gem::Version
44
- version: 3.2.0
45
- none: false
46
- description: Tension brings some sanity to CSS & JS organization for modern front–end
47
- development.
26
+ version: '3.2'
27
+ description: Tension brings some sanity to Rails's CSS & JS organization for modern
28
+ front–end development.
48
29
  email: piers@impossibly.org
49
30
  executables: []
50
31
  extensions: []
@@ -53,32 +34,32 @@ files:
53
34
  - LICENSE
54
35
  - README.md
55
36
  - lib/tension.rb
56
- - lib/tension/application.rb
37
+ - lib/tension/environment.rb
57
38
  - lib/tension/railtie.rb
58
- - lib/tension/tagger.rb
39
+ - lib/tension/tension_helper.rb
59
40
  - tension.gemspec
60
41
  homepage: https://github.com/piersadrian/tension
61
- licenses: []
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
62
45
  post_install_message:
63
46
  rdoc_options: []
64
47
  require_paths:
65
48
  - lib
66
49
  required_ruby_version: !ruby/object:Gem::Requirement
67
50
  requirements:
68
- - - ! '>='
51
+ - - '>='
69
52
  - !ruby/object:Gem::Version
70
53
  version: '0'
71
- none: false
72
54
  required_rubygems_version: !ruby/object:Gem::Requirement
73
55
  requirements:
74
- - - ! '>='
56
+ - - '>='
75
57
  - !ruby/object:Gem::Version
76
58
  version: '0'
77
- none: false
78
59
  requirements: []
79
60
  rubyforge_project:
80
- rubygems_version: 1.8.24
61
+ rubygems_version: 2.0.2
81
62
  signing_key:
82
- specification_version: 3
63
+ specification_version: 4
83
64
  summary: Tighten up Rails's asset pipeline for CSS & JS.
84
65
  test_files: []
@@ -1,62 +0,0 @@
1
- module Tension
2
- class Application
3
-
4
- class << self
5
-
6
- # This method collects all asset paths for the asset pipeline to
7
- # precompile. It's called from `config/application.rb` when determining
8
- # which additional assets to include in the pipeline's precompilation
9
- # process. Tension will assume that any subdirectories in your assets
10
- # directory are module scopes. You can also explicitly set module scopes:
11
- #
12
- # Rails.application.config.tension_modules = %W( blog account )
13
- #
14
- # Tension::Application will then search for all javascripts and stylesheets
15
- # one filesystem level deep in those scopes. The search paths become:
16
- #
17
- # app/assets/{javascripts,stylesheets}/*.{js,css}
18
- # app/assets/{javascripts,stylesheets}/{blog,account}/**/*.{js,css}
19
- #
20
- # Any assets in these paths will be added to the pipeline and compiled.
21
-
22
- def collect_assets
23
- assets = %W(stylesheets javascripts).map do |type|
24
- glob_within_asset_path( type ) + module_scopes.map do |scope|
25
- glob_within_asset_path( type, scope )
26
- end
27
- end
28
-
29
- assets.flatten
30
- end
31
-
32
-
33
- private
34
-
35
- def module_scopes
36
- # find dirs in app/assets
37
- end
38
-
39
- # Loads the file paths within a given subdirectory of "app/assets/".
40
- def glob_within_asset_path type, *path_parts
41
- # Only recursively find children if a particular subdirectory of an
42
- # asset `type` is given. That way we can specify WHICH subdirectories
43
- # of a `type` have assets that matter.
44
- path_parts << "**" if path_parts.present?
45
-
46
- root_path = File.join(Rails.root, "app", "assets", type)
47
- pattern = File.expand_path( File.join(*path_parts, "*.*"), root_path )
48
-
49
- paths = Dir.glob( pattern ).map do |file_path|
50
- # Remove extra, pre-compilation file extensions and any part of the
51
- # filepath at or above the asset `type`.
52
- file_path.gsub( file_path.match(/(\.css|\.js)(.*)/)[2], '' )
53
- .gsub( root_path + "/", '' ) rescue nil
54
- end
55
-
56
- paths.compact
57
- end
58
-
59
- end
60
-
61
- end
62
- end
@@ -1,82 +0,0 @@
1
- module Pipeline
2
- class Builder
3
-
4
- GLOBAL_ASSET_NAME = "application".freeze
5
- SHARED_SUFFIX = "_common".freeze
6
- ASSET_SPECIFICITY_ORDER = [ :action, :controller, :module ].freeze
7
-
8
- # Determines which JS/CSS files should be included based on the current
9
- # controller, action, and which files actually exist. If the current
10
- # controller is "OrcaHealth::PagesController" and action is "products",
11
- # this method (called with `type = :js`) will attempt to include
12
- #
13
- # + orca_health/pages/products.js
14
- # + orca_health/pages_common.js
15
- # + orca_health.js
16
- #
17
- # in that order. ONLY THE FIRST ASSET LOCATED WILL BE INCLUDED. Hence,
18
- # you should always use Sprockets directives to include dependencies.
19
- #
20
- # Any files that don't exist will not be included in the page. Pass
21
- # `{ except: :application }` to exclude `application.{js,css}`.
22
-
23
- def self.asset_paths type, controller_path, action_name, options = {}
24
- options[:except] = [ options[:except] ] unless options[:except].is_a? Array
25
-
26
- # Check if the best asset's already been loaded and stored.
27
- asset = known_asset_paths[ controller_path ].try(:fetch, action_name, nil).try(:fetch, type, nil)
28
-
29
- if asset.nil?
30
- path_components = controller_path.split("/")
31
- controller_name = path_components.pop
32
- module_path = path_components.join("/")
33
-
34
- possible_paths = {
35
- module: module_path,
36
- controller: [ module_path, controller_name + SHARED_SUFFIX ].join("/"),
37
- action: [ module_path, controller_name, action_name ].join("/")
38
- }
39
-
40
- # Find and store the best possible asset for this controller/action combo.
41
- asset = most_specific_asset_path( type, possible_paths )
42
-
43
- known_asset_paths[ controller_path ] ||= Hash.new
44
- known_asset_paths[ controller_path ][ action_name ] ||= Hash.new
45
- known_asset_paths[ controller_path ][ action_name ][ type ] = asset
46
- end
47
-
48
- assets = if options[:except].include? :application
49
- [ asset ]
50
- else
51
- [ GLOBAL_ASSET_NAME, asset ]
52
- end
53
-
54
- assets.compact
55
- end
56
-
57
-
58
- private
59
-
60
- def self.known_asset_paths
61
- @known_asset_paths ||= Hash.new
62
- end
63
-
64
- def self.most_specific_asset_path filetype, paths
65
- ASSET_SPECIFICITY_ORDER.each do |name|
66
- path = paths[name]
67
-
68
- # Using the Sprockets `#index` helps performance in production.
69
- asset = if Rails.env.production?
70
- Rails.application.assets.index.find_asset([ path, filetype ].join("."))
71
- else
72
- Rails.application.assets.find_asset([ path, filetype ].join("."))
73
- end
74
-
75
- # This loop is relatively expensive, so return the asset path as soon
76
- # as possible rather than continue looping.
77
- break path if asset.present?
78
- end
79
- end
80
-
81
- end
82
- end