vitrine 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -18
- data/lib/asset_compiler.rb +148 -0
- data/lib/version.rb +1 -1
- data/lib/vitrine.rb +5 -82
- data/test/test_vitrine.rb +6 -82
- data/test/test_vitrine_asset_compiler.rb +96 -0
- data/test/test_vitrine_in_rack_stack.rb +6 -3
- data/vitrine.gemspec +4 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ee018a5ccd0dc9855944a5383d88ea37bde9b68
|
4
|
+
data.tar.gz: 318885cc0cdb952a5e0da8374493ad73df9919f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3ffd8d9dc3d901e9f05c8b5abfd9ff0a94f860f910934918015b55eaf82382876baca2610b31e233f854377812c0ff181c3de74e9ef21b4b802f7ac4145403d
|
7
|
+
data.tar.gz: 87fd5a399ecd9d46ce855ff408361f7d226d6f1571ac3fad59ab82052e25c32ef105c5cc364381427878237aadd5db47282251a4f4d652552ce0f887a4b3b510
|
data/README.md
CHANGED
@@ -23,24 +23,16 @@ Vitrine assumes that there are two directories under the current tree:
|
|
23
23
|
|
24
24
|
Vitrine is for **development**. It takes runtime compilation to 11 and beyound. Running tasks
|
25
25
|
is all fine and good when you build out the app for production, but when iterating on UI it's essential
|
26
|
-
to be able to just yank the file
|
26
|
+
to be able to just yank the file in there and carry on. THe compilation perks include:
|
27
27
|
|
28
|
-
|
28
|
+
* Any `.scss` file you shove into the `public` directory can be referenced as `.css` from your HTML.
|
29
|
+
Ask for `foo.css` and `foo.scss` will be compiled on the fly.
|
30
|
+
* Any `.coffee` file you shove into the `public` directory can be references as `.js` from your HTML.
|
31
|
+
Ask for `bar.js` and `bar.coffee` will be compiled on the fly.
|
32
|
+
* CoffeeScript files will have source-maps out of the box for pleasant browser debugging.
|
33
|
+
* Decent error messages will be shown for both invalid SCSS and invalid CoffeeScript.
|
29
34
|
|
30
|
-
|
31
|
-
Vitrine will automatically compile it via SASS.
|
32
|
-
|
33
|
-
### For CoffeeScript, with source maps
|
34
|
-
|
35
|
-
Same thing applies to CoffeeScript - put `.coffee` files in "public", and reference them as `.js` files.
|
36
|
-
Vitrine will generate you source maps on the fly for pleasant browser debugging.
|
37
|
-
|
38
|
-
## Sensible error messages when automatic compilation fails
|
39
|
-
|
40
|
-
Vitrine will try to show you sensible errors if your SCSS or CoffeeScript fail to compile due to syntax errors and
|
41
|
-
the like. CoffeeScript errors go to the browser console, and Sass errors go in a generated element on top of your page.
|
42
|
-
|
43
|
-
## Do not recompile on every request
|
35
|
+
## Asset caching
|
44
36
|
|
45
37
|
Succesfully compiled assets will be cached to save time on next reload, and ETagged based on their
|
46
38
|
mtime.
|
@@ -51,8 +43,8 @@ If you have the "views" directory available, Vitrine will try to pick up any usa
|
|
51
43
|
From there on, it's going to try to render it with the automatically picked template engine using the
|
52
44
|
standard Sinatra facilities. You can use HAML, LESS, Slim, ERB, Builder or anything else you like.
|
53
45
|
|
54
|
-
If you are writing an SPA, you can make a file called "catch_all.erb" which is going to be
|
55
|
-
for all missing URLs without extension.
|
46
|
+
If you are writing an SPA, you can make a file called "catch_all.erb" which is going to be
|
47
|
+
the fall-through template for all missing URLs without extension.
|
56
48
|
|
57
49
|
## Automatic reload via Guard
|
58
50
|
|
@@ -80,6 +72,15 @@ forget to set `:root` - like so:
|
|
80
72
|
vitrine.settings.set :root => File.dirname(__FILE__)
|
81
73
|
end
|
82
74
|
|
75
|
+
You can also only opt-in to the asset compilation system of Vitrine only once you have migrated your app from
|
76
|
+
the prototype stage into, say, a Sinatra application.
|
77
|
+
|
78
|
+
Note that you _need_ to have an `ExecJS` environment on your server for this:
|
79
|
+
|
80
|
+
use Vitrine::AssetCompiler.new do | ac |
|
81
|
+
vitrine.settings.set :root => File.dirname(__FILE__)
|
82
|
+
end
|
83
|
+
|
83
84
|
## Contributing to vitrine
|
84
85
|
|
85
86
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# The part of Vitrine responsible for SASS and CoffeeScript
|
2
|
+
# compilation and caching. By default it will assume your
|
3
|
+
# public directory is set on the inner app in the Rack stack,
|
4
|
+
# and can be retreived using the standard Sinatra settings
|
5
|
+
# protocol, like so:
|
6
|
+
#
|
7
|
+
# @app.settings.public_folder
|
8
|
+
#
|
9
|
+
# However, you can also set this setting on the app object using the
|
10
|
+
# AssetCompiler#public_dir accessor.
|
11
|
+
#
|
12
|
+
# use Vitrine::AssetCompiler do | compiler |
|
13
|
+
# compiler.public_dir = File.dirname(__FILE__) + '/public'
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# This allows you to use the asset compiler when the inner app
|
17
|
+
# is not a Sinatra application.
|
18
|
+
#
|
19
|
+
# Obviously, the usual limitation apply for this kind of workflow -
|
20
|
+
# you pretty much have to have an ExecJS env on yourserver, or else...
|
21
|
+
class Vitrine::AssetCompiler < Sinatra::Base
|
22
|
+
set :show_exceptions, false
|
23
|
+
set :raise_errors, true
|
24
|
+
|
25
|
+
# An explicit override for +public_folder+ setting,
|
26
|
+
# if set will take precedence over the setting
|
27
|
+
attr_accessor :public_dir
|
28
|
+
|
29
|
+
# Try to find SCSS replacement for missing CSS
|
30
|
+
get /(.+)\.css/ do | basename |
|
31
|
+
begin
|
32
|
+
content_type 'text/css', :charset => 'utf-8'
|
33
|
+
# TODO: has no handling for .sass
|
34
|
+
scss_source_path = File.join(get_public, "#{basename}.scss")
|
35
|
+
mtime_cache(scss_source_path) do
|
36
|
+
# TODO: Examine http://sass-lang.com/documentation/file.SASS_REFERENCE.html
|
37
|
+
# It already has provisions for error display, among other things
|
38
|
+
Sass.compile_file(scss_source_path, cache_location: '/tmp/vitrine/sass-cache')
|
39
|
+
end
|
40
|
+
rescue Errno::ENOENT # Missing SCSS
|
41
|
+
forward_or_halt "No such CSS or SCSS file found"
|
42
|
+
rescue Exception => e # CSS syntax error or something alike
|
43
|
+
# Add a generated DOM element before <body/> to inject
|
44
|
+
# a visible error message
|
45
|
+
error_tpl = 'body:before {
|
46
|
+
background: white; padding: 3px; font-family: monospaced; color: red;
|
47
|
+
font-size: 14px; content: %s }'
|
48
|
+
css_message = error_tpl % [e.class, "\n", "--> ", e.message].join.inspect
|
49
|
+
# If we halt with 500 this will not be shown as CSS
|
50
|
+
halt 200, css_message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate a sourcemap for CoffeeScript files
|
55
|
+
get /(.+)\.js\.map$/ do | basename |
|
56
|
+
begin
|
57
|
+
coffee_source = File.join(get_public, "#{basename}.coffee")
|
58
|
+
content_type 'application/json', :charset => 'utf-8'
|
59
|
+
mtime_cache(coffee_source) do
|
60
|
+
Vitrine.build_coffeescript_source_map_body(coffee_source, get_public)
|
61
|
+
end
|
62
|
+
rescue Errno::ENOENT # Missing CoffeeScript
|
63
|
+
forward_or_halt "No coffeescript file found to generate the map for"
|
64
|
+
rescue Exception => e # CS syntax error or something alike
|
65
|
+
halt 400, 'Compliation of the related CoffeeScript file failed'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Try to find CoffeeScript replacement for missing JS
|
70
|
+
get /(.+)\.js$/ do | basename |
|
71
|
+
# If this file is not found resort back to a coffeescript
|
72
|
+
begin
|
73
|
+
coffee_source = File.join(get_public, "#{basename}.coffee")
|
74
|
+
content_type 'text/javascript'
|
75
|
+
mtime_cache(coffee_source) do
|
76
|
+
source_body = File.read(coffee_source)
|
77
|
+
# We could have sent a header, but it's a nice idea to have the
|
78
|
+
# sourcemap header saved if we write out the compiled JS,
|
79
|
+
# whereas otherwise it would have been discarded
|
80
|
+
[
|
81
|
+
"//# sourceMappingURL=#{basename}.js.map",
|
82
|
+
CoffeeScript.compile(source_body)
|
83
|
+
].join("\n")
|
84
|
+
end
|
85
|
+
rescue Errno::ENOENT # Missing CoffeeScript
|
86
|
+
forward_or_halt "No such JS file and could not find a .coffee replacement"
|
87
|
+
rescue Exception => e # CS syntax error or something alike
|
88
|
+
# Inject the syntax error into the browser console
|
89
|
+
console_message = 'console.error(%s)' % [e.class, "\n", "--> ", e.message].join.inspect
|
90
|
+
# Avoid 500 because it plays bad with LiveReload
|
91
|
+
halt 200, console_message
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def mtime_cache(path, &blk)
|
96
|
+
# Mix in the request URL into the cache key so that we can hash
|
97
|
+
# .map sourcemaps and .js compiles based off of the same file path
|
98
|
+
# and mtime
|
99
|
+
key = [File.expand_path(path), File.mtime(path), request.path_info, get_public]
|
100
|
+
cache_sha = Digest::SHA1.hexdigest(Marshal.dump(key))
|
101
|
+
|
102
|
+
# Store in a temp dir
|
103
|
+
FileUtils.mkdir_p '/tmp/vitrine'
|
104
|
+
p = '/tmp/vitrine/%s' % cache_sha
|
105
|
+
|
106
|
+
# Only write it out unless a file with the same SHA does not exist
|
107
|
+
unless File.exist?(p)
|
108
|
+
Vitrine.atomic_write(p) do |f|
|
109
|
+
log "---> Recompiling #{path} for #{request.path_info}"
|
110
|
+
f.write(yield)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# And send out the file that's been written
|
115
|
+
last_modified(File.mtime(p))
|
116
|
+
etag File.mtime(p).to_i.to_s
|
117
|
+
File.read(p)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Get path to the public directory, trying (in order:)
|
122
|
+
# self.public_dir reader
|
123
|
+
# the inner app's public_folder setting
|
124
|
+
# my own public_folder setting
|
125
|
+
def get_public
|
126
|
+
inner_public = if @app && @app.respond_to?(:settings)
|
127
|
+
@app.settings.public_folder
|
128
|
+
else
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
choices = [@public_dir, inner_public, settings.public_dir]
|
132
|
+
choices.compact.shift
|
133
|
+
end
|
134
|
+
|
135
|
+
def forward_or_halt msg
|
136
|
+
if @app
|
137
|
+
log "Forwarding, #{msg} -> pub #{get_public.inspect}"
|
138
|
+
forward
|
139
|
+
else
|
140
|
+
halt 404, msg
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def log(mgs)
|
145
|
+
env['captivity.logger'].debug(msg) if env['captivity.logger']
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
data/lib/version.rb
CHANGED
data/lib/vitrine.rb
CHANGED
@@ -3,10 +3,12 @@ require 'coffee-script'
|
|
3
3
|
require 'rack/contrib/try_static'
|
4
4
|
require 'sass'
|
5
5
|
require 'pathname'
|
6
|
+
require 'fileutils'
|
6
7
|
|
7
8
|
require_relative 'version'
|
8
9
|
require_relative 'atomic_write'
|
9
10
|
require_relative 'sourcemaps'
|
11
|
+
require_relative 'asset_compiler'
|
10
12
|
|
11
13
|
# A little idiosyncrastic asset server.
|
12
14
|
# Does very simple things:
|
@@ -14,14 +16,15 @@ require_relative 'sourcemaps'
|
|
14
16
|
# * automatic compilation of CoffeeScript and SASS assets - just request them with .js and .css
|
15
17
|
# and Vitrine will find them and compile them for you on the spot
|
16
18
|
class Vitrine::App < Sinatra::Base
|
17
|
-
set :static, true
|
18
19
|
|
19
20
|
set :show_exceptions, false
|
20
21
|
set :raise_errors, true
|
21
22
|
|
22
23
|
# Sets whether Vitrine will output messages about dynamic assets
|
23
24
|
set :silent, true
|
24
|
-
set :public_folder, ->{ File.join(settings.root, 'public') }
|
25
|
+
set :public_folder, ->{ File.join(settings.root, 'public') }
|
26
|
+
|
27
|
+
use Vitrine::AssetCompiler
|
25
28
|
|
26
29
|
# For extensionless things try to pick out the related templates
|
27
30
|
# from the views directory, and render them with a default layout.
|
@@ -88,88 +91,8 @@ class Vitrine::App < Sinatra::Base
|
|
88
91
|
template_engine = File.extname(template_path).gsub(/^\./, '')
|
89
92
|
render(template_engine, File.read(template_path), :layout => get_layout, :locals => locals)
|
90
93
|
end
|
91
|
-
|
92
|
-
|
93
|
-
# Try to find SCSS replacement for missing CSS
|
94
|
-
get /(.+)\.css/ do | basename |
|
95
|
-
begin
|
96
|
-
content_type 'text/css', :charset => 'utf-8'
|
97
|
-
# TODO: has no handling for .sass
|
98
|
-
scss_source_path = File.join(settings.root, 'public', "#{basename}.scss")
|
99
|
-
mtime_cache(scss_source_path) do
|
100
|
-
# TODO: Examine http://sass-lang.com/documentation/file.SASS_REFERENCE.html
|
101
|
-
Sass.compile_file(scss_source_path, cache_location: '/tmp/vitrine/sass-cache')
|
102
|
-
end
|
103
|
-
rescue Errno::ENOENT # Missing SCSS
|
104
|
-
halt 404, "No such CSS or SCSS file found"
|
105
|
-
rescue Exception => e # CSS syntax error or something alike
|
106
|
-
# Add a generated DOM element before <body/> to inject
|
107
|
-
# a visible error message
|
108
|
-
error_tpl = 'body:before { background: white; font-family: sans-serif; color: red; font-size: 14px; content: %s }'
|
109
|
-
css_message = error_tpl % [e.class, "\n", "--> ", e.message].join.inspect
|
110
|
-
# If we halt with 500 this will not be shown as CSS
|
111
|
-
halt 200, css_message
|
112
|
-
end
|
113
|
-
end
|
114
94
|
|
115
|
-
# Generate a sourcemap for CoffeeScript files
|
116
|
-
get /(.+)\.js\.map$/ do | basename |
|
117
|
-
begin
|
118
|
-
coffee_source = File.join(settings.root, 'public', "#{basename}.coffee")
|
119
|
-
content_type 'application/json', :charset => 'utf-8'
|
120
|
-
mtime_cache(coffee_source) do
|
121
|
-
Vitrine.build_coffeescript_source_map_body(coffee_source, File.join(settings.root, 'public'))
|
122
|
-
end
|
123
|
-
rescue Errno::ENOENT # Missing CoffeeScript
|
124
|
-
halt 404, "No coffeescript file found to generate the map for"
|
125
|
-
rescue Exception => e # CS syntax error or something alike
|
126
|
-
halt 400, 'Compliation of the related CoffeeScript file failed'
|
127
|
-
end
|
128
|
-
end
|
129
95
|
|
130
|
-
# Try to find CoffeeScript replacement for missing JS
|
131
|
-
get /(.+)\.js$/ do | basename |
|
132
|
-
# If this file is not found resort back to a coffeescript
|
133
|
-
begin
|
134
|
-
coffee_source = File.join(settings.root, 'public', "#{basename}.coffee")
|
135
|
-
content_type 'text/javascript'
|
136
|
-
mtime_cache(coffee_source) do
|
137
|
-
["//# sourceMappingURL=#{basename}.js.map", CoffeeScript.compile(File.read(coffee_source))].join("\n")
|
138
|
-
end
|
139
|
-
rescue Errno::ENOENT # Missing CoffeeScript
|
140
|
-
halt 404, "No such JS file and could not find a .coffee replacement"
|
141
|
-
rescue Exception => e # CS syntax error or something alike
|
142
|
-
# Inject the syntax error into the browser console
|
143
|
-
console_message = 'console.error(%s)' % [e.class, "\n", "--> ", e.message].join.inspect
|
144
|
-
halt 500, console_message
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
require 'fileutils'
|
149
|
-
|
150
|
-
def mtime_cache(path, &blk)
|
151
|
-
# Mix in the request URL into the cache key so that we can hash
|
152
|
-
# .map sourcemaps and .js compiles based off of the same file path
|
153
|
-
# and mtime
|
154
|
-
key = [File.expand_path(path), File.mtime(path), request.path_info, settings.root]
|
155
|
-
cache_sha = Digest::SHA1.hexdigest(Marshal.dump(key))
|
156
|
-
|
157
|
-
# Store in a temp dir
|
158
|
-
FileUtils.mkdir_p '/tmp/vitrine'
|
159
|
-
p = '/tmp/vitrine/%s' % cache_sha
|
160
|
-
if File.exist?(p)
|
161
|
-
etag File.mtime(p)
|
162
|
-
File.read(p)
|
163
|
-
else
|
164
|
-
yield.tap do | body |
|
165
|
-
Vitrine.atomic_write(p) do |f|
|
166
|
-
log "---> Recompiling #{path} for #{request.path_info}"
|
167
|
-
f.write body
|
168
|
-
end
|
169
|
-
etag File.mtime(p)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
96
|
|
174
97
|
def get_layout
|
175
98
|
layouts = Dir.glob(File.join(settings.views, 'layout.*'))
|
data/test/test_vitrine.rb
CHANGED
@@ -77,70 +77,9 @@ class TestVitrine < Test::Unit::TestCase
|
|
77
77
|
end
|
78
78
|
|
79
79
|
get '/nice.js'
|
80
|
-
|
81
|
-
assert_not_nil last_response.headers['ETag'], 'Should set ETag for the compiled version'
|
82
|
-
assert_equal 200, last_response.status
|
83
|
-
assert_equal 'text/javascript;charset=utf-8', last_response.content_type
|
84
|
-
|
85
|
-
assert last_response.body.include?( 'alert("rockage!")'), 'Should include the compiled function'
|
86
|
-
assert last_response.body.include?( '//# sourceMappingURL=/nice.js.map'),
|
87
|
-
'Should include the reference to the source map'
|
88
|
-
end
|
89
|
-
|
90
|
-
def test_compiles_coffeescript_sourcemap
|
91
|
-
|
92
|
-
FileUtils.mkdir_p File.join(@tempdir, 'public', 'js')
|
93
|
-
|
94
|
-
write_public 'js/nice.coffee' do | f |
|
95
|
-
f.puts 'alert "rockage!"'
|
96
|
-
end
|
97
|
-
|
98
|
-
# Sourcemap will only ever get requested AFTER the corresponding JS file
|
99
|
-
get '/js/nice.js'
|
100
|
-
assert last_response.ok?
|
101
|
-
|
102
|
-
get '/js/nice.js.map'
|
103
|
-
ref = {"version"=>3, "file"=>"", "sourceRoot"=>"", "sources"=>["/js/nice.coffee"],
|
104
|
-
"names"=>[], "mappings"=>"AAAA;CAAA,CAAA,GAAA,KAAA;CAAA"}
|
105
|
-
assert_equal ref, JSON.parse(last_response.body)
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_sends_vanilla_js_if_its_present
|
109
|
-
write_public 'vanilla.js' do | f |
|
110
|
-
f.puts 'vanilla();'
|
111
|
-
end
|
112
|
-
|
113
|
-
get '/vanilla.js'
|
114
|
-
assert_equal 200, last_response.status
|
115
|
-
assert_equal "vanilla();\n", last_response.body
|
116
|
-
end
|
117
|
-
|
118
|
-
def test_invalid_coffeescript_creates_decent_error_alerts
|
119
|
-
write_public 'faulty.coffee' do | f |
|
120
|
-
f.puts 'function() { junked up }'
|
121
|
-
end
|
122
|
-
|
123
|
-
get '/faulty.js'
|
124
|
-
|
125
|
-
assert_equal 500, last_response.status
|
126
|
-
assert_equal 'text/javascript;charset=utf-8', last_response.content_type
|
127
|
-
err = 'console.error("ExecJS::RuntimeError\n--> SyntaxError: reserved word \"function\"")'
|
128
|
-
assert_equal err, last_response.body
|
80
|
+
assert_include last_response.body, 'alert("rockage!")', 'Should include the compiled function'
|
129
81
|
end
|
130
82
|
|
131
|
-
def test_caches_compiled_js_by_etag_and_responds_with_304_when_requested_again
|
132
|
-
write_public 'nice.coffee' do | f |
|
133
|
-
f.puts 'alert "rockage!"'
|
134
|
-
end
|
135
|
-
|
136
|
-
get '/nice.js'
|
137
|
-
assert_equal 200, last_response.status
|
138
|
-
assert_not_nil last_response.headers['ETag']
|
139
|
-
|
140
|
-
etag = last_response.headers['ETag']
|
141
|
-
get '/nice.js', {}, rack_env = {'HTTP_IF_NONE_MATCH' => etag}
|
142
|
-
assert_equal 304, last_response.status
|
143
|
-
end
|
144
83
|
|
145
84
|
def test_sends_vanilla_css_if_present
|
146
85
|
write_public 'vanilla.css' do | f |
|
@@ -153,29 +92,14 @@ class TestVitrine < Test::Unit::TestCase
|
|
153
92
|
assert_equal '/* vanilla CSS kode */', last_response.body
|
154
93
|
end
|
155
94
|
|
156
|
-
def test_compiles_scss_when_requested_as_css
|
157
|
-
write_public 'styles.scss' do | f |
|
158
|
-
f.puts '.foo {'
|
159
|
-
f.puts '.bar { font-size: 10px; }'
|
160
|
-
f.puts '}'
|
161
|
-
end
|
162
|
-
|
163
|
-
get '/styles.css'
|
164
|
-
|
165
|
-
assert last_response.ok?
|
166
|
-
assert_not_nil last_response.headers['ETag'], 'Should set ETag for the compiled version'
|
167
|
-
assert last_response.body.include?('.foo .bar {'), 'Should have compiled the CSS rule'
|
168
|
-
end
|
169
95
|
|
170
|
-
def
|
171
|
-
write_public '
|
172
|
-
f.puts '
|
96
|
+
def test_sends_vanilla_js_if_its_present
|
97
|
+
write_public 'vanilla.js' do | f |
|
98
|
+
f.puts 'vanilla();'
|
173
99
|
end
|
174
100
|
|
175
|
-
get '/
|
176
|
-
|
101
|
+
get '/vanilla.js'
|
177
102
|
assert_equal 200, last_response.status
|
178
|
-
|
179
|
-
assert last_response.body.include?('Sass::SyntaxError'), 'Should include the syntax error class'
|
103
|
+
assert_equal "vanilla();\n", last_response.body
|
180
104
|
end
|
181
105
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
class TestVitrineAssetCompiler < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods, VitrineTesting
|
5
|
+
|
6
|
+
def app # overridden
|
7
|
+
Vitrine::AssetCompiler.new.tap { |a| a.settings.set :root, @tempdir }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_compiles_coffeescript_to_js_when_addressed_by_js_extension
|
11
|
+
write_public 'nice.coffee' do | f |
|
12
|
+
f.puts 'alert "rockage!"'
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/nice.js'
|
16
|
+
|
17
|
+
assert_not_nil last_response.headers['ETag'], 'Should set ETag for the compiled version'
|
18
|
+
assert_equal 200, last_response.status
|
19
|
+
assert_equal 'text/javascript;charset=utf-8', last_response.content_type
|
20
|
+
|
21
|
+
assert last_response.body.include?( 'alert("rockage!")'), 'Should include the compiled function'
|
22
|
+
assert last_response.body.include?( '//# sourceMappingURL=/nice.js.map'),
|
23
|
+
'Should include the reference to the source map'
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_compiles_coffeescript_sourcemap
|
27
|
+
|
28
|
+
FileUtils.mkdir_p File.join(@tempdir, 'public', 'js')
|
29
|
+
|
30
|
+
write_public 'js/nice.coffee' do | f |
|
31
|
+
f.puts 'alert "rockage!"'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sourcemap will only ever get requested AFTER the corresponding JS file
|
35
|
+
get '/js/nice.js'
|
36
|
+
assert last_response.ok?
|
37
|
+
|
38
|
+
get '/js/nice.js.map'
|
39
|
+
ref = {"version"=>3, "file"=>"", "sourceRoot"=>"", "sources"=>["/js/nice.coffee"],
|
40
|
+
"names"=>[], "mappings"=>"AAAA;CAAA,CAAA,GAAA,KAAA;CAAA"}
|
41
|
+
assert_equal ref, JSON.parse(last_response.body)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_invalid_coffeescript_creates_decent_error_alerts
|
45
|
+
write_public 'faulty.coffee' do | f |
|
46
|
+
f.puts 'function() { junked up }'
|
47
|
+
end
|
48
|
+
|
49
|
+
get '/faulty.js'
|
50
|
+
|
51
|
+
assert_equal 200, last_response.status
|
52
|
+
assert_equal 'text/javascript;charset=utf-8', last_response.content_type
|
53
|
+
err = 'console.error("ExecJS::RuntimeError\n--> SyntaxError: reserved word \"function\"")'
|
54
|
+
assert_equal err, last_response.body
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_caches_compiled_js_by_etag_and_responds_with_304_when_requested_again
|
58
|
+
write_public 'nice.coffee' do | f |
|
59
|
+
f.puts 'alert "rockage!"'
|
60
|
+
end
|
61
|
+
|
62
|
+
get '/nice.js'
|
63
|
+
assert_equal 200, last_response.status
|
64
|
+
assert_not_nil last_response.headers['ETag']
|
65
|
+
|
66
|
+
etag = last_response.headers['ETag']
|
67
|
+
get '/nice.js', {}, rack_env = {'HTTP_IF_NONE_MATCH' => etag}
|
68
|
+
assert_equal 304, last_response.status
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_compiles_scss_when_requested_as_css
|
72
|
+
write_public 'styles.scss' do | f |
|
73
|
+
f.puts '.foo {'
|
74
|
+
f.puts '.bar { font-size: 10px; }'
|
75
|
+
f.puts '}'
|
76
|
+
end
|
77
|
+
|
78
|
+
get '/styles.css'
|
79
|
+
|
80
|
+
assert last_response.ok?
|
81
|
+
assert_not_nil last_response.headers['ETag'], 'Should set ETag for the compiled version'
|
82
|
+
assert last_response.body.include?('.foo .bar {'), 'Should have compiled the CSS rule'
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_displays_decent_alerts_for_scss_errors
|
86
|
+
write_public 'faulty.scss' do | f |
|
87
|
+
f.puts '.foo {{ junkiness-factor: 24pem; }'
|
88
|
+
end
|
89
|
+
|
90
|
+
get '/faulty.css'
|
91
|
+
|
92
|
+
assert_equal 200, last_response.status
|
93
|
+
assert last_response.body.include?('body:before {'), 'Should include the generated element selector'
|
94
|
+
assert last_response.body.include?('Sass::SyntaxError'), 'Should include the syntax error class'
|
95
|
+
end
|
96
|
+
end
|
@@ -7,9 +7,12 @@ class TestVitrineInRackStack < Test::Unit::TestCase
|
|
7
7
|
def app
|
8
8
|
td = temporary_app_dir
|
9
9
|
outer = Rack::Builder.new do
|
10
|
-
|
11
|
-
|
10
|
+
# The outer app
|
11
|
+
use Vitrine::App do |v|
|
12
|
+
v.settings.set root: td
|
12
13
|
end
|
14
|
+
|
15
|
+
# And a lobster downstream
|
13
16
|
map "/lobster" do
|
14
17
|
run Rack::Lobster.new
|
15
18
|
end
|
@@ -25,7 +28,7 @@ class TestVitrineInRackStack < Test::Unit::TestCase
|
|
25
28
|
|
26
29
|
def test_fetch_js
|
27
30
|
write_public('hello.coffee') do | f |
|
28
|
-
f << 'window.alert
|
31
|
+
f << 'window.alert "Hello Coffee"'
|
29
32
|
end
|
30
33
|
|
31
34
|
get '/hello.js'
|
data/vitrine.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "vitrine"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.14"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Julik Tarkhanov"]
|
12
|
-
s.date = "2013-12-
|
12
|
+
s.date = "2013-12-23"
|
13
13
|
s.description = " Serves ERB templates with live CoffeeScript and SASS "
|
14
14
|
s.email = "me@julik.nl"
|
15
15
|
s.executables = ["vitrine"]
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"README.md",
|
26
26
|
"Rakefile",
|
27
27
|
"bin/vitrine",
|
28
|
+
"lib/asset_compiler.rb",
|
28
29
|
"lib/atomic_write.rb",
|
29
30
|
"lib/server.rb",
|
30
31
|
"lib/sourcemaps.rb",
|
@@ -32,6 +33,7 @@ Gem::Specification.new do |s|
|
|
32
33
|
"lib/vitrine.rb",
|
33
34
|
"test/helper.rb",
|
34
35
|
"test/test_vitrine.rb",
|
36
|
+
"test/test_vitrine_asset_compiler.rb",
|
35
37
|
"test/test_vitrine_in_rack_stack.rb",
|
36
38
|
"vitrine.gemspec"
|
37
39
|
]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vitrine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -222,6 +222,7 @@ files:
|
|
222
222
|
- README.md
|
223
223
|
- Rakefile
|
224
224
|
- bin/vitrine
|
225
|
+
- lib/asset_compiler.rb
|
225
226
|
- lib/atomic_write.rb
|
226
227
|
- lib/server.rb
|
227
228
|
- lib/sourcemaps.rb
|
@@ -229,6 +230,7 @@ files:
|
|
229
230
|
- lib/vitrine.rb
|
230
231
|
- test/helper.rb
|
231
232
|
- test/test_vitrine.rb
|
233
|
+
- test/test_vitrine_asset_compiler.rb
|
232
234
|
- test/test_vitrine_in_rack_stack.rb
|
233
235
|
- vitrine.gemspec
|
234
236
|
homepage: http://github.com/julik/vitrine
|