pinion 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|