spar 0.2.7 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +272 -1
- data/bin/spar +30 -1
- data/lib/spar.rb +128 -7
- data/lib/spar/assets/404.jpg +0 -0
- data/lib/spar/assets/500.jpg +0 -0
- data/lib/spar/cli.rb +75 -22
- data/lib/spar/compiled_asset.rb +90 -0
- data/lib/spar/compiler.rb +74 -0
- data/lib/spar/compressor.rb +18 -0
- data/lib/spar/deployers/cloudfront_deployer.rb +33 -0
- data/lib/spar/deployers/deployer.rb +26 -0
- data/lib/spar/deployers/local_deployer.rb +15 -0
- data/lib/spar/deployers/s3_deployer.rb +75 -0
- data/lib/spar/directive_processor.rb +36 -0
- data/lib/spar/exceptions.rb +330 -0
- data/lib/spar/generators/templates/{README → base/README} +0 -0
- data/lib/spar/generators/templates/base/config.yml +12 -0
- data/lib/spar/generators/templates/pages/haml/app/pages/index.html.haml +11 -0
- data/lib/spar/generators/templates/pages/html/app/pages/index.html +16 -0
- data/lib/spar/generators/templates/{app/assets/javascripts/javascript.js.coffee → scripts/coffee/app/javascripts/application.js.coffee} +6 -3
- data/lib/spar/generators/templates/scripts/js/app/javascripts/application.js +11 -0
- data/lib/spar/generators/templates/styles/css/app/stylesheets/application.css +18 -0
- data/lib/spar/generators/templates/{app/assets → styles/sass/app}/stylesheets/application.css.sass +8 -4
- data/lib/spar/helpers.rb +31 -102
- data/lib/spar/not_found.rb +64 -0
- data/lib/spar/rewrite.rb +29 -0
- data/lib/spar/static.rb +46 -0
- data/lib/spar/tasks.rb +8 -13
- data/lib/spar/version.rb +1 -1
- metadata +261 -66
- data/lib/spar/assets.rb +0 -78
- data/lib/spar/base.rb +0 -54
- data/lib/spar/css_compressor.rb +0 -17
- data/lib/spar/deployer.rb +0 -198
- data/lib/spar/generators/application.rb +0 -42
- data/lib/spar/generators/templates/Gemfile +0 -3
- data/lib/spar/generators/templates/Rakefile +0 -8
- data/lib/spar/generators/templates/app/views/index.haml +0 -7
- data/lib/spar/generators/templates/config.ru +0 -9
- data/lib/spar/generators/templates/config/application.rb +0 -12
- data/lib/spar/generators/templates/script/server +0 -1
- data/lib/spar/static_compiler.rb +0 -91
data/README.md
CHANGED
@@ -1 +1,272 @@
|
|
1
|
-
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
Spar, the *Single Page Application Rocketship*, is an opinionated framework that aims to ease single-page web-app development by addressing major challenges:
|
4
|
+
|
5
|
+
* Asset organization
|
6
|
+
* Compilation pipeline
|
7
|
+
* Build & deployment support
|
8
|
+
|
9
|
+
Additionally, Spar provides templates & example projects for either a bare-bones project, or one with some of our favorite tools pre-included.
|
10
|
+
|
11
|
+
Under the hood, Spar is a Sprockets-based, Rails-derived project. Spar uses Sprockets to provide a powerful asset-pipeline, while stripping out legacy web-app support and introducing tools & patterns specific to single-page apps.
|
12
|
+
|
13
|
+
# Requirements
|
14
|
+
|
15
|
+
* Ruby 1.9.2 or greater (preferably 1.9.3).
|
16
|
+
|
17
|
+
# Installing Spar
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ gem install spar
|
21
|
+
````
|
22
|
+
|
23
|
+
If using `rbenv`:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
$ rbenv rehash
|
27
|
+
```
|
28
|
+
|
29
|
+
# Getting Started
|
30
|
+
|
31
|
+
Issue the following command to create a new Spar project:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
$ spar new myapp
|
35
|
+
$ cd myapp
|
36
|
+
$ spar server
|
37
|
+
```
|
38
|
+
Your app will now be available at [http://localhost:8888](http://localhost:8888)
|
39
|
+
|
40
|
+
# Organization
|
41
|
+
|
42
|
+
Spar apps are organized into the following folders and files:
|
43
|
+
|
44
|
+
/app #Compiled assets that make your app
|
45
|
+
/images
|
46
|
+
/javascripts
|
47
|
+
application.js.coffee #Your main JS output
|
48
|
+
/stylesheets
|
49
|
+
application.css.sass #Your main CSS output
|
50
|
+
/pages
|
51
|
+
index.html.haml #Your single, root HTML page
|
52
|
+
|
53
|
+
/static #File in here will be available to your application, but not managed as assets.
|
54
|
+
/downloads #File in here will include a 'Content-Disposition: attachment;' header if deployed to S3
|
55
|
+
|
56
|
+
/vendor #Put external libraries like jquery or bootstrap here
|
57
|
+
/javascripts
|
58
|
+
/stylesheets
|
59
|
+
|
60
|
+
config.yml #ENV settings for development, staging, production
|
61
|
+
README #your project's README
|
62
|
+
|
63
|
+
# Configuration
|
64
|
+
|
65
|
+
`config.yml` defines your project's configuration for different environments. You may define any properties you like, which are available to you in all your asset files.
|
66
|
+
|
67
|
+
These settings may be overriden on a per-environment basis for `development`, `staging`, and `production` like so:
|
68
|
+
|
69
|
+
```yaml
|
70
|
+
default:
|
71
|
+
debug: true
|
72
|
+
my_app_name: My App!
|
73
|
+
my_api: http://localhost:8080
|
74
|
+
|
75
|
+
staging:
|
76
|
+
debug: false
|
77
|
+
|
78
|
+
production:
|
79
|
+
debug: false
|
80
|
+
compress: true
|
81
|
+
my_api: http://production-api.mysite.com
|
82
|
+
```
|
83
|
+
|
84
|
+
Spar respects the following known configuration options:
|
85
|
+
|
86
|
+
- `debug`: true/false, includes JS files individually for easier debugging
|
87
|
+
- `digest`: true/false, adds MD5 hashes to end of filenames for proper cache busting in deploys
|
88
|
+
- `compress`: true/false, JS and CSS compression (uglify, and yahoo UI CSS compressor)
|
89
|
+
|
90
|
+
# Asset Pipeline
|
91
|
+
|
92
|
+
All asset files in the `app` directory are transformed through the Spar asset pipeline. Transformation first occurs for configuration properties defined in `config.yml`, followed by JS/CSS asset-compilation and composition.
|
93
|
+
|
94
|
+
## Configuration Property Replacement
|
95
|
+
|
96
|
+
First, configuration property substitution takes place according to your `config.yml` file. For instance, if your `index.html.haml` looks like this:
|
97
|
+
|
98
|
+
```haml
|
99
|
+
%html
|
100
|
+
%head
|
101
|
+
%title [{ my_app_name }]
|
102
|
+
```
|
103
|
+
it transforms to become:
|
104
|
+
|
105
|
+
```haml
|
106
|
+
%html
|
107
|
+
%head
|
108
|
+
%title My App!
|
109
|
+
```
|
110
|
+
|
111
|
+
## Compilation
|
112
|
+
|
113
|
+
After Spar performs configuration replacement, it then process files according to their extensions.
|
114
|
+
|
115
|
+
Inlcuded with Spar are transformations from:
|
116
|
+
|
117
|
+
- `file.html.haml` => `file.html`
|
118
|
+
- `file.js.coffee` => `file.js`
|
119
|
+
- `file.css.sass` => `file.css`
|
120
|
+
- `file.css.less` => `file.less`
|
121
|
+
|
122
|
+
## Dependency Management
|
123
|
+
|
124
|
+
Multiple Javascript (or CSS) files can be merged into a single file using the `require` and `require_tree` pre-processor directives.
|
125
|
+
|
126
|
+
If you want to serve one file, say `application.js`, that includes the content of multiple JS files, you may define an `application.js.coffee` like so:
|
127
|
+
|
128
|
+
```coffeescript
|
129
|
+
# This file will be compiled into application.js, which
|
130
|
+
# will include all the files below required below
|
131
|
+
|
132
|
+
# The require directive can include individual file assets
|
133
|
+
# For instance, to include the compiled output of utils.js.coffee
|
134
|
+
#= require utils
|
135
|
+
|
136
|
+
# You can also recursively includes entire directories
|
137
|
+
# using the require_tree directive
|
138
|
+
#= require_tree ./controllers
|
139
|
+
#= require_tree ./models
|
140
|
+
#= require_tree ./views
|
141
|
+
```
|
142
|
+
Stylesheet files are composed similarly, however directives should be placed in CSS/SASS comments appropriately:
|
143
|
+
|
144
|
+
```css
|
145
|
+
/*= require buttons.css */
|
146
|
+
```
|
147
|
+
|
148
|
+
## Deploy Directive
|
149
|
+
Most Spar apps will compile into a single `application.js` and `application.css` file, each including the `deploy` directive to ensure it is deployed.
|
150
|
+
|
151
|
+
If you wish to deploy additional root-level asset files, you may instruct Spar to do so by adding a `deploy` directive at the top of the file like so:
|
152
|
+
|
153
|
+
```coffeescript
|
154
|
+
#= deploy
|
155
|
+
```
|
156
|
+
|
157
|
+
Or, in CSS:
|
158
|
+
|
159
|
+
```css
|
160
|
+
/*= deploy */
|
161
|
+
```
|
162
|
+
You only need to do this for additional root-level Javascript and CSS based files, as Spar deploys all images, pages, and static files automatically.
|
163
|
+
|
164
|
+
# Example Spar Applications
|
165
|
+
|
166
|
+
For your reference, and to build on top of, we've created two example applications using Spar.
|
167
|
+
|
168
|
+
The first is the quintessential TODO application as popularized by [addyosmani](http://addyosmani.github.com/todomvc/). The second, is a bootstrap application containing some of our favorite tools to kick-start the pretty (jQuery, Backbone, and Twitter Bootstrap).
|
169
|
+
|
170
|
+
Both can be found at our [spar-examples](https://github.com/BoundlessLearning/spar-examples) repo.
|
171
|
+
|
172
|
+
# Deploying Your Spar App
|
173
|
+
|
174
|
+
Spar supports three different deployment methods out of the box:
|
175
|
+
|
176
|
+
* `local`: Deploy your app to a directory local to your computer.
|
177
|
+
* `s3`: Deploy your app to an AWS S3 bucket.
|
178
|
+
* `cloudfront`: Deploy your app to an AWS S3 bucket and invalidate a Cloudfront distribution.
|
179
|
+
|
180
|
+
To deploy your app, run:
|
181
|
+
|
182
|
+
```bash
|
183
|
+
spar deploy poduction
|
184
|
+
```
|
185
|
+
|
186
|
+
You can pass any environment name to the deploy command, typically `staging` or `production`.
|
187
|
+
|
188
|
+
## Local Deployment
|
189
|
+
|
190
|
+
To deploy to a local directory, setup your `config.yml` file environments like so:
|
191
|
+
|
192
|
+
```yaml
|
193
|
+
default:
|
194
|
+
debug: true
|
195
|
+
|
196
|
+
staging:
|
197
|
+
deploy_strategy: local
|
198
|
+
deploy_path: compiled/staging
|
199
|
+
|
200
|
+
production:
|
201
|
+
deploy_strategy: local
|
202
|
+
deploy_path: compiled/production
|
203
|
+
```
|
204
|
+
|
205
|
+
The `deploy_path` may be either a relative path in your application or a global path on your computer.
|
206
|
+
|
207
|
+
## S3 Deployment
|
208
|
+
|
209
|
+
To deploy to an S3 bucket, setup your environments like so:
|
210
|
+
|
211
|
+
```yaml
|
212
|
+
production:
|
213
|
+
deploy_strategy: s3
|
214
|
+
aws_key: "my_access_key"
|
215
|
+
aws_secret: "my+super+secret+access+key"
|
216
|
+
deploy_bucket: "mysite.test.com"
|
217
|
+
```
|
218
|
+
|
219
|
+
You'll need to enter your own credentials. You can find your S3 credentials on the [AWS Security Credentials](https://portal.aws.amazon.com/gp/aws/securityCredentials) page.
|
220
|
+
|
221
|
+
Next, you'll need visit the [AWS S3 Console](https://console.aws.amazon.com/s3/home) and create a bucket to host your app. We suggest using the same as your fully qualified domain name. You should not use this bucket for anything else.
|
222
|
+
|
223
|
+
![click here](http://spar-screenshots.s3.amazonaws.com/s3_click_here.png)
|
224
|
+
|
225
|
+
![create bucket](http://spar-screenshots.s3.amazonaws.com/s3_create_bucket.png)
|
226
|
+
|
227
|
+
Next, you'll need to turn on [S3 Website Hosting](http://aws.typepad.com/aws/2011/02/host-your-static-website-on-amazon-s3.html) in the S3 console.
|
228
|
+
|
229
|
+
![bucket properties](http://spar-screenshots.s3.amazonaws.com/s3_bucket_properties.png)
|
230
|
+
|
231
|
+
![enable website](http://spar-screenshots.s3.amazonaws.com/s3_enable_website.png)
|
232
|
+
|
233
|
+
You'll need to create a new `CNAME` record. How this works is up to your hosting provider, but it should look something like this.
|
234
|
+
|
235
|
+
app.example.com. IN CNAME app.example.com.s3-website-us-east-1.amazonaws.com.
|
236
|
+
|
237
|
+
### About Logging
|
238
|
+
|
239
|
+
You'll probably want to be able to log requests to your site. Even though your app uses cutting edge webscale tools like Airbrake, Loggly, Google Analytics, MixPanel, et al, eventually you'll want to know how many people hit you with IE6 or NoScript, and you gave them the middle finger.
|
240
|
+
|
241
|
+
Create a bucket log using the [AWS S3 Console](https://console.aws.amazon.com/s3/home). Give it a name like `logs.example.com` and update either your app's CloudFront distribution or your app's S3 bucket to write its log files to this log bucket.
|
242
|
+
|
243
|
+
![enable logging](http://spar-screenshots.s3.amazonaws.com/s3_enable_logging.png)
|
244
|
+
|
245
|
+
|
246
|
+
## CloudFront Deployment
|
247
|
+
|
248
|
+
Cloudfront deployment is very similar to S3 deployment, but you need to add a `cloudfront_distribution` property to your config file:
|
249
|
+
|
250
|
+
```yaml
|
251
|
+
production:
|
252
|
+
deploy_strategy: cloudfront
|
253
|
+
aws_key: "my_access_key"
|
254
|
+
aws_secret: "my+super+secret+access+key"
|
255
|
+
deploy_bucket: "mysite.test.com"
|
256
|
+
cloudfront_distribution: "distribution+id"
|
257
|
+
```
|
258
|
+
Cloudfront will turn your fast site into a *really* fast site. From the [AWS CloudFront Console](https://console.aws.amazon.com/cloudfront/home), create a new distribution *with the website form of your bucket name as the origin* and save the ID in your config.yml.
|
259
|
+
|
260
|
+
Take note of the **Domain Name** field (something like `d242ood0j0gl2v.cloudfront.net`). You will need to replace the CNAME you created earlier.
|
261
|
+
|
262
|
+
app.example.com. IN CNAME d242ood0j0gl2v.cloudfront.net.
|
263
|
+
|
264
|
+
Now, every time you deploy, Spar will automatically issue CloudFront invalidation requests for index.html (and anything else without a hash value). CloudFront invalidations usually take around 8 minutes, but they can take quite a bit lot longer when Amazon is having problems.
|
265
|
+
|
266
|
+
# Issues & Bugs
|
267
|
+
|
268
|
+
Please use our Github [issue tracker](https://github.com/BoundlessLearning/spar/issues) for Spar.
|
269
|
+
|
270
|
+
# License
|
271
|
+
|
272
|
+
Spar is licensed under the terms of the MIT License, see the included LICENSE file.
|
data/bin/spar
CHANGED
@@ -1,3 +1,32 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'rbconfig'
|
4
|
+
|
5
|
+
if RUBY_VERSION < '1.9.2'
|
6
|
+
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
|
7
|
+
abort <<-end_message
|
8
|
+
|
9
|
+
Spar requires Ruby 1.9.2+.
|
10
|
+
|
11
|
+
You're running
|
12
|
+
#{desc}
|
13
|
+
|
14
|
+
Please upgrade to continue.
|
15
|
+
|
16
|
+
end_message
|
17
|
+
end
|
18
|
+
Signal.trap("INT") { puts; exit(1) }
|
19
|
+
|
20
|
+
require 'bundler/setup'
|
21
|
+
require 'spar'
|
22
|
+
Bundler.require if File.exists?('Gemfile')
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'spar/cli'
|
26
|
+
Spar::CLI.start
|
27
|
+
rescue Interrupt => e
|
28
|
+
puts "\nStopping..."
|
29
|
+
exit 1
|
30
|
+
rescue SystemExit => e
|
31
|
+
exit e.status
|
32
|
+
end
|
data/lib/spar.rb
CHANGED
@@ -1,16 +1,137 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'pathname'
|
3
|
+
require 'sprockets'
|
4
|
+
require 'haml'
|
5
|
+
|
1
6
|
module Spar
|
2
7
|
autoload :Version, 'spar/version'
|
3
|
-
autoload :
|
4
|
-
autoload :
|
5
|
-
autoload :
|
8
|
+
autoload :CLI, 'spar/cli'
|
9
|
+
autoload :Rewrite, 'spar/rewrite'
|
10
|
+
autoload :Exceptions, 'spar/exceptions'
|
11
|
+
autoload :DirectiveProcessor, 'spar/directive_processor'
|
6
12
|
autoload :Helpers, 'spar/helpers'
|
7
|
-
autoload :
|
8
|
-
autoload :
|
13
|
+
autoload :Compressor, 'spar/compressor'
|
14
|
+
autoload :Compiler, 'spar/compiler'
|
15
|
+
autoload :CompiledAsset, 'spar/compiled_asset'
|
16
|
+
autoload :Deployer, 'spar/deployers/deployer'
|
17
|
+
autoload :Assets, 'spar/assets'
|
18
|
+
autoload :Static, 'spar/static'
|
19
|
+
autoload :NotFound, 'spar/not_found'
|
20
|
+
|
21
|
+
DEFAULTS = {
|
22
|
+
'digest' => false,
|
23
|
+
'debug' => true,
|
24
|
+
'compress' => false,
|
25
|
+
'js_compressor' => {
|
26
|
+
'mangle' => false
|
27
|
+
},
|
28
|
+
'css_compressor' => {},
|
29
|
+
'cache_control' => "public, max-age=#{60 * 60 * 24 * 7}"
|
30
|
+
}
|
31
|
+
|
32
|
+
def self.root
|
33
|
+
@root ||= begin
|
34
|
+
# Remove the line number from backtraces making sure we don't leave anything behind
|
35
|
+
call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
|
36
|
+
root_path = File.dirname(call_stack.detect { |p| p !~ %r[[\w.-]*/lib/spar|rack[\w.-]*/lib/rack] })
|
37
|
+
|
38
|
+
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.yml")
|
39
|
+
parent = File.dirname(root_path)
|
40
|
+
root_path = parent != root_path && parent
|
41
|
+
end
|
42
|
+
|
43
|
+
root = File.exist?("#{root_path}/config.yml") ? root_path : Dir.pwd
|
44
|
+
raise "Could not find root path for #{self}" unless root
|
45
|
+
|
46
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? Pathname.new(root).expand_path : Pathname.new(root).realpath
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.environment=(environment)
|
51
|
+
@environment = environment
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.environment
|
55
|
+
@environment ||= ENV['SPAR_ENV'] || ENV['RACK_ENV'] || 'development'
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.assets
|
59
|
+
@assets ||= Spar::Assets.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.static
|
63
|
+
@static ||= Spar::Static.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.not_found
|
67
|
+
@not_found ||= Spar::NotFound.new
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.sprockets
|
71
|
+
@sprockets ||= begin
|
72
|
+
env = Sprockets::Environment.new(root)
|
73
|
+
|
74
|
+
if settings['compress']
|
75
|
+
env.js_compressor = Compressor::JS.new
|
76
|
+
env.css_compressor = Compressor::CSS.new
|
77
|
+
end
|
78
|
+
|
79
|
+
child_folders = ['javascripts', 'stylesheets', 'images', 'pages', 'fonts']
|
80
|
+
|
81
|
+
for child_folder in child_folders
|
82
|
+
env.append_path(root.join('app', child_folder))
|
83
|
+
env.append_path(root.join('vendor', child_folder))
|
84
|
+
Gem.loaded_specs.each do |name, gem|
|
85
|
+
env.append_path(File.join(gem.full_gem_path, 'vendor', 'assets', child_folder))
|
86
|
+
env.append_path(File.join(gem.full_gem_path, 'app', 'assets', 'assets', child_folder))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
env.append_path(root.join('components'))
|
9
91
|
|
10
|
-
|
92
|
+
for path in (settings['paths'] || [])
|
93
|
+
env.append_path(root.join(*path.split('/')))
|
94
|
+
end
|
11
95
|
|
12
|
-
|
96
|
+
env.register_engine '.haml', Tilt::HamlTemplate
|
97
|
+
env.register_engine '.md', Tilt::BlueClothTemplate
|
98
|
+
env.register_engine '.textile', Tilt::RedClothTemplate
|
13
99
|
|
100
|
+
env.register_postprocessor 'text/css', Spar::DirectiveProcessor
|
101
|
+
env.register_postprocessor 'application/javascript', Spar::DirectiveProcessor
|
102
|
+
env.register_postprocessor 'text/html', Spar::DirectiveProcessor
|
103
|
+
|
104
|
+
env
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.app
|
109
|
+
app = Rack::Builder.new do
|
110
|
+
use Spar::Rewrite
|
111
|
+
use Spar::Exceptions
|
112
|
+
|
113
|
+
run Rack::Cascade.new([Spar.static, Spar.sprockets, Spar.not_found])
|
114
|
+
|
115
|
+
use Rack::ContentType
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.settings
|
120
|
+
@settings ||= load_config
|
14
121
|
end
|
15
122
|
|
123
|
+
protected
|
124
|
+
|
125
|
+
def self.load_config
|
126
|
+
pathname = Pathname.new(Spar.root).join("config.yml")
|
127
|
+
begin
|
128
|
+
yaml = YAML.load_file(pathname)
|
129
|
+
settings = DEFAULTS.merge(yaml['default'] || {}).merge(yaml[environment] || {})
|
130
|
+
settings['environment'] = environment
|
131
|
+
settings
|
132
|
+
rescue => e
|
133
|
+
raise "Could not load the config.yml file: #{e.message}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
16
137
|
end
|