pinion 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +56 -18
- data/lib/pinion/bundle.rb +25 -19
- data/lib/pinion/server.rb +43 -20
- data/lib/pinion/sinatra_helpers.rb +1 -3
- data/lib/pinion/version.rb +1 -1
- metadata +16 -16
data/README.md
CHANGED
@@ -50,7 +50,7 @@ class YourApp < Sinatra::Base
|
|
50
50
|
pinion.watch "public/stylus"
|
51
51
|
end
|
52
52
|
|
53
|
-
helpers
|
53
|
+
helpers Pinion::SinatraHelpers
|
54
54
|
...
|
55
55
|
end
|
56
56
|
```
|
@@ -117,31 +117,33 @@ Pinion provides some helpers to help you construct links for assets.
|
|
117
117
|
```
|
118
118
|
|
119
119
|
This assumes that you have the `Pinion::Server` instance available inside your app as `pinion`. If you're
|
120
|
-
using
|
120
|
+
using Sinatra and `Pinion::SinatraHelpers`, then the helpers are available right in your app's scope:
|
121
121
|
|
122
122
|
``` erb
|
123
123
|
<%= css_url("style.css") %>
|
124
124
|
```
|
125
125
|
|
126
|
-
|
126
|
+
## In Production
|
127
127
|
|
128
128
|
In production, you may wish to concatenate and minify your assets before you serve them. This is done through
|
129
129
|
using asset bundles. Pinion provides a predefined bundle type, `:concatenate_and_uglify_js`, for your
|
130
130
|
convenience.
|
131
131
|
|
132
|
-
You
|
132
|
+
You will create bundles when you set up your `Pinion::Server` instance:
|
133
|
+
|
134
|
+
``` ruby
|
135
|
+
pinion.create_bundle(:main_bundle, :concatenate_and_uglify_js, ["app.js", "util.js", "jquery.js"])
|
136
|
+
```
|
137
|
+
|
138
|
+
In this case, `:main_bundle` is an identifier for this bundle, and will the name under which this bundle is
|
139
|
+
served. In your view, you will use the bundle similarly to how you use the `js_url` or `css_url` helpers:
|
133
140
|
|
134
141
|
``` erb
|
135
|
-
<%=
|
136
|
-
"app.js",
|
137
|
-
"helpers.js",
|
138
|
-
"util.js",
|
139
|
-
"jquery.js"
|
140
|
-
) %>
|
142
|
+
<%= js_bundle(:main_bundle) %>
|
141
143
|
```
|
142
144
|
|
143
|
-
In development, the individual `<script>` tags for each asset will be emitted; in production, a single
|
144
|
-
(`main-bundle.js`) will be produced.
|
145
|
+
In development, the individual `<script>` tags for each asset will be emitted; in production, a single
|
146
|
+
asset (`main-bundle.js`) will be produced.
|
145
147
|
|
146
148
|
The `:concatenate_and_uglify_js` bundle type simply concatenates JS files and runs them through
|
147
149
|
[Uglifier](https://github.com/lautis/uglifier). No default CSS bundle type is provided (but the built-in Sass
|
@@ -168,6 +170,15 @@ Note that in production mode, asset URLs will have the md5sum of the asset inser
|
|
168
170
|
|
169
171
|
and these assets are served with long (1-year) expiry, for good cacheability.
|
170
172
|
|
173
|
+
# Example
|
174
|
+
|
175
|
+
You can see an example app using Pinion and Sinatra in the `example/` directory. This app shows serving some
|
176
|
+
static and compiled assets as well as a simple asset bundle. Run `bundle install` in that directory to get the
|
177
|
+
necessary gems, then run it:
|
178
|
+
|
179
|
+
rackup config.ru # Development mode
|
180
|
+
RACK_ENV=production rackup config.ru # Production mode
|
181
|
+
|
171
182
|
# Notes
|
172
183
|
|
173
184
|
* Currently, Pinion sidesteps the dependency question by invalidating its cache of each file of a particular
|
@@ -176,12 +187,39 @@ and these assets are served with long (1-year) expiry, for good cacheability.
|
|
176
187
|
intance, if `foo/bar` and `foo/baz` are both on the watch list, and both of the files `foo/bar/style.scss`
|
177
188
|
and `foo/baz/style.scss` exist, then `foo/bar/style.scss` will be used if a request occurs for
|
178
189
|
`/style.css`.)
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
190
|
+
* If you don't use the url helpers provided by Pinion and instead just serve assets with a plain url (that is,
|
191
|
+
without a checksum in the url), Pinion will serve the assets with 10-minute expiry in the Cache-Control
|
192
|
+
header. In general, you should try to serve all your assets by using the urls given by the helpers.
|
193
|
+
|
194
|
+
## Why not use Sprockets?
|
195
|
+
|
196
|
+
You should! [Sprockets](https://github.com/sstephenson/sprockets/) is a great project. Pinion is a smaller
|
197
|
+
project than Sprockets, and it does fewer things. I made Pinion because I'm not using Rails (the Rails
|
198
|
+
integration is where Sprockets really shines) and because some of the design choices that Sprockets makes
|
199
|
+
don't really fit my ideal workflow. Here are a few things that Pinion does differently:
|
200
|
+
|
201
|
+
* Conversions are defined from one filetype to another (e.g. `:scss => :css`), not by the chain of file
|
202
|
+
extensions (e.g. `foo.css.scss`). I like the asset manager to follow the file naming scheme, not the other
|
203
|
+
way around.
|
204
|
+
* It's very easy to define your own conversions instead of using the built-in ones (and this is expected, as
|
205
|
+
Pinion only comes with a few of them). This makes it very simple to customize the behavior for your own
|
206
|
+
needs. Want to output minified css in production but a human-readable version in dev? This is easy to do
|
207
|
+
(and this is the default behavior of the built-in sass/scss converters).
|
208
|
+
* To support concatenation, Sprockets uses special directives in comments at the beginning of files (e.g.
|
209
|
+
`//= require jquery`) to specify dependencies between files. To my mind, this is not desirable because:
|
210
|
+
|
211
|
+
* It's easier to debug in development if the files aren't all concatentated together.
|
212
|
+
* Other systems (say, a node.js-based JS test runner) don't understand these dependencies.
|
213
|
+
* How you bundle assets is mostly a caching/performance concern separate from your app logic, so it
|
214
|
+
doesn't necessarily make sense to tie them together. For instance, you may wish to bundle together your
|
215
|
+
vendored JS separately from your application JS if you expect the latter to change much more frequently.
|
216
|
+
|
217
|
+
In contrast, Pinion bundles are created by using the `*_bundle` methods in your app server (view) code,
|
218
|
+
which (in my opinion) is much more obvious behavior.
|
219
|
+
|
220
|
+
This being said, you should certainly consider using Sprockets if it fits into your project. It is a very
|
221
|
+
established project that powers the Rails asset pipeline, and it has great support for a wide variety of
|
222
|
+
conversion types. If you use both, let me know what you think!
|
185
223
|
|
186
224
|
# Authors
|
187
225
|
|
data/lib/pinion/bundle.rb
CHANGED
@@ -8,41 +8,47 @@ module Pinion
|
|
8
8
|
# A `Bundle` is a set of assets of the same type that will be served as a single grouped asset in
|
9
9
|
# production. A `Bundle` has a `BundleType` that defines how to process the bundle.
|
10
10
|
class Bundle < Asset
|
11
|
+
# Each bundle is cached by name.
|
11
12
|
@@bundles = {}
|
12
13
|
|
13
|
-
attr_reader :contents, :name
|
14
|
+
attr_reader :contents, :name, :paths
|
14
15
|
|
15
16
|
# Create a new `Bundle`.
|
16
|
-
def initialize(bundle_type, name,
|
17
|
+
def initialize(bundle_type, name, paths)
|
17
18
|
@bundle_type = bundle_type
|
18
19
|
@name = name
|
19
|
-
@
|
20
|
-
raise Error, "No
|
21
|
-
|
22
|
-
|
20
|
+
@paths = paths
|
21
|
+
raise Error, "No paths provided" if paths.empty?
|
22
|
+
|
23
|
+
@assets = paths.map do |path|
|
24
|
+
asset = Asset[path]
|
25
|
+
raise "Error: no such asset available: #{path}" unless asset
|
26
|
+
asset
|
27
|
+
end
|
28
|
+
@extension = @assets.first.extension
|
29
|
+
unless @assets.all? { |asset| asset.extension == @extension }
|
23
30
|
raise Error, "All assets in a bundle must have the same extension"
|
24
31
|
end
|
25
|
-
@contents = bundle_type.process(assets)
|
32
|
+
@contents = bundle_type.process(@assets)
|
26
33
|
@checksum = Digest::MD5.hexdigest(@contents)
|
27
|
-
@mtime = assets.map(&:mtime).max
|
34
|
+
@mtime = @assets.map(&:mtime).max
|
28
35
|
@length = Rack::Utils.bytesize(@contents)
|
29
36
|
end
|
30
37
|
|
31
|
-
# Create a new bundle from a bundle_type name (e.g. `:concatenate_and_uglify_js`) and an array of
|
32
|
-
#
|
33
|
-
def self.create(
|
38
|
+
# Create a new bundle from a bundle_type name (e.g. `:concatenate_and_uglify_js`) and an array of paths.
|
39
|
+
# The name is taken as the identifier in the resulting path.
|
40
|
+
def self.create(name, bundle_type_name, paths)
|
34
41
|
bundle_type = BundleType[bundle_type_name]
|
35
42
|
raise Error, "No such bundle type #{bundle_type_name}" unless bundle_type
|
36
|
-
|
37
|
-
|
43
|
+
if @@bundles[name.to_s]
|
44
|
+
raise Error, "There is already a bundle called #{name}. Each bundle must have a different name."
|
45
|
+
end
|
46
|
+
bundle = Bundle.new(bundle_type, name, paths)
|
47
|
+
@@bundles[name.to_s] = bundle
|
38
48
|
bundle
|
39
49
|
end
|
40
50
|
|
41
|
-
# Find a `Bundle` by its name
|
42
|
-
def self.[](name
|
43
|
-
return nil unless name && checksum
|
44
|
-
bundle = @@bundles[checksum]
|
45
|
-
(bundle && (bundle.name == name)) ? bundle : nil
|
46
|
-
end
|
51
|
+
# Find a `Bundle` by its name.
|
52
|
+
def self.[](name) name && @@bundles[name.to_s] end
|
47
53
|
end
|
48
54
|
end
|
data/lib/pinion/server.rb
CHANGED
@@ -64,14 +64,30 @@ module Pinion
|
|
64
64
|
path.sub!("-#{checksum_tag}", "")
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
# First see if there is a bundle with this name
|
68
|
+
asset = Bundle[bundle_name]
|
69
|
+
if asset
|
70
|
+
# If the checksum doesn't match the bundle, we want to return a 404.
|
71
|
+
asset = nil unless asset.checksum == checksum_tag
|
72
|
+
else
|
73
|
+
# If there is no bundle with this name, find another type of Asset with the name instead.
|
74
|
+
asset = Asset[path]
|
75
|
+
end
|
68
76
|
|
69
77
|
if asset
|
70
78
|
# If the ETag matches, give a 304
|
71
79
|
return [304, {}, []] if env["HTTP_IF_NONE_MATCH"] == %Q["#{asset.checksum}"]
|
72
80
|
|
73
|
-
|
74
|
-
|
81
|
+
if Pinion.environment == "production"
|
82
|
+
# In production, if there is a checksum, cache for a year (because if the asset changes with a
|
83
|
+
# redeploy, the checksum will change). If there is no checksum, cache for 10 minutes (this way at
|
84
|
+
# worst we only serve 10 minute stale assets, and caches in front of Pinion will be able to help
|
85
|
+
# out).
|
86
|
+
cache_policy = checksum_tag ? "max-age=31536000" : "max-age=600"
|
87
|
+
else
|
88
|
+
# Don't cache in dev.
|
89
|
+
cache_policy = "must-revalidate"
|
90
|
+
end
|
75
91
|
headers = {
|
76
92
|
"Content-Type" => asset.content_type,
|
77
93
|
"Content-Length" => asset.length.to_s,
|
@@ -113,29 +129,36 @@ module Pinion
|
|
113
129
|
def css_inline(path) %Q{<style type="text/css">#{asset_inline(path)}</style>} end
|
114
130
|
def js_inline(path) %Q{<script>#{asset_inline(path)}</script>} end
|
115
131
|
|
116
|
-
#
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
asset
|
132
|
+
# Create a bundle of assets. Each asset must convert to the same final type. `name` is an identifier for
|
133
|
+
# this bundle; `bundle_name` is the # identifier for your bundle type (e.g. `:concatenate_and_uglify_js`);
|
134
|
+
# and `paths` are all the asset paths. In development, no bundles will be created (but the list of
|
135
|
+
# discrete assets will be saved for use in a subsequent `bundle_url` call).
|
136
|
+
def create_bundle(name, bundle_name, paths)
|
137
|
+
if Bundle[name]
|
138
|
+
raise "Error: there is already a bundle called #{name} with different component files. Each " <<
|
139
|
+
"bundle must have a unique name."
|
125
140
|
end
|
126
|
-
|
127
|
-
[
|
128
|
-
|
129
|
-
def js_bundle(bundle_name, name, *paths)
|
130
|
-
bundle_url(bundle_name, name, *paths).map { |path| js_wrapper(path) }.join
|
141
|
+
|
142
|
+
normalized_paths = paths.map { |path| path.sub(%r[^(#{@mount_point})?/?], "") }
|
143
|
+
Bundle.create(name, bundle_name, normalized_paths)
|
131
144
|
end
|
132
|
-
|
133
|
-
|
145
|
+
|
146
|
+
# Return the bundle url. In production, the single bundled result is produced; otherwise, each individual
|
147
|
+
# asset_url is returned.
|
148
|
+
def bundle_url(name)
|
149
|
+
bundle = Bundle[name]
|
150
|
+
raise "No such bundle: #{name}" unless bundle
|
151
|
+
return bundle.paths.map { |p| asset_url(p) } unless Pinion.environment == "production"
|
152
|
+
["#{@mount_point}/#{bundle.name}-#{bundle.checksum}.#{bundle.extension}"]
|
134
153
|
end
|
154
|
+
def js_bundle(name) bundle_url(name).map { |path| js_wrapper(path) }.join end
|
155
|
+
def css_bundle(name) bundle_url(name).map { |path| css_wrapper(path) }.join end
|
135
156
|
|
136
157
|
private
|
137
158
|
|
138
|
-
|
159
|
+
# Need type="text/javascript" for IE compatibility. The content-type will be set correctly as
|
160
|
+
# "application/javascript" in the response.
|
161
|
+
def js_wrapper(inner) %Q{<script type="text/javascript" src="#{inner}"></script>} end
|
139
162
|
def css_wrapper(inner) %Q{<link type="text/css" rel="stylesheet" href="#{inner}" />} end
|
140
163
|
|
141
164
|
def with_content_length(response)
|
data/lib/pinion/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pinion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-17 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
16
|
-
requirement: &
|
16
|
+
requirement: &26493960 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '1.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *26493960
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &26493500 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *26493500
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: yard
|
38
|
-
requirement: &
|
38
|
+
requirement: &26493020 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *26493020
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: scope
|
49
|
-
requirement: &
|
49
|
+
requirement: &26492500 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *26492500
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rack-test
|
60
|
-
requirement: &
|
60
|
+
requirement: &26492020 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *26492020
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: coffee-script
|
71
|
-
requirement: &
|
71
|
+
requirement: &26491360 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *26491360
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: dedent
|
82
|
-
requirement: &
|
82
|
+
requirement: &26490460 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *26490460
|
91
91
|
description: ! 'Pinion is a Rack application that you can use to compile and serve
|
92
92
|
assets (such as Javascript and CSS).
|
93
93
|
|