phrender 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 092133a20cb9d29d3b2a8867224a38851c3dd891
4
+ data.tar.gz: 0a2f321d07614c3e4e744419c1df287d93588772
5
+ SHA512:
6
+ metadata.gz: d4d79647ef74036f80da6c17ea500fea90893da54989e90146947180a3f2a40231a0c38e4b75c6160f8841ae38d9ee714cc8feea1f138b3ac1c89225e5b7154e
7
+ data.tar.gz: 33e1fa532034a483a3a7e3342c41a66be0b12008be3be5a7fba43e0e4a8fb7002d0072f99b664812bb1b16f81e9e949db4f83ddef99744a1de5341c24419025d
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ *.gem
2
+ .bundle
3
+ .config
4
+ Gemfile.lock
5
+ doc/
6
+ pkg
7
+ rdoc
8
+ spec/reports
9
+ test/tmp
10
+ test/version_tmp
11
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in phrender.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 theScore Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Phrender
2
+
3
+ A rack app and api to render javascript based websites using PhantomJS.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'phrender'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install phrender
18
+
19
+ Then install PhantomJS:
20
+
21
+ $ brew install phantomjs
22
+
23
+ ## Usage
24
+
25
+ *NOTE*: Ember users, there is a driver for ember. See the [Ember Driver](#Ember
26
+ Driver) section.
27
+
28
+ ### For completely static javascript sites:
29
+
30
+ In your rackup file:
31
+
32
+ ```ruby
33
+ require 'phrender'
34
+
35
+ # Available options are timeout, which gets passed on to PhantomJS. The asset
36
+ # root should reflect where your app is stored, as well as all images, fonts,
37
+ # css, and other assets.
38
+ phrender = Phrender::RackStatic.new("asset_root", options)
39
+
40
+ # The index file should link to no javascript on its own, or include any script
41
+ # tags.
42
+ phrender.index_file 'phrender.html'
43
+
44
+ # This is the main application. It may or may not also start up the app.
45
+ phrender.add_javascript_file '/assets/application.js'
46
+
47
+ # This is just raw javascript. Typically the app boot code.
48
+ phrender.add_javascript "MyApp.boot()"
49
+
50
+ # Rackup!
51
+ run phrender.rack_app
52
+ ```
53
+
54
+ ### For partially dynamic javascript sites:
55
+
56
+ Use this method if some other rack-based application is serving up your assets,
57
+ or your index.html file. This can be useful for development, and even in some
58
+ production cases.
59
+
60
+ In your rackup file:
61
+
62
+ ```ruby
63
+ require 'phrender'
64
+ require 'my_rack_app'
65
+
66
+ # This is what you provide
67
+ app = MyRackApp.new
68
+
69
+ # Available options are timeout and ssl, if, for some reason, your rack app is
70
+ # serving up SSL encrypted pages. Run `phantomjs --help` to see available
71
+ # options, or use something falsey to disable. Both options are passed to
72
+ # phantom.
73
+ phrender = Phrender::RackMiddleware.new(app, options)
74
+
75
+ # These options are the same as above, but instead of referencing actual files,
76
+ # the paths are the request paths to send to the upstream application server.
77
+ phrender.index_file 'phrender.html'
78
+ phrender.add_javascript_file '/assets/application.js'
79
+ phrender.add_javascript "MyApp.boot()"
80
+
81
+ # Rackup!
82
+ run phrender.rack_app
83
+ ```
84
+
85
+ ## Signalling Render Completion
86
+
87
+ Phrender will, after `timeout` milliseconds serve up whatever has been rendered.
88
+ However, performance is better if you signal to phrender that rendering is
89
+ complete, and content should be served up. To do this, your application needs to
90
+ log to the console:
91
+
92
+ ```
93
+ console.log('-- PHRENDER COMPLETE --');
94
+ ```
95
+
96
+ If you are using ember, there is already a mechanism to do this, using the ember
97
+ driver.
98
+
99
+ ## Ember Driver
100
+
101
+ There is a driver provided for ember users that overrides `Ember.Route` to log
102
+ out to phrender, without harming your application. The driver is stored in
103
+ `lib/phrender/support/ember_driver.js`. This is how you include it:
104
+
105
+ ```
106
+ phrender.add_javascript_file '/assets/application.js'
107
+ phrender.add_javascript_file :ember_driver
108
+ phrender.add_javascript "MyApp.boot()"
109
+ ```
110
+
111
+ It's important to include the driver *after* ember and *before* you boot your
112
+ application.
113
+
114
+ ## Contributing
115
+
116
+ 1. Fork it
117
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
118
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
119
+ 4. Push to the branch (`git push origin my-new-feature`)
120
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,4 @@
1
+ class Phrender
2
+ EMBER_DRIVER = File.read(
3
+ File.expand_path('../phrender/support/ember_driver.js', __FILE__))
4
+ end
@@ -0,0 +1,55 @@
1
+ require 'colorize'
2
+
3
+ class Phrender::Logger
4
+ @print_color = !ENV.has_key?('DISABLE_COLOR')
5
+
6
+ class << self
7
+ MESSAGE_FORMAT = "%s: %s"
8
+
9
+ def log_json(json)
10
+ %w(console info error trace critical).each do |type|
11
+ if json.has_key? type
12
+ send type.to_sym, json[type]
13
+ end
14
+ end
15
+ end
16
+
17
+ def console(message)
18
+ log MESSAGE_FORMAT % [ apply_color("CONSOLE", :magenta), message ]
19
+ end
20
+
21
+ def info(message)
22
+ log MESSAGE_FORMAT % [ apply_color("INFO"), message ]
23
+ end
24
+
25
+ def error(message)
26
+ log MESSAGE_FORMAT % [ apply_color("ERROR", :red), message ]
27
+ end
28
+
29
+ def trace(message)
30
+ log MESSAGE_FORMAT % [ apply_color("TRACE", :cyan), message ]
31
+ end
32
+
33
+ def critical(message)
34
+ log MESSAGE_FORMAT % [ apply_color("CRITICAL", :on_red), message ]
35
+ end
36
+
37
+ def log(msg, color = nil)
38
+ message = "[%s] - %s" % [Time.now, msg]
39
+ $stdout.puts message
40
+ $stdout.flush
41
+ end
42
+
43
+ protected
44
+
45
+ def apply_color(message, color = nil)
46
+ if !color.nil? && @print_color
47
+ message.send(color.to_sym)
48
+ else
49
+ message
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,82 @@
1
+ require 'phrender/logger'
2
+ require 'phrender/phantom_js_session'
3
+
4
+ require 'open3'
5
+ require 'multi_json'
6
+
7
+ class Phrender::PhantomJSEngine
8
+
9
+ def initialize(opts = {})
10
+ # Apply defaults
11
+ opts = { :timeout => 10000, :ssl => false }.merge opts
12
+
13
+ @poll_interval = 0.1
14
+ @timeout = opts[:timeout] / 1000.0
15
+ @ssl_protocol = opts.delete :ssl
16
+ @logger = Phrender::Logger
17
+
18
+ phantom_program = File.expand_path '../support/phantom_bridge.js', __FILE__
19
+
20
+ MultiJson.use :json_gem
21
+
22
+ @boot_cmd = [
23
+ 'phantomjs',
24
+ phantom_program,
25
+ "--ignore-ssl-errors=true"
26
+ ]
27
+ @boot_cmd.push "--ssl-protocol=%s" % [ @ssl_protocol ] if @ssl_protocol
28
+ end
29
+
30
+ def render(html, javascript, url = nil)
31
+ command = app_cmd(html, javascript, url)
32
+ session = Phrender::PhantomJSSession.new command, @timeout
33
+
34
+ begin
35
+ sleep @poll_interval
36
+ parse_output(session)
37
+ end while !session.expired? && !session.rendered
38
+
39
+ # Clean up phantom
40
+ session.shutdown
41
+
42
+ # Feed something out the chain
43
+ if session.rendered
44
+ session.page
45
+ elsif session.expired?
46
+ @logger.critical "PhantomJS timed out. Likely a javascript execution error."
47
+ ''
48
+ else
49
+ @logger.critical "Phantom terminated without expiring or returning anything. This is bad."
50
+ ''
51
+ end
52
+ end
53
+
54
+ def app_cmd(html, javascript, url)
55
+ program_options = { :html => html,
56
+ :javascript => javascript,
57
+ :url => url,
58
+ :timeout => @timeout }
59
+ encoded_options = MultiJson.dump(MultiJson.dump(program_options))
60
+ "%s %s" % [ @boot_cmd.join(' '), encoded_options ]
61
+ end
62
+
63
+ protected
64
+
65
+ def parse_output(session)
66
+ output = session.stdout.gets
67
+ begin
68
+ data = JSON.parse output
69
+ if [ 'error', 'trace', 'console' ].any? { |key| data.has_key? key }
70
+ @logger.log_json data
71
+ elsif data.has_key? 'page'
72
+ session.rendered = true
73
+ session.page = data['page']
74
+ end
75
+ session.rendered
76
+ rescue
77
+ false
78
+ end
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,29 @@
1
+ class Phrender::PhantomJSSession
2
+ attr_accessor :stdin
3
+ attr_accessor :stdout
4
+ attr_accessor :stderr
5
+ attr_accessor :wait_thr
6
+ attr_accessor :rendered
7
+ attr_accessor :page
8
+
9
+ def initialize(cmd, timeout)
10
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(cmd)
11
+ @start_time = Time.now
12
+ @rendered = false
13
+ @timeout = timeout
14
+ end
15
+
16
+ def expired?
17
+ (Time.now - @start_time) >= @timeout
18
+ end
19
+
20
+ def shutdown
21
+ @stdin.close
22
+ @stdout.close
23
+ @stderr.close
24
+ begin
25
+ Process.kill("TERM", @wait_thr.pid)
26
+ rescue Errno::ESRCH
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ class Phrender::RackBase
2
+ class Proxy
3
+ class << self
4
+ attr_accessor :host
5
+ end
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ self.class.host.call(env, @app)
13
+ end
14
+ end
15
+
16
+ attr_accessor :index_file
17
+
18
+ def initialize(*args)
19
+ @javascript_paths = []
20
+ @raw_javascript = ''
21
+ Proxy.host = self
22
+ end
23
+
24
+ def rack_app
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def render(path, app)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def call(env, app)
33
+ status, headers, body = app.call(env)
34
+ if status == 404
35
+ body = render(env['PATH_INFO'], app)
36
+ [ 200, { 'Content-Type' => 'text/html' }, body ]
37
+ else
38
+ [ status, headers, body ]
39
+ end
40
+ end
41
+
42
+ def add_javascript_file(path)
43
+ @javascript_paths.push path
44
+ end
45
+
46
+ def add_javascript(code)
47
+ @raw_javascript << ';' + code
48
+ end
49
+
50
+ end
@@ -0,0 +1,55 @@
1
+ require 'phrender/phantom_js_engine'
2
+ require 'phrender/rack_base'
3
+
4
+ require 'rack'
5
+
6
+ class Phrender::RackMiddleware < Phrender::RackBase
7
+ def initialize(backend, opts = {})
8
+ @phantom = Phrender::PhantomJSEngine.new(opts)
9
+ @backend = backend
10
+ super
11
+ end
12
+
13
+ def rack_app
14
+ backend = @backend
15
+ @app ||= Rack::Builder.new do
16
+ use Proxy
17
+ run backend
18
+ end
19
+ end
20
+
21
+ def render(path, app)
22
+ program = load_js(app)
23
+ html = load_html(app)
24
+ @phantom.render(html, program)
25
+ end
26
+
27
+ protected
28
+
29
+ def load_html(app)
30
+ req = Rack::MockRequest.env_for('',
31
+ 'PATH_INFO' => @index_file,
32
+ 'REQUEST_METHOD' => 'GET'
33
+ )
34
+ status, headers, body = app.call(req)
35
+ body
36
+ end
37
+
38
+ def load_js(app)
39
+ js_from_files = @javascript_paths.map do |path|
40
+ if path == :ember_driver
41
+ Phrender::EMBER_DRIVER
42
+ else
43
+ req = Rack::MockRequest.env_for('',
44
+ 'PATH_INFO' => path,
45
+ 'REQUEST_METHOD' => 'GET'
46
+ )
47
+ status, headers, body = app.call(req)
48
+ body
49
+ end
50
+ end.join(';')
51
+ program = js_from_files + @raw_javascript
52
+ program
53
+ end
54
+
55
+ end
@@ -0,0 +1,45 @@
1
+ require 'phrender/phantom_js_engine'
2
+ require 'phrender/rack_base'
3
+
4
+ require 'rack'
5
+
6
+ class Phrender::RackStatic < Phrender::RackBase
7
+ def initialize(root_directory, opts = {})
8
+ @phantom = Phrender::PhantomJSEngine.new(opts)
9
+ @root_directory = root_directory
10
+ super
11
+ end
12
+
13
+ def rack_app
14
+ static_directory = @root_directory
15
+ @app ||= Rack::Builder.new do
16
+ use Proxy
17
+ run Rack::File.new(static_directory)
18
+ end
19
+ end
20
+
21
+ def render(path, app)
22
+ program = load_js(app)
23
+ html = load_html(app)
24
+ @phantom.render(html, program)
25
+ end
26
+
27
+ protected
28
+
29
+ def load_html(app)
30
+ File.read File.join(@root_directory, @index_file)
31
+ end
32
+
33
+ def load_js(app)
34
+ js_from_files = @javascript_paths.map do |path|
35
+ if path == :ember_driver
36
+ Phrender::EMBER_DRIVER
37
+ else
38
+ File.read File.join(@root_directory, path)
39
+ end
40
+ end.join(';')
41
+ program = js_from_files + @raw_javascript
42
+ program
43
+ end
44
+
45
+ end
@@ -0,0 +1,16 @@
1
+ window.__PHRENDER = true;
2
+ window.__firstRender = true;
3
+ window.__afterRender = function() {
4
+ console.log('-- PHRENDER COMPLETE --');
5
+ };
6
+ Ember.Route = Ember.Route.extend({
7
+ render: function(){
8
+ if (window.__firstRender) {
9
+ window.__firstRender = false;
10
+ } else {
11
+ Ember.run.scheduleOnce('afterRender', null, window.__afterRender);
12
+ }
13
+ this._super.apply(this, arguments);
14
+ }
15
+ });
16
+
@@ -0,0 +1,108 @@
1
+ var system = require('system'),
2
+ fs = require('fs'),
3
+ webpage = require('webpage');
4
+
5
+ // streams
6
+ var stdout = system.stdout;
7
+
8
+ // global opts
9
+ var options = JSON.parse(system.args[system.args.length - 1]),
10
+ globals = {
11
+ "page": null,
12
+ "timer": null,
13
+ "expired": false,
14
+ "rendered": false,
15
+ "html": options.html
16
+ };
17
+
18
+ // functions
19
+ var printJson,
20
+ logMessage,
21
+ logTrace,
22
+ logError,
23
+ printPage,
24
+ waitForRender,
25
+ writeHtml,
26
+ run;
27
+
28
+ printJson = function(messageType, message) {
29
+ var payload = {};
30
+ payload[messageType] = message;
31
+ stdout.writeLine(JSON.stringify(payload));
32
+ stdout.flush();
33
+ };
34
+
35
+ logMessage = function(message) {
36
+ printJson('console', message);
37
+ };
38
+
39
+ logTrace = function(trace) {
40
+ if (trace && trace.length) {
41
+ var traceStack = [];
42
+ trace.forEach(function(t) {
43
+ traceStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function + '")' : ''));
44
+ });
45
+
46
+ printJson('trace', traceStack.join('\n'));
47
+ }
48
+ };
49
+
50
+ logError = function(message, trace) {
51
+ printJson('error', message);
52
+
53
+ logTrace(trace);
54
+ };
55
+
56
+ printPage = function(url) {
57
+ globals.page = webpage.create();
58
+
59
+ globals.page.setContent(globals.html, url);
60
+
61
+ // Log javascript console messages
62
+ globals.page.onConsoleMessage = function(msg) {
63
+ if (msg.trim() === "-- PHRENDER COMPLETE --"){
64
+ globals.rendered = true;
65
+ } else {
66
+ logMessage(msg);
67
+ }
68
+ };
69
+
70
+ // capture errors
71
+ globals.page.onError = logError;
72
+
73
+ globals.page.evaluate(function(code) {
74
+ eval(code);
75
+ }, options.javascript);
76
+
77
+ // Catch something
78
+ globals.timer = setTimeout(writeHtml, options.timeout);
79
+
80
+ // Wait for the flag to switch
81
+ waitForRender();
82
+ };
83
+
84
+ waitForRender = function() {
85
+ if (!globals.expired) {
86
+ if (!globals.rendered) {
87
+ setTimeout(waitForRender, 100);
88
+ } else {
89
+ clearTimeout(globals.timer);
90
+ writeHtml();
91
+ }
92
+ }
93
+ };
94
+
95
+ writeHtml = function() {
96
+ globals.expired = true;
97
+ var html = globals.page.evaluate(function() {
98
+ return document.documentElement.outerHTML;
99
+ });
100
+ printJson("page", html);
101
+ phantom.exit();
102
+ };
103
+
104
+ run = function() {
105
+ printPage(options.url);
106
+ };
107
+
108
+ run();
@@ -0,0 +1,3 @@
1
+ class Phrender
2
+ VERSION = "0.0.1"
3
+ end
data/lib/phrender.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "phrender/version"
2
+ require "phrender/logger"
3
+ require "phrender/phantom_js_engine"
4
+ require "phrender/phantom_js_session"
5
+ require "phrender/rack_base"
6
+ require "phrender/rack_middleware"
7
+ require "phrender/rack_static"
8
+
9
+ class Phrender
10
+ end
@@ -0,0 +1,27 @@
1
+ # Borrowed from activesupport, but without the :try method
2
+
3
+ class String
4
+ # Strips indentation in heredocs.
5
+ #
6
+ # For example in
7
+ #
8
+ # if options[:usage]
9
+ # puts <<-USAGE.strip_heredoc
10
+ # This command does such and such.
11
+ #
12
+ # Supported options are:
13
+ # -h This message
14
+ # ...
15
+ # USAGE
16
+ # end
17
+ #
18
+ # the user would see the usage message aligned against the left margin.
19
+ #
20
+ # Technically, it looks for the least indented line in the whole string, and removes
21
+ # that amount of leading whitespace.
22
+ def strip_heredoc
23
+ matches = scan(/^[ \t]*(?=\S)/).min
24
+ indent = (matches.respond_to? :size) ? matches.size : 0
25
+ gsub(/^[ \t]{#{indent}}/, '')
26
+ end
27
+ end
data/phrender.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'phrender/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "phrender"
8
+ spec.version = Phrender::VERSION
9
+ spec.authors = ["M Smart, theScore Inc."]
10
+ spec.email = ["matthew.smart@thescore.com"]
11
+ spec.description = %q{Rack server for rendering javascript apps for bots}
12
+ spec.summary = %q{Rack server for rendering javascript apps for bots}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "rack", "~> 1.5.2"
22
+ spec.add_runtime_dependency "colorize"
23
+ spec.add_runtime_dependency "multi_json"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency 'rspec', '~> 3.0.0.beta1'
28
+ spec.add_development_dependency 'rack-test'
29
+ spec.add_development_dependency 'sprockets'
30
+ spec.add_development_dependency 'pry', '0.9.12.2'
31
+ end
@@ -0,0 +1,15 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ var head = document.getElementsByTagName('head')[0],
5
+ title = document.createElement('title'),
6
+ paragraph = document.createElement('p');
7
+
8
+ title.appendChild(document.createTextNode("Phrender The Prerenderer"));
9
+ head.appendChild(title);
10
+
11
+ paragraph.appendChild(document.createTextNode("Hello!"));
12
+ document.body.appendChild(paragraph);
13
+
14
+ setTimeout(function(){window.console.log('-- PHRENDER COMPLETE --');},1000);
15
+ })();
@@ -0,0 +1,6 @@
1
+ <html>
2
+ <head>
3
+ </head>
4
+ <body>
5
+ </body>
6
+ </html>
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'phrender/phantom_js_engine'
3
+
4
+ describe 'Phrender::PhantomJSEngine' do
5
+ let(:phantom) {
6
+ Phrender::PhantomJSEngine.new(:timeout => 10000, :ssl => false)
7
+ }
8
+
9
+ let(:index) {
10
+ File.read(File.expand_path('../phantom_js_engine/index.html', __FILE__))
11
+ }
12
+
13
+ let(:app) {
14
+ File.read(File.expand_path('../phantom_js_engine/app.js', __FILE__))
15
+ }
16
+
17
+ it 'generates a startup command with escaped json' do
18
+ command = phantom.app_cmd(index, app, 'http://localhost')
19
+ expect(command).to match(
20
+ /phantomjs (.+?)phrender\/lib\/phrender\/support\/phantom_bridge.js/
21
+ )
22
+ expect(command).to include("--ignore-ssl-errors=true")
23
+ expect(command).to include("<html>")
24
+ expect(command).to include("use strict")
25
+ end
26
+
27
+ it 'renders a simple page' do
28
+ whitespace_regex = /(\n|^ +)/
29
+ html = <<-HTML.strip_heredoc.gsub(whitespace_regex, '')
30
+ <html>
31
+ <head>
32
+ <title>Phrender The Prerenderer</title>
33
+ </head>
34
+ <body>
35
+ <p>Hello!</p>
36
+ </body>
37
+ </html>
38
+ HTML
39
+ expect(phantom.render(index, app).gsub(whitespace_regex, '')).to eq(html)
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ var head = document.getElementsByTagName('head')[0],
5
+ title = document.createElement('title'),
6
+ paragraph = document.createElement('p');
7
+
8
+ title.appendChild(document.createTextNode("Phrender The Prerenderer"));
9
+ head.appendChild(title);
10
+
11
+ paragraph.appendChild(document.createTextNode("Hello!"));
12
+ document.body.appendChild(paragraph);
13
+
14
+ window.App = {
15
+ run: function() {
16
+ window.console.log('-- PHRENDER COMPLETE --');
17
+ }
18
+ };
19
+ })();
@@ -0,0 +1,10 @@
1
+ <%= <<-HTML.strip_heredoc
2
+ <html>
3
+ <head>
4
+ </head>
5
+ <body>
6
+ <h1>What a page!</h1>
7
+ </body>
8
+ </html>
9
+ HTML
10
+ %>
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'phrender/rack_middleware'
3
+ require 'sprockets'
4
+
5
+ describe 'Phrender::RackMiddleware' do
6
+ let(:root) { File.expand_path '../rack_middleware', __FILE__ }
7
+ let(:backend) {
8
+ b = Sprockets::Environment.new(root)
9
+ b.append_path 'assets'
10
+ b
11
+ }
12
+ let(:app) {
13
+ p = Phrender::RackMiddleware.new(backend)
14
+ p.index_file = 'phrender.html'
15
+ p.add_javascript_file 'app.js'
16
+ p.add_javascript 'App.run'
17
+ p.rack_app
18
+ }
19
+
20
+ it 'runs the app contained in the referenced assets' do
21
+ get('/')
22
+ whitespace_regex = /(\n|^ +)/
23
+ html = '<html><head><title>Phrender The Prerenderer</title></head><body><h1>What a page!</h1><p>Hello!</p></body></html>'
24
+ expect(last_response.body.gsub(whitespace_regex, '')).to eq(html)
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ var head = document.getElementsByTagName('head')[0],
5
+ title = document.createElement('title'),
6
+ paragraph = document.createElement('p');
7
+
8
+ title.appendChild(document.createTextNode("Phrender The Prerenderer"));
9
+ head.appendChild(title);
10
+
11
+ paragraph.appendChild(document.createTextNode("Hello!"));
12
+ document.body.appendChild(paragraph);
13
+
14
+ window.App = {
15
+ run: function() {
16
+ window.console.log('-- PHRENDER COMPLETE --');
17
+ }
18
+ };
19
+ })();
@@ -0,0 +1 @@
1
+ The body of a static file.
File without changes
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'phrender/rack_static'
3
+
4
+ describe 'Phrender::RackStatic' do
5
+ let(:root) { File.expand_path '../rack_static', __FILE__ }
6
+ let(:app) {
7
+ p = Phrender::RackStatic.new(root)
8
+ p.index_file = 'phrender.html'
9
+ p.add_javascript_file 'app.js'
10
+ p.add_javascript 'App.run'
11
+ p.rack_app
12
+ }
13
+
14
+ it 'runs the app contained in the referenced assets' do
15
+ get('/')
16
+ expect(last_response.body).to eq('<html><head><title>Phrender The Prerenderer</title></head><body><p>Hello!</p></body></html>')
17
+ end
18
+
19
+ it 'resolves static assets' do
20
+ get('/files/static.txt')
21
+ expect(last_response.body.strip).to eq('The body of a static file.')
22
+ end
23
+
24
+ end
@@ -0,0 +1,14 @@
1
+ require 'rack/test'
2
+ require 'string/strip'
3
+
4
+ ENV['RACK_ENV'] = 'test'
5
+
6
+ RSpec.configure do |config|
7
+ # Use color in STDOUT
8
+ config.color = true
9
+
10
+ # Use the specified formatter
11
+ config.formatter = :documentation # :progress, :html, :textmate
12
+
13
+ config.include Rack::Test::Methods
14
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phrender
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - M Smart, theScore Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0.beta1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0.beta1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-test
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sprockets
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.9.12.2
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.9.12.2
139
+ description: Rack server for rendering javascript apps for bots
140
+ email:
141
+ - matthew.smart@thescore.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - Gemfile
148
+ - LICENSE
149
+ - README.md
150
+ - Rakefile
151
+ - lib/phrender.rb
152
+ - lib/phrender/ember_driver.rb
153
+ - lib/phrender/logger.rb
154
+ - lib/phrender/phantom_js_engine.rb
155
+ - lib/phrender/phantom_js_session.rb
156
+ - lib/phrender/rack_base.rb
157
+ - lib/phrender/rack_middleware.rb
158
+ - lib/phrender/rack_static.rb
159
+ - lib/phrender/support/ember_driver.js
160
+ - lib/phrender/support/phantom_bridge.js
161
+ - lib/phrender/version.rb
162
+ - lib/string/strip.rb
163
+ - phrender.gemspec
164
+ - spec/phrender/phantom_js_engine/app.js
165
+ - spec/phrender/phantom_js_engine/index.html
166
+ - spec/phrender/phantom_js_engine_spec.rb
167
+ - spec/phrender/rack_middleware/assets/app.js
168
+ - spec/phrender/rack_middleware/assets/phrender.html.erb
169
+ - spec/phrender/rack_middleware_spec.rb
170
+ - spec/phrender/rack_static/app.js
171
+ - spec/phrender/rack_static/files/static.txt
172
+ - spec/phrender/rack_static/phrender.html
173
+ - spec/phrender/rack_static_spec.rb
174
+ - spec/spec_helper.rb
175
+ homepage: ''
176
+ licenses:
177
+ - MIT
178
+ metadata: {}
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubyforge_project:
195
+ rubygems_version: 2.2.2
196
+ signing_key:
197
+ specification_version: 4
198
+ summary: Rack server for rendering javascript apps for bots
199
+ test_files:
200
+ - spec/phrender/phantom_js_engine/app.js
201
+ - spec/phrender/phantom_js_engine/index.html
202
+ - spec/phrender/phantom_js_engine_spec.rb
203
+ - spec/phrender/rack_middleware/assets/app.js
204
+ - spec/phrender/rack_middleware/assets/phrender.html.erb
205
+ - spec/phrender/rack_middleware_spec.rb
206
+ - spec/phrender/rack_static/app.js
207
+ - spec/phrender/rack_static/files/static.txt
208
+ - spec/phrender/rack_static/phrender.html
209
+ - spec/phrender/rack_static_spec.rb
210
+ - spec/spec_helper.rb