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 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