forked from apache/whimsy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vue_server.rb
187 lines (155 loc) · 4.65 KB
/
vue_server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#
# This class spawns a io.js process to run a HTTP server which accepts
# POST requests containing Vue/jsdom scripts and responds with
# HTML results. It provides a Rack interface, enabling this server to
# be run with Capybara/RackTest.
#
require 'ruby2js'
require 'net/http'
require 'stringio'
require 'capybara/rspec'
require 'ruby2js/filter/vue'
class VueServer
@@pid = nil
@@port = nil
# start a new server
def self.start
return if @@pid
# select an available port
server = TCPServer.new('127.0.0.1', 0)
@@port = server.addr[1]
server.close
# spawn a server process
nodejs = (`which nodejs`.empty? ? 'node' : 'nodejs')
puts "Vue server is using: '#{nodejs}'"
@@pid = spawn(nodejs, '-e',
Ruby2JS.convert(@@server, {ivars: {:@port => @@port}}))
# wait for server to start
(0..10).each do |i|
begin
response = new.call('rack.input' => StringIO.new("response.end('hi')"))
return if response.first == '200' and response.last == 'hi'
STDERR.puts response
raise RuntimeError('Invalid VueServer response received')
rescue Errno::ECONNREFUSED
sleep i * 0.1
end
end
end
# rack compatible interface
def call(env)
http = Net::HTTP.new('localhost', @@port)
request = Net::HTTP::Post.new('/', {})
request.body = env['rack.input'].read
response = http.request(request)
[response.code, response.to_hash(), response.body]
end
# stop server
def self.stop
return unless @@pid
begin
http = Net::HTTP.new('localhost', @@port)
request = Net::HTTP::Post.new('/', {})
request.body = "response.end('bye'); process.exit(0)"
_response = http.request(request)
rescue Errno::ECONNREFUSED, EOFError
nil
ensure
Process.wait(@@pid)
@@pid = nil
end
end
# the server itself
@@server = proc do
_cleanup = require("jsdom-global/register")
delete global.XMLHttpRequest
process.env.VUE_ENV = 'server'
Vue = require('vue')
Vue.config.productionTip = false
# render a response, using server side rendering
def Vue.renderResponse(component, response)
renderer = require('vue-server-renderer').createRenderer()
app = Vue.new(render: proc {|h| return h(component)})
renderer.renderToString(app) do |err, html|
if err
response.end(err.toString() + "\n" + err.stack)
else
response.end(html)
end
end
end
# render a element, using client side rendering
def Vue.renderElement(component)
outer = document.createElement('div')
inner = document.createElement('span')
outer.appendChild(inner)
Vue.new(el: inner, render: proc {|h| return h(component)})
return outer.firstChild
end
# render an app, using client side rendering. Convenience methods are
# provided to querySelector, and to extract outerHTML.
def Vue.renderApp(component)
outer = document.createElement('div')
inner = document.createElement('span')
outer.appendChild(inner)
app = Vue.new(el: inner, render: proc {|h| return h(component)})
inner = outer.firstChild # appears not to be used, but it is
def app.outerHTML
return inner.outerHTML
end
def app.querySelector(selector)
return outer.querySelector(selector)
end
return app
end
jQuery = require('jquery') # the variable jQuery is needed, even if it appears not
http = require('http')
server = http.createServer do |request, response|
data = ''
request.on('data') do |chunk|
data += chunk
end
request.on 'error' do |error|
console.error "VueServer error: #{error.message}"
end
request.on 'end' do
response.writeHead(200, 'Content-Type' => 'text/plain')
begin
eval(data)
rescue => error
response.end(error.toString())
end
end
end
server.listen(@port)
end
end
shared_context "vue_server", server: :vue do
#
# administrivia
#
before :all do
VueServer.start
Dir.chdir File.expand_path('../../views', __FILE__) do
@_script = Ruby2JS.convert(File.read('app.js.rb'), file: 'app.js.rb')
end
end
before :each do
@_app, Capybara.app = Capybara.app, VueServer.new
end
def on_vue_server(&block)
locals = {}
instance_variables.each do |ivar|
next if ivar.to_s.start_with? '@_'
locals[ivar] = instance_variable_get(ivar)
end
page.driver.post('/', @_script + ';' +
Ruby2JS.convert(block, vue: true, ivars: locals).to_s)
end
after :each do
Capybara.app = @_app
end
at_exit do
VueServer.stop
end
end