wunderbar 1.1.2 → 1.2.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.
- checksums.yaml +4 -4
- data/lib/wunderbar/builder.rb +10 -3
- data/lib/wunderbar/react.rb +16 -177
- data/lib/wunderbar/render.rb +200 -0
- data/lib/wunderbar/underscore.rb +2 -1
- data/lib/wunderbar/vendor/vue-server.min.js +1 -0
- data/lib/wunderbar/vendor/vue.min.js +6 -0
- data/lib/wunderbar/version.rb +2 -2
- data/lib/wunderbar/vue.rb +40 -0
- data/wunderbar.gemspec +4 -4
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d0bf66bfd444cace4acde0057ba3bf0ac53f02f
|
4
|
+
data.tar.gz: 223ff899aa06573c0b4d53b6f79a2c23755a64de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6025587bdcf0cf572312da1006565ac289f4a0ec2615ace9f8fdea71c891544c461b13efef0a5ca1f30fb7a1c3d10bc154426beb539e9adfd1f2a66bcfe8df2c
|
7
|
+
data.tar.gz: 0d237270a67cbc952272be4b54b21af56646b447f8a91e1ff415fb5b9be5a26bd30191ffe9056e2f7c973b85b1376bf2b83f48a6cb0f327e54411fae45f3ae34
|
data/lib/wunderbar/builder.rb
CHANGED
@@ -171,15 +171,21 @@ module Wunderbar
|
|
171
171
|
end
|
172
172
|
|
173
173
|
def text! text
|
174
|
-
|
174
|
+
text = TextNode.new(text)
|
175
|
+
@node.children << text
|
176
|
+
text
|
175
177
|
end
|
176
178
|
|
177
179
|
def declare! *args
|
178
|
-
|
180
|
+
doctype = DocTypeNode.new(*args)
|
181
|
+
@node.children << doctype
|
182
|
+
doctype
|
179
183
|
end
|
180
184
|
|
181
185
|
def comment! text
|
182
|
-
|
186
|
+
comment = CommentNode.new(text)
|
187
|
+
@node.children << comment
|
188
|
+
comment
|
183
189
|
end
|
184
190
|
|
185
191
|
def indented_text!(text)
|
@@ -188,6 +194,7 @@ module Wunderbar
|
|
188
194
|
text.extend SpacedNode if @spaced
|
189
195
|
@node.children << text
|
190
196
|
@spaced = false
|
197
|
+
text
|
191
198
|
end
|
192
199
|
|
193
200
|
def target!
|
data/lib/wunderbar/react.rb
CHANGED
@@ -1,196 +1,35 @@
|
|
1
|
-
|
2
|
-
raise RuntimeError.new('Sinatra is a prereq for wunderbar/react')
|
3
|
-
# a parallel solution is possible for Rails, but that work hasn't
|
4
|
-
# been done yet
|
5
|
-
end
|
6
|
-
|
7
|
-
require 'wunderbar/sinatra'
|
8
|
-
require 'wunderbar/script'
|
1
|
+
require 'wunderbar/render'
|
9
2
|
require 'ruby2js/filter/react'
|
10
3
|
require 'execjs'
|
11
|
-
require 'nokogumbo'
|
12
4
|
|
13
5
|
react = File.expand_path('../vendor/react-with-addons.min.js', __FILE__)
|
14
|
-
Wunderbar::Asset.script name: 'react-min.js', file: react,
|
6
|
+
Wunderbar::Asset.script name: 'react-min.js', file: react, render: true
|
15
7
|
|
16
8
|
reactdom = File.expand_path('../vendor/react-dom.min.js', __FILE__)
|
17
|
-
Wunderbar::Asset.script name: 'react-dom.min.js', file: reactdom,
|
9
|
+
Wunderbar::Asset.script name: 'react-dom.min.js', file: reactdom, render: true,
|
18
10
|
server: File.expand_path('../vendor/react-dom-server.min.js', __FILE__)
|
19
11
|
|
20
|
-
class Wunderbar::
|
21
|
-
|
22
|
-
def self.convert(file)
|
23
|
-
cached = @@cached_scripts[file]
|
24
|
-
return cached if cached and cached.uptodate?
|
25
|
-
return nil unless File.exist? file
|
26
|
-
@@cached_scripts[file] = Ruby2JS.convert(File.read(file), file: file)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Wunderbar::ClientScriptNode < Wunderbar::ScriptNode
|
31
|
-
end
|
32
|
-
|
33
|
-
class Wunderbar::XmlMarkup
|
34
|
-
def render(container, &block)
|
35
|
-
csspath = Wunderbar::Node.parse_css_selector(container)
|
36
|
-
root = @node.root
|
37
|
-
|
38
|
-
# find the scripts and target on the page
|
39
|
-
scripts = root.search('script')
|
40
|
-
target = root.at(container)
|
41
|
-
|
42
|
-
# compute base
|
43
|
-
base = root.at('base')
|
44
|
-
base = base && base.attrs[:href]
|
45
|
-
base ||= @_scope.env['REQUEST_URI'][/.*\//]
|
46
|
-
|
47
|
-
_base = @_scope.env['HTTP_X_WUNDERBAR_BASE']
|
48
|
-
base = base[_base.length..-1] if _base and base.start_with? _base
|
49
|
-
|
50
|
-
if base == '..' or base.end_with? '/..'
|
51
|
-
base = (Pathname.new(@_scope.env['REQUEST_URI']) + '../' + base).to_s
|
52
|
-
end
|
53
|
-
|
54
|
-
script = @_scope.env['SCRIPT_NAME']
|
55
|
-
base = base[script.length..-1] if script and base.start_with? script
|
56
|
-
|
57
|
-
base = base[1..-1] if base.start_with? '/'
|
58
|
-
|
59
|
-
# compute client side container
|
60
|
-
element = "document.querySelector(#{container.inspect})"
|
61
|
-
if csspath.length == 1 and csspath[0].length == 1
|
62
|
-
value = csspath[0].values.first
|
63
|
-
case csspath[0].keys.first
|
64
|
-
when :id
|
65
|
-
element = "document.getElementById(#{value.inspect})"
|
66
|
-
when :class
|
67
|
-
value = value.join(' ')
|
68
|
-
element = "document.getElementsByClassName(#{value.inspect})[0]"
|
69
|
-
when :name
|
70
|
-
element = "document.getElementsByTagName(#{value.inspect})[0]"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# build client and server scripts
|
75
|
-
common = Ruby2JS.convert(block, scope: @_scope, react: true)
|
76
|
-
server = "ReactDOMServer.renderToString(#{common})"
|
77
|
-
client = "ReactDOM.render(#{common}, #{element})"
|
12
|
+
class Wunderbar::Render
|
13
|
+
RUBY2JS_OPTIONS = {react: true}
|
78
14
|
|
79
|
-
|
80
|
-
|
81
|
-
result = nil
|
82
|
-
next if Wunderbar::ClientScriptNode === script
|
83
|
-
|
84
|
-
if script.attrs[:src]
|
85
|
-
src = script.attrs[:src]
|
86
|
-
|
87
|
-
src = File.join(base, src) if not base.empty?
|
88
|
-
src.sub!(/\?.*$/, '') # strip queries (typically mtimes)
|
89
|
-
src.untaint
|
90
|
-
|
91
|
-
name = File.expand_path(src, @_scope.settings.public_folder.untaint)
|
92
|
-
name.untaint unless src.tainted?
|
93
|
-
if File.exist? name
|
94
|
-
result = File.read(name)
|
95
|
-
else
|
96
|
-
file = File.expand_path(src+'.rb', @_scope.settings.views.untaint)
|
97
|
-
result = Wunderbar::Asset.convert(file)
|
98
|
-
end
|
99
|
-
else
|
100
|
-
result = Ruby2JS.convert(script.block, binding: script.binding)
|
101
|
-
end
|
102
|
-
|
103
|
-
result
|
104
|
-
end
|
105
|
-
|
106
|
-
builder = Wunderbar::HtmlMarkup.new({})
|
107
|
-
begin
|
108
|
-
setup = []
|
109
|
-
Wunderbar::Asset.scripts.each do |script|
|
110
|
-
next unless script.options[:react]
|
111
|
-
setup += script.options[:react] if Array === script.options[:react]
|
112
|
-
|
113
|
-
if script.contents
|
114
|
-
scripts.unshift script.contents
|
115
|
-
elsif script.path
|
116
|
-
if script.path.start_with? '/'
|
117
|
-
path = (ENV['DOCUMENT_ROOT'] + script.path).untaint
|
118
|
-
else
|
119
|
-
path = File.expand_path(script.path, Wunderbar::Asset.root)
|
120
|
-
end
|
121
|
-
setup << File.read(script.options[:server] || path)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# concatenate and execute scripts on server
|
126
|
-
scripts.unshift *setup.uniq
|
127
|
-
context = ExecJS.compile(scripts.compact.join(";\n"))
|
128
|
-
|
129
|
-
# insert results into target
|
130
|
-
nodes = builder._ { context.eval(server) }
|
131
|
-
nodes.each {|node| node.parent = target}
|
132
|
-
target.children += nodes
|
133
|
-
rescue ExecJS::ProgramError => e
|
134
|
-
Wunderbar.error e
|
135
|
-
target.children << builder._pre(e.message).node?
|
136
|
-
end
|
137
|
-
|
138
|
-
# add client side script
|
139
|
-
tag! 'script', Wunderbar::ClientScriptNode, client
|
15
|
+
def self.server(common)
|
16
|
+
"ReactDOMServer.renderToString(#{common})"
|
140
17
|
end
|
141
|
-
end
|
142
18
|
|
143
|
-
|
144
|
-
|
145
|
-
@etag ||= Digest::MD5.hexdigest(to_str)
|
19
|
+
def self.client(common, element, target)
|
20
|
+
"ReactDOM.render(#{common}, #{element})"
|
146
21
|
end
|
147
22
|
|
148
|
-
def
|
149
|
-
|
23
|
+
def self.eval(scripts, server)
|
24
|
+
context = ExecJS.compile(scripts.compact.join(";\n"))
|
25
|
+
context.eval(server)
|
26
|
+
rescue ExecJS::ProgramError => e
|
27
|
+
Wunderbar.error e
|
28
|
+
"<pre>" + e.message.gsub('&', '&').gsub('<', '<').gsub('>', '>') +
|
29
|
+
"</pre>"
|
150
30
|
end
|
151
31
|
end
|
152
32
|
|
153
|
-
get %r{/([-\w]+)\.js} do |script|
|
154
|
-
file = File.join(settings.views, "#{script}.js.rb")
|
155
|
-
begin
|
156
|
-
js = Wunderbar::Asset.convert(file)
|
157
|
-
pass unless js
|
158
|
-
rescue Exception => e
|
159
|
-
Wunderbar.error e.to_s
|
160
|
-
return [500, {'Content-type' => 'text/plain'}, "*** ERROR ***\n\n#{e}"]
|
161
|
-
end
|
162
|
-
|
163
|
-
response.headers['SourceMap'] = "#{script}.js.map"
|
164
|
-
|
165
|
-
etag js.etag
|
166
|
-
|
167
|
-
content_type 'application/javascript; charset=utf-8'
|
168
|
-
|
169
|
-
js.to_s
|
170
|
-
end
|
171
|
-
|
172
|
-
get %r{/((?:\w+\/)*[-\w]+)\.js.rb} do |script|
|
173
|
-
file = File.join(settings.views, "#{script}.js.rb")
|
174
|
-
pass unless File.exist? file
|
175
|
-
send_file file
|
176
|
-
end
|
177
|
-
|
178
|
-
get %r{/([-\w]+)\.js.map} do |script|
|
179
|
-
file = File.join(settings.views, "#{script}.js.rb")
|
180
|
-
js = Wunderbar::Asset.convert(file)
|
181
|
-
pass unless js
|
182
|
-
|
183
|
-
etag js.sourcemap_etag
|
184
|
-
|
185
|
-
sourcemap = js.sourcemap
|
186
|
-
|
187
|
-
content_type 'application/json;charset:utf8'
|
188
|
-
base = settings.views + '/'
|
189
|
-
sourcemap[:file] = sourcemap[:file].sub base, ''
|
190
|
-
sourcemap[:sources].map! {|source| source.sub base, ''}
|
191
|
-
JSON.pretty_generate sourcemap
|
192
|
-
end
|
193
|
-
|
194
33
|
# Monkeypatch to address https://github.com/sstephenson/execjs/pull/180
|
195
34
|
require 'execjs'
|
196
35
|
class ExecJS::ExternalRuntime::Context
|
@@ -0,0 +1,200 @@
|
|
1
|
+
unless defined? Sinatra
|
2
|
+
raise RuntimeError.new('Sinatra is a prereq for wunderbar/render')
|
3
|
+
# a parallel solution is possible for Rails, but that work hasn't
|
4
|
+
# been done yet
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'wunderbar/sinatra'
|
8
|
+
require 'wunderbar/script'
|
9
|
+
require 'nokogumbo'
|
10
|
+
|
11
|
+
class Wunderbar::Asset
|
12
|
+
@@cached_scripts = {}
|
13
|
+
def self.convert(file)
|
14
|
+
cached = @@cached_scripts[file]
|
15
|
+
return cached if cached and cached.uptodate?
|
16
|
+
return nil unless File.exist? file
|
17
|
+
@@cached_scripts[file] = Ruby2JS.convert(File.read(file), file: file)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Wunderbar::ClientScriptNode < Wunderbar::ScriptNode
|
22
|
+
end
|
23
|
+
|
24
|
+
class Wunderbar::XmlMarkup
|
25
|
+
def render(container, &block)
|
26
|
+
csspath = Wunderbar::Node.parse_css_selector(container)
|
27
|
+
root = @node.root
|
28
|
+
|
29
|
+
# find the scripts and target on the page
|
30
|
+
scripts = root.search('script')
|
31
|
+
target = root.at(container)
|
32
|
+
|
33
|
+
# compute base
|
34
|
+
base = root.at('base')
|
35
|
+
base = base && base.attrs[:href]
|
36
|
+
base ||= @_scope.env['REQUEST_URI'][/.*\//]
|
37
|
+
|
38
|
+
_base = @_scope.env['HTTP_X_WUNDERBAR_BASE']
|
39
|
+
base = base[_base.length..-1] if _base and base.start_with? _base
|
40
|
+
|
41
|
+
if base == '..' or base.end_with? '/..'
|
42
|
+
base = (Pathname.new(@_scope.env['REQUEST_URI']) + '../' + base).to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
script = @_scope.env['SCRIPT_NAME']
|
46
|
+
base = base[script.length..-1] if script and base.start_with? script
|
47
|
+
|
48
|
+
base = base[1..-1] if base.start_with? '/'
|
49
|
+
|
50
|
+
# compute client side container
|
51
|
+
element = "document.querySelector(#{container.inspect})"
|
52
|
+
if csspath.length == 1 and csspath[0].length == 1
|
53
|
+
value = csspath[0].values.first
|
54
|
+
case csspath[0].keys.first
|
55
|
+
when :id
|
56
|
+
element = "document.getElementById(#{value.inspect})"
|
57
|
+
when :class
|
58
|
+
value = value.join(' ')
|
59
|
+
element = "document.getElementsByClassName(#{value.inspect})[0]"
|
60
|
+
when :name
|
61
|
+
element = "document.getElementsByTagName(#{value.inspect})[0]"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# build client and server scripts
|
66
|
+
options = Wunderbar::Render::RUBY2JS_OPTIONS.merge(scope: @_scope)
|
67
|
+
common = Ruby2JS.convert(block, options)
|
68
|
+
server = Wunderbar::Render.server(common)
|
69
|
+
client = Wunderbar::Render.client(common, element, target)
|
70
|
+
|
71
|
+
# extract content of scripts
|
72
|
+
scripts.map! do |script|
|
73
|
+
result = nil
|
74
|
+
next if Wunderbar::ClientScriptNode === script
|
75
|
+
|
76
|
+
if script.attrs[:src]
|
77
|
+
src = script.attrs[:src]
|
78
|
+
|
79
|
+
src = File.join(base, src) if not base.empty?
|
80
|
+
src.sub!(/\?.*$/, '') # strip queries (typically mtimes)
|
81
|
+
src.untaint
|
82
|
+
|
83
|
+
name = File.expand_path(src, @_scope.settings.public_folder.untaint)
|
84
|
+
name.untaint unless src.tainted?
|
85
|
+
if File.exist? name
|
86
|
+
result = File.read(name)
|
87
|
+
else
|
88
|
+
file = File.expand_path(src+'.rb', @_scope.settings.views.untaint)
|
89
|
+
result = Wunderbar::Asset.convert(file)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
result = Ruby2JS.convert(script.block, binding: script.binding)
|
93
|
+
end
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
builder = Wunderbar::HtmlMarkup.new({})
|
99
|
+
setup = []
|
100
|
+
requires = {}
|
101
|
+
browserify = false
|
102
|
+
Wunderbar::Asset.scripts.each do |script|
|
103
|
+
next unless script.options[:render]
|
104
|
+
setup += script.options[:render] if Array === script.options[:render]
|
105
|
+
requires.merge! script.options[:require] if script.options[:require]
|
106
|
+
browserify = true if script.options[:browserify]
|
107
|
+
|
108
|
+
if script.contents
|
109
|
+
scripts.unshift script.contents
|
110
|
+
elsif script.path
|
111
|
+
if script.path.start_with? '/'
|
112
|
+
path = (ENV['DOCUMENT_ROOT'] + script.path).untaint
|
113
|
+
else
|
114
|
+
path = File.expand_path(script.path, Wunderbar::Asset.root)
|
115
|
+
end
|
116
|
+
setup << File.read(script.options[:server] || path)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# concatenate and execute scripts on server
|
121
|
+
if browserify
|
122
|
+
setup << requires.map {|key, value| "#{key}=require(#{value.inspect})"}.
|
123
|
+
join(';')
|
124
|
+
end
|
125
|
+
scripts.unshift *setup.uniq
|
126
|
+
File.write('/home/rubys/tmp/scripts', scripts.join(";\n") + ";\n" + server)
|
127
|
+
html = Wunderbar::Render.eval(scripts, server)
|
128
|
+
|
129
|
+
# insert results into target
|
130
|
+
nodes = builder._ { html }
|
131
|
+
begin
|
132
|
+
nodes.each {|node| node.parent = target}
|
133
|
+
target.children += nodes
|
134
|
+
rescue => e
|
135
|
+
div = Wunderbar::Node.new('span',
|
136
|
+
style: 'background-color:#ff0; margin: 1em 0; padding: 1em; ' +
|
137
|
+
'border: 4px solid red; border-radius: 1em')
|
138
|
+
div.children << Wunderbar::Node.new('pre', e.to_s)
|
139
|
+
div.children << Wunderbar::Node.new('pre', e.backtrace)
|
140
|
+
div.children << Wunderbar::Node.new('pre', html)
|
141
|
+
div.children << Wunderbar::Node.new('pre', nodes.inspect)
|
142
|
+
div.children.each {|node| node.parent = div}
|
143
|
+
target.children << div
|
144
|
+
end
|
145
|
+
|
146
|
+
# add client side script
|
147
|
+
tag! 'script', Wunderbar::ClientScriptNode, client
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Ruby2JS::Serializer
|
152
|
+
def etag
|
153
|
+
@etag ||= Digest::MD5.hexdigest(to_str)
|
154
|
+
end
|
155
|
+
|
156
|
+
def sourcemap_etag
|
157
|
+
@sourcemap_etag ||= Digest::MD5.hexdigest(sourcemap.inspect)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
get %r{/([-\w]+)\.js} do |script|
|
162
|
+
file = File.join(settings.views, "#{script}.js.rb")
|
163
|
+
begin
|
164
|
+
js = Wunderbar::Asset.convert(file)
|
165
|
+
pass unless js
|
166
|
+
rescue Exception => e
|
167
|
+
Wunderbar.error e.to_s
|
168
|
+
return [500, {'Content-type' => 'text/plain'}, "*** ERROR ***\n\n#{e}"]
|
169
|
+
end
|
170
|
+
|
171
|
+
response.headers['SourceMap'] = "#{script}.js.map"
|
172
|
+
|
173
|
+
etag js.etag
|
174
|
+
|
175
|
+
content_type 'application/javascript; charset=utf-8'
|
176
|
+
|
177
|
+
js.to_s
|
178
|
+
end
|
179
|
+
|
180
|
+
get %r{/((?:\w+\/)*[-\w]+)\.js.rb} do |script|
|
181
|
+
file = File.join(settings.views, "#{script}.js.rb")
|
182
|
+
pass unless File.exist? file
|
183
|
+
send_file file
|
184
|
+
end
|
185
|
+
|
186
|
+
get %r{/([-\w]+)\.js.map} do |script|
|
187
|
+
file = File.join(settings.views, "#{script}.js.rb")
|
188
|
+
js = Wunderbar::Asset.convert(file)
|
189
|
+
pass unless js
|
190
|
+
|
191
|
+
etag js.sourcemap_etag
|
192
|
+
|
193
|
+
sourcemap = js.sourcemap
|
194
|
+
|
195
|
+
content_type 'application/json;charset:utf8'
|
196
|
+
base = settings.views + '/'
|
197
|
+
sourcemap[:file] = sourcemap[:file].sub base, ''
|
198
|
+
sourcemap[:sources].map! {|source| source.sub base, ''}
|
199
|
+
JSON.pretty_generate sourcemap
|
200
|
+
end
|