wunderbar 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2dfe16b13a2f629c45680c78f47f43767405965
4
- data.tar.gz: 18a1e83ff86efe5020049b63a0d3c9262f8a92e0
3
+ metadata.gz: 5d0bf66bfd444cace4acde0057ba3bf0ac53f02f
4
+ data.tar.gz: 223ff899aa06573c0b4d53b6f79a2c23755a64de
5
5
  SHA512:
6
- metadata.gz: 3290901783f5f6ae6537878a5a1ae2f321046b419e5687e8d274a9634853dfba02d81e7fa1ee401da48ce8bbd3a68edf7db289d8d55ffaa16de46da2cdfb1bff
7
- data.tar.gz: 5234d352fe6213551c4e68f2f9a8a3dcc252d4c22f73ce24d73613f7fa05212b491133acd27af844a55937535071fd3afd7f1006167903aba9ab0fac25dd6a95
6
+ metadata.gz: 6025587bdcf0cf572312da1006565ac289f4a0ec2615ace9f8fdea71c891544c461b13efef0a5ca1f30fb7a1c3d10bc154426beb539e9adfd1f2a66bcfe8df2c
7
+ data.tar.gz: 0d237270a67cbc952272be4b54b21af56646b447f8a91e1ff415fb5b9be5a26bd30191ffe9056e2f7c973b85b1376bf2b83f48a6cb0f327e54411fae45f3ae34
@@ -171,15 +171,21 @@ module Wunderbar
171
171
  end
172
172
 
173
173
  def text! text
174
- @node.children << TextNode.new(text)
174
+ text = TextNode.new(text)
175
+ @node.children << text
176
+ text
175
177
  end
176
178
 
177
179
  def declare! *args
178
- @node.children << DocTypeNode.new(*args)
180
+ doctype = DocTypeNode.new(*args)
181
+ @node.children << doctype
182
+ doctype
179
183
  end
180
184
 
181
185
  def comment! text
182
- @node.children << CommentNode.new(text)
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!
@@ -1,196 +1,35 @@
1
- unless defined? Sinatra
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, react: true
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, react: true,
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::Asset
21
- @@cached_scripts = {}
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
- # extract content of scripts
80
- scripts.map! do |script|
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
- class Ruby2JS::Serializer
144
- def etag
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 sourcemap_etag
149
- @sourcemap_etag ||= Digest::MD5.hexdigest(sourcemap.inspect)
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('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;') +
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