wake-assets 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # Wake::Assets
2
+
3
+ This module helps you render links to assets managed using
4
+ [wake](http://github.com/jcoglan/wake). It's easy to set up and works with any
5
+ Ruby web framework.
6
+
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ $ gem install wake-assets
12
+ ```
13
+
14
+
15
+ ## Usage
16
+
17
+ These examples are based on the [wake example build
18
+ config](https://github.com/jcoglan/wake#usage).
19
+
20
+ #### At boot time
21
+
22
+ When your app boots, create an instance of `Wake::Assets` and keep this object
23
+ around through the lifetime of the app process. For example, in Rails:
24
+
25
+ ```ruby
26
+ require 'wake/assets'
27
+
28
+ dev = Rails.env.development?
29
+
30
+ $wake = Wake::Assets.new(
31
+ :wake => File.expand_path('node_modules/.bin/wake', Rails.root),
32
+ :root => File.expand_path('public', Rails.root),
33
+ :mode => dev ? :sources : :targets,
34
+ :monitor => dev
35
+ )
36
+ ```
37
+
38
+ The options are:
39
+
40
+ * `:wake` - the path to your `wake` executable
41
+ * `:root` - the document root of your application
42
+ * `:mode` - `:sources` if you want to render links to source files, `:targets`
43
+ if you want optimised files
44
+ * `:monitor` - whether to monitor the filesystem for changes, recommended in
45
+ development but not in production
46
+
47
+ #### At request time
48
+
49
+ On each request, create a renderer from your `Assets` instance. In Rails, you
50
+ might do this with a helper:
51
+
52
+ ```ruby
53
+ module AssetsHelper
54
+ CONFIG_PATH = File.expand_path('package.json', Rails.root)
55
+ ASSET_HOSTS = JSON.parse(File.read(CONFIG_PATH))['wake']['css']['hosts']
56
+
57
+ def assets
58
+ @assets ||= $wake.renderer(
59
+ :builds => {
60
+ 'css' => request.ssl? ? 'ssl' : 'min',
61
+ 'javascript' => 'min',
62
+ 'binary' => 'min'
63
+ },
64
+ :hosts => ASSET_HOSTS[Rails.env][request.ssl? ? 'https' : 'http'],
65
+ :inline => false
66
+ )
67
+ end
68
+ end
69
+ ```
70
+
71
+ The options are:
72
+
73
+ * `:builds` - which build to use for each asset type, the default for each is
74
+ `min`
75
+ * `:hosts` - the set of asset hosts to use for rendering links, the default is
76
+ an empty list
77
+ * `:inline` - whether to render assets inline so the browser does not make
78
+ additional requests for them, default is `false`
79
+
80
+ #### In your templates
81
+
82
+ With this helper in place, you can render links to JavaScript, CSS and images:
83
+
84
+ ```ruby
85
+ assets.include_js 'scripts.js'
86
+ # => '<script type="text/javascript" src="/assets/scripts-bb210c6.js"></script>'
87
+
88
+ assets.include_css 'style.css'
89
+ # => '<link rel="stylesheet" type="text/css" href="/assets/styles-5a2ceb1.css">'
90
+
91
+ assets.include_image 'logo.png', :html => {:alt => 'Logo'}
92
+ # => '<img src="/assets/logo-2fa8d38.png" alt="Logo">'
93
+ ```
94
+
95
+ You can pass the `:inline` option to any of these to override the per-request
96
+ `:inline` setting:
97
+
98
+ ```ruby
99
+ assets.include_js 'scripts.js', :inline => true
100
+ # => '<script type="text/javascript">alert("Hello, world!")</script>'
101
+ ```
102
+
103
+
104
+ ## License
105
+
106
+ (The MIT License)
107
+
108
+ Copyright (c) 2013 James Coglan, Songkick
109
+
110
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
111
+ this software and associated documentation files (the 'Software'), to deal in
112
+ the Software without restriction, including without limitation the rights to
113
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
114
+ the Software, and to permit persons to whom the Software is furnished to do so,
115
+ subject to the following conditions:
116
+
117
+ The above copyright notice and this permission notice shall be included in all
118
+ copies or substantial portions of the Software.
119
+
120
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
121
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
122
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
123
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
124
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
125
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
126
+
data/lib/wake.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Wake
2
+ root = File.expand_path('../wake', __FILE__)
3
+ autoload :Assets, root + '/assets'
4
+ autoload :Renderer, root + '/renderer'
5
+ end
6
+
@@ -0,0 +1,148 @@
1
+ require 'base64'
2
+ require 'erb'
3
+ require 'json'
4
+ require 'listen'
5
+ require 'mime/types'
6
+ require 'pathname'
7
+ require 'set'
8
+
9
+ module Wake
10
+ class Assets
11
+
12
+ DEFAULT_BUILD = 'min'
13
+ DEFAULT_MODE = :targets
14
+ DEFAULT_WAKE = './node_modules/wake/bin/wake'
15
+ CACHE_FILE = '.wake.json'
16
+ MANIFEST = '.manifest.json'
17
+ PACKAGE_FILE = 'package.json'
18
+ WAKE_FILE = 'wake.json'
19
+ CONFIG_FILES = Set.new([CACHE_FILE, MANIFEST, PACKAGE_FILE, WAKE_FILE])
20
+
21
+ CSS = 'css'
22
+ JS = 'javascript'
23
+ IMG = 'binary'
24
+
25
+ class InvalidReference < StandardError
26
+ end
27
+
28
+ autoload :Renderer, File.expand_path('../assets/renderer', __FILE__)
29
+
30
+ def initialize(options)
31
+ @pwd = File.expand_path(options.fetch(:pwd, Dir.pwd))
32
+ @wake = options.fetch(:wake, File.expand_path(DEFAULT_WAKE, @pwd))
33
+ @root = Pathname.new(File.expand_path(options.fetch(:root, @pwd)))
34
+ @mode = options.fetch(:mode, DEFAULT_MODE)
35
+ @manifest = new_manifest_cache
36
+ @paths = new_path_cache
37
+
38
+ system(@wake, '--cache')
39
+ read_config
40
+
41
+ return unless options[:monitor]
42
+
43
+ listener = Listen.to(@pwd).change do |modified, added, removed|
44
+ all = (modified + added + removed).map &File.method(:basename)
45
+ system(@wake, '--cache') if (added.any? or removed.any?) and not all.include?(CACHE_FILE)
46
+ update! if (CONFIG_FILES & all).any?
47
+ end
48
+ listener.force_polling(true)
49
+ listener.start
50
+ end
51
+
52
+ def deployment?
53
+ @mode == :targets
54
+ end
55
+
56
+ def paths_for(group, names, options = {})
57
+ build = options.fetch(:build, DEFAULT_BUILD)
58
+ unless @config[group].fetch('builds', {}).has_key?(build)
59
+ build = DEFAULT_BUILD
60
+ end
61
+ names.map { |name| @paths[group][name][build] }.flatten
62
+ end
63
+
64
+ def relative(path)
65
+ '/' + Pathname.new(path).relative_path_from(@root).to_s
66
+ end
67
+
68
+ def renderer(options = {})
69
+ Renderer.new(self, options)
70
+ end
71
+
72
+ private
73
+
74
+ def find_paths_for(group, name, build)
75
+ absolute_paths = begin
76
+ cache = @cache[group][name]
77
+ if @mode.to_s == 'sources'
78
+ cache.fetch('sources')
79
+ else
80
+ [cache.fetch('targets').fetch(build)]
81
+ end
82
+ rescue
83
+ nil
84
+ end
85
+
86
+ if absolute_paths.nil?
87
+ raise InvalidReference, "Could not find assets, group: #{group}, name: #{name}, build: #{build}"
88
+ end
89
+
90
+ absolute_paths.map do |path|
91
+ basename = File.basename(path)
92
+ dirname = File.dirname(path)
93
+ manifest = File.join(dirname, MANIFEST)
94
+
95
+ File.join(dirname, @manifest[manifest].fetch(basename, basename))
96
+ end
97
+ end
98
+
99
+ def new_manifest_cache
100
+ Hash.new do |hash, path|
101
+ hash[path] = File.file?(path) ? JSON.parse(File.read(path)) : {}
102
+ end
103
+ end
104
+
105
+ def new_path_cache
106
+ Hash.new do |h, group|
107
+ h[group] = Hash.new do |i, name|
108
+ i[name] = Hash.new do |j, build|
109
+ j[build] = find_paths_for(group, name, build)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def read_config
116
+ cache = File.join(@pwd, CACHE_FILE)
117
+ wake = File.join(@pwd, WAKE_FILE)
118
+ package = File.join(@pwd, PACKAGE_FILE)
119
+
120
+ @config = if File.file?(wake)
121
+ JSON.parse(File.read(wake))
122
+ elsif File.file?(package)
123
+ JSON.parse(File.read(package))['wake']
124
+ else
125
+ {}
126
+ end
127
+
128
+ @cache = JSON.parse(File.read(cache))
129
+ end
130
+
131
+ def update!
132
+ paths = new_path_cache
133
+ read_config
134
+
135
+ @paths.each do |group, a|
136
+ a.each do |name, b|
137
+ b.each do |build, files|
138
+ paths[group][name][build]
139
+ end
140
+ end
141
+ end
142
+ @manifest = new_manifest_cache
143
+ @paths = paths
144
+ end
145
+
146
+ end
147
+ end
148
+
@@ -0,0 +1,100 @@
1
+ module Wake
2
+ class Assets
3
+ class Renderer
4
+
5
+ def initialize(assets, options)
6
+ @assets = assets
7
+ @builds = options.fetch(:builds, {})
8
+ @hosts = options.fetch(:hosts, [])
9
+ @inline = options[:inline]
10
+ end
11
+
12
+ def include_css(*names)
13
+ names, options = extract_options(names)
14
+
15
+ tags = if options.fetch(:inline, @inline)
16
+ paths_for(CSS, names, options).map do |path|
17
+ %Q{#{tag :style, options, :type => 'text/css'}#{File.read path}</style>}
18
+ end
19
+ else
20
+ urls_for(CSS, names, options).map do |url|
21
+ tag :link, options, :rel => 'stylesheet', :type => 'text/css', :href => url
22
+ end
23
+ end
24
+
25
+ html_safe(tags * '')
26
+ end
27
+
28
+ def include_image(*names)
29
+ names, options = extract_options(names)
30
+
31
+ tags = if options.fetch(:inline, @inline)
32
+ paths_for(IMG, names, options).map do |path|
33
+ base64 = Base64.strict_encode64(File.read(path))
34
+ mime = MIME::Types.type_for(path).first
35
+ tag :img, options, :src => "data:#{mime};base64,#{base64}"
36
+ end
37
+ else
38
+ urls_for(IMG, names, options).map { |url| tag :img, options, :src => url }
39
+ end
40
+
41
+ html_safe(tags * '')
42
+ end
43
+
44
+ def include_js(*names)
45
+ names, options = extract_options(names)
46
+
47
+ tags = if options.fetch(:inline, @inline)
48
+ paths_for(JS, names, options).map do |path|
49
+ %Q{#{tag :script, options, :type => 'text/javascript'}#{File.read path}</script>}
50
+ end
51
+ else
52
+ urls_for(JS, names, options).map do |url|
53
+ %Q{#{tag :script, options, :type => 'text/javascript', :src => url}</script>}
54
+ end
55
+ end
56
+
57
+ html_safe(tags * '')
58
+ end
59
+
60
+ def paths_for(type, names, options = {})
61
+ @assets.paths_for(type, names, {:build => @builds[type]}.merge(options))
62
+ end
63
+
64
+ def urls_for(type, names, options = {})
65
+ paths = paths_for(type, names, options).map { |p| @assets.relative(p) }
66
+
67
+ return paths unless @hosts.any?
68
+
69
+ paths.map do |path|
70
+ @hosts[path.hash % @hosts.size].gsub(/\/*$/, '') + path
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def extract_options(names)
77
+ [names.grep(String), names.grep(Hash).first || {}]
78
+ end
79
+
80
+ def h(string)
81
+ ERB::Util.html_escape(string)
82
+ end
83
+
84
+ def html_safe(string)
85
+ if defined? ActiveSupport
86
+ ActiveSupport::SafeBuffer.new(string)
87
+ else
88
+ string
89
+ end
90
+ end
91
+
92
+ def tag(name, options, attrs)
93
+ attrs = attrs.merge(options.fetch(:html, {}))
94
+ "<#{name} #{ attrs.map { |k,v| %Q{#{k}="#{h v.to_s}"} }.join(' ') }>"
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wake-assets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Coglan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: listen
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mime-types
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '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: '0'
46
+ description:
47
+ email: jcoglan@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - README.md
52
+ files:
53
+ - README.md
54
+ - lib/wake.rb
55
+ - lib/wake/assets.rb
56
+ - lib/wake/assets/renderer.rb
57
+ homepage: http://github.com/jcoglan/wake-assets
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --main
62
+ - README.md
63
+ - --markup
64
+ - markdown
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.23
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Renders links to assets managed by wake
85
+ test_files: []