hyperstack-config 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +29 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +303 -0
  6. data/README.md +73 -0
  7. data/Rakefile +11 -0
  8. data/bin/hyperstack-hotloader +22 -0
  9. data/hyperstack-config.gemspec +45 -0
  10. data/lib/hyperstack-config.rb +45 -0
  11. data/lib/hyperstack-hotloader-config.js.erb +7 -0
  12. data/lib/hyperstack-loader-application.rb.erb +1 -0
  13. data/lib/hyperstack-loader-system-code.rb.erb +1 -0
  14. data/lib/hyperstack-loader.js +6 -0
  15. data/lib/hyperstack-prerender-loader-application.rb.erb +1 -0
  16. data/lib/hyperstack-prerender-loader-system-code.rb.erb +1 -0
  17. data/lib/hyperstack-prerender-loader.js +4 -0
  18. data/lib/hyperstack/active_support_string_inquirer.rb +32 -0
  19. data/lib/hyperstack/autoloader.rb +140 -0
  20. data/lib/hyperstack/autoloader_starter.rb +15 -0
  21. data/lib/hyperstack/boot.rb +41 -0
  22. data/lib/hyperstack/client_readers.rb +19 -0
  23. data/lib/hyperstack/client_stubs.rb +12 -0
  24. data/lib/hyperstack/config/version.rb +5 -0
  25. data/lib/hyperstack/config_settings.rb +49 -0
  26. data/lib/hyperstack/context.rb +36 -0
  27. data/lib/hyperstack/deprecation_warning.rb +10 -0
  28. data/lib/hyperstack/env.rb +10 -0
  29. data/lib/hyperstack/environment/development/hyperstack_env.rb +5 -0
  30. data/lib/hyperstack/environment/production/hyperstack_env.rb +5 -0
  31. data/lib/hyperstack/environment/staging/hyperstack_env.rb +5 -0
  32. data/lib/hyperstack/environment/test/hyperstack_env.rb +5 -0
  33. data/lib/hyperstack/hotloader.rb +129 -0
  34. data/lib/hyperstack/hotloader/add_error_boundry.rb +31 -0
  35. data/lib/hyperstack/hotloader/css_reloader.rb +41 -0
  36. data/lib/hyperstack/hotloader/server.rb +296 -0
  37. data/lib/hyperstack/hotloader/short_cut.js +9 -0
  38. data/lib/hyperstack/hotloader/socket.rb +136 -0
  39. data/lib/hyperstack/hotloader/stack-trace.js +2977 -0
  40. data/lib/hyperstack/hotloader/stub.rb +10 -0
  41. data/lib/hyperstack/imports.rb +104 -0
  42. data/lib/hyperstack/js_imports.rb +18 -0
  43. data/lib/hyperstack/on_client.rb +5 -0
  44. data/lib/hyperstack/on_error.rb +5 -0
  45. data/lib/hyperstack/rail_tie.rb +87 -0
  46. data/lib/hyperstack/string.rb +6 -0
  47. metadata +350 -0
@@ -0,0 +1,36 @@
1
+ module Hyperstack
2
+ # Allows interactive systems to reset context to the state at boot. Any
3
+ # modules or classes that set context instance variables to hold things like
4
+ # call backs should use Hyperstack::Context.set_var(self, :@var_name) { .... }
5
+ # the provided block will be rerun and the instance variable re-initialized
6
+ # when the reset! method is called
7
+ module Context
8
+ # Replace @foo ||= ... with
9
+ # Context.set_var(self, :@foo) { ... }
10
+ # If reset! has been called then the instance variable will be record, and
11
+ # will be reset on the next call to reset!
12
+ # If you want to record the current value of the instance variable then set
13
+ # force to true.
14
+ def self.set_var(ctx, var, force: nil)
15
+ inst_value_b4 = ctx.instance_variable_get(var)
16
+ if @context && !@context[ctx].key?(var) && (force || !inst_value_b4)
17
+ @context[ctx][var] = (inst_value_b4 && inst_value_b4.dup)
18
+ end
19
+ inst_value_b4 || ctx.instance_variable_set(var, yield)
20
+ end
21
+
22
+ def self.reset!(reboot = true)
23
+ # if @context is already initialized then reset all the instance
24
+ # vars using their corresponding blocks. Otherwise initialize
25
+ # @context.
26
+ if @context
27
+ @context.each do |ctx, vars|
28
+ vars.each { |var, init| ctx.instance_variable_set(var, init.dup) }
29
+ end
30
+ Hyperstack::Application::Boot.run if reboot
31
+ else
32
+ @context = Hash.new { |h, k| h[k] = {} }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module Hyperstack
2
+ def self.deprecation_warning(name, message)
3
+ return if env.production?
4
+ @deprecation_messages ||= []
5
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
6
+ return if @deprecation_messages.include? message
7
+ @deprecation_messages << message
8
+ `console.warn.apply(console, [message])`
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= begin
4
+ env = Rails.env if defined? Rails
5
+ env ||= ENV['RACK_ENV']
6
+ env = 'development' unless %w[development production test staging].include? env
7
+ ActiveSupport::StringInquirer.new(env)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('development')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('production')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('staging')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('test')
4
+ end
5
+ end
@@ -0,0 +1,129 @@
1
+ require 'hyperstack/hotloader/add_error_boundry'
2
+ require 'hyperstack/hotloader/stack-trace.js'
3
+ require 'hyperstack/hotloader/css_reloader'
4
+ require 'opal-parser' # gives me 'eval', for hot-loading code
5
+
6
+ require 'json'
7
+ require 'hyperstack/hotloader/short_cut.js'
8
+
9
+ # Opal client to support hot reloading
10
+ $eval_proc = proc do |file_name, s|
11
+ $_hyperstack_reloader_file_name = file_name
12
+ eval s
13
+ end
14
+
15
+ module Hyperstack
16
+
17
+ class Hotloader
18
+ def self.callbackmaps
19
+ @@callbackmaps ||= Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = Array.new }}}
20
+ end
21
+
22
+ def self.record(klass, instance_var, depth, *items)
23
+ if $_hyperstack_reloader_file_name
24
+ callbackmaps[$_hyperstack_reloader_file_name][klass][instance_var].concat items
25
+ else
26
+ callback = lambda do |stack_frames|
27
+ file_name = `#{stack_frames[depth]}.fileName`
28
+ match = /^(.+\/assets\/)(.+\/)\2/.match(file_name)
29
+ if match
30
+ file_name = file_name.gsub(match[1]+match[2], '')
31
+ callbackmaps[file_name][klass][instance_var].concat items
32
+ end
33
+ end
34
+ error = lambda do |err|
35
+ `console.error(#{"hyperstack hot loader could not find source file for callback: #{err}"})`
36
+ end
37
+ `StackTrace.get().then(#{callback}).catch(#{error})`
38
+ end
39
+ end
40
+
41
+ def self.remove(file_name)
42
+ callbackmaps[file_name].each do |klass, instance_vars|
43
+ instance_vars.each do |instance_var, items|
44
+ klass.instance_variable_get(instance_var).reject! { |item| items.include? item }
45
+ end
46
+ end
47
+ end
48
+
49
+ def connect_to_websocket(port)
50
+ host = `window.location.host`.sub(/:\d+/, '')
51
+ host = '127.0.0.1' if host == ''
52
+ protocol = `window.location.protocol` == 'https:' ? 'wss:' : 'ws:'
53
+ ws_url = "#{host}:#{port}"
54
+ puts "Hot-Reloader connecting to #{ws_url}"
55
+ ws = `new WebSocket(#{protocol} + '//' + #{ws_url})`
56
+ `#{ws}.onmessage = #{lambda { |e| reload(e) }}`
57
+ `setInterval(function() { #{ws}.send('') }, #{@ping * 1000})` if @ping
58
+ end
59
+
60
+ def notify_error(reload_request)
61
+ msg = "Hotloader #{reload_request[:filename]} RELOAD ERROR:\n\n#{$!}"
62
+ puts msg
63
+ alert msg if use_alert?
64
+ end
65
+
66
+ @@USE_ALERT = true
67
+ def self.alerts_on!
68
+ @@USE_ALERT = true
69
+ end
70
+
71
+ def self.alerts_off!
72
+ @@USE_ALERT = false
73
+ end
74
+
75
+ def use_alert?
76
+ @@USE_ALERT
77
+ end
78
+
79
+ def reload(e)
80
+ reload_request = JSON.parse(`e.data`)
81
+ if reload_request[:type] == "ruby"
82
+ puts "Reloading #{reload_request[:filename]} (asset_path: #{reload_request[:asset_path]})"
83
+ begin
84
+ #Hyperstack::Context.reset! false
85
+ file_name = reload_request[:asset_path] #.gsub(/.+hyperstack\//, '')
86
+ Hotloader.remove(file_name)
87
+ $eval_proc.call file_name, reload_request[:source_code]
88
+ rescue
89
+ notify_error(reload_request)
90
+ end
91
+ if @reload_post_callback
92
+ @reload_post_callback.call
93
+ else
94
+ puts "no reloading callback to call"
95
+ end
96
+ end
97
+ if reload_request[:type] == "css"
98
+ @css_reloader.reload(reload_request, `document`)
99
+ end
100
+ end
101
+
102
+ # @param port [Integer] opal hot reloader port to connect to
103
+ # @param reload_post_callback [Proc] optional callback to be called after re evaluating a file for example in react.rb files we want to do a React::Component.force_update!
104
+ def initialize(port=25222, ping=nil, &reload_post_callback)
105
+ @port = port
106
+ @reload_post_callback = reload_post_callback
107
+ @css_reloader = CssReloader.new
108
+ @ping = ping
109
+ end
110
+ # Opens a websocket connection that evaluates new files and runs the optional @reload_post_callback
111
+ def listen
112
+ connect_to_websocket(@port)
113
+ end
114
+
115
+ def self.listen(port=25222, ping=nil)
116
+ ::Hyperstack::Internal::Component::TopLevelRailsComponent.include AddErrorBoundry
117
+ @server = Hotloader.new(port, ping) do
118
+ # TODO: check this out when Operations are integrated
119
+ # if defined?(Hyperloop::Internal::Operation::ClientDrivers) &&
120
+ # Hyperloop::ClientDrivers.respond_to?(:initialize_client_drivers_on_boot)
121
+ # Hyperloop::ClientDrivers.initialize_client_drivers_on_boot
122
+ # end
123
+ Hyperstack::Component.force_update!
124
+ end
125
+ @server.listen
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,31 @@
1
+ module Hyperstack
2
+ class Hotloader
3
+ module AddErrorBoundry
4
+ def self.included(base)
5
+ base.after_error do |*err|
6
+ @err = err
7
+ Hyperstack::Component.force_update!
8
+ end
9
+ base.define_method :render do
10
+ @err ? parse_display_and_clear_error : top_level_render
11
+ end
12
+ end
13
+
14
+ def parse_display_and_clear_error
15
+ e = @err[0]
16
+ component_stack = @err[1]['componentStack'].split("\n ")
17
+ @err = nil
18
+ display_error(e, component_stack)
19
+ end
20
+
21
+ def display_error(e, component_stack)
22
+ DIV do
23
+ DIV { "Uncaught error: #{e}" }
24
+ component_stack.each do |line|
25
+ DIV { line }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ require 'native'
2
+ module Hyperstack
3
+ class Hotloader
4
+ class CssReloader
5
+
6
+ def reload(reload_request, document)
7
+ url = reload_request[:url]
8
+ puts "Reloading CSS: #{url}"
9
+ to_append = "t_hot_reload=#{Time.now.to_i}"
10
+ links = Native(`document.getElementsByTagName("link")`)
11
+ (0..links.length-1).each { |i|
12
+ link = links[i]
13
+ if link.rel == 'stylesheet' && is_matching_stylesheet?(link.href, url)
14
+ if link.href !~ /\?/
15
+ link.href += "?#{to_append}"
16
+ else
17
+ if link.href !~ /t_hot_reload/
18
+ link.href += "&#{to_append}"
19
+ else
20
+ link.href = link.href.sub(/t_hot_reload=\d+/, to_append)
21
+ end
22
+ end
23
+ end
24
+ }
25
+ end
26
+
27
+ def is_matching_stylesheet?(href, url)
28
+ # straight match, like in Rack::Sass::Plugin
29
+ if href.index(url)
30
+ true
31
+ else
32
+ # Rails asset pipeline match
33
+ url_base = File.basename(url).sub(/\.s?css+/, '').sub(/\.s?css+/, '')
34
+ href_base = File.basename(href).sub(/\.self-?.*.css.+/, '')
35
+ url_base == href_base
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,296 @@
1
+ require 'websocket'
2
+ require 'socket'
3
+ require 'fiber'
4
+ require 'listen'
5
+ require 'optparse'
6
+ require 'json'
7
+
8
+ module Hyperstack
9
+ class Hotloader
10
+ # Most of this lifted from https://github.com/saward/Rubame
11
+ class Server
12
+
13
+ attr_reader :directories
14
+ def initialize(options)
15
+ Socket.do_not_reverse_lookup
16
+ @hostname = '0.0.0.0'
17
+ @port = options[:port]
18
+ setup_directories(options)
19
+
20
+ @reading = []
21
+ @writing = []
22
+
23
+ @clients = {} # Socket as key, and Client as value
24
+
25
+ @socket = TCPServer.new(@hostname, @port)
26
+ @reading.push @socket
27
+ end
28
+
29
+ # adds known directories automatically if they exist
30
+ # - rails js app/assets/javascripts app/assets/stylesheets
31
+ # - reactrb rails defaults app/views/components
32
+ # - you tell me and I'll add them
33
+ def setup_directories(options)
34
+ @directories = options[:directories] || []
35
+ [
36
+ 'app/assets/javascripts',
37
+ 'app/assets/stylesheets',
38
+ 'app/views/components'
39
+ ].each { |known_dir|
40
+ if !@directories.include?(known_dir) && File.exists?(known_dir)
41
+ @directories << known_dir
42
+ end
43
+ }
44
+ end
45
+
46
+ def accept
47
+ socket = @socket.accept_nonblock
48
+ @reading.push socket
49
+ handshake = WebSocket::Handshake::Server.new
50
+ client = Client.new(socket, handshake, self)
51
+
52
+ while line = socket.gets
53
+ client.handshake << line
54
+ break if client.handshake.finished?
55
+ end
56
+ if client.handshake.valid?
57
+ @clients[socket] = client
58
+ client.write handshake.to_s
59
+ client.opened = true
60
+ return client
61
+ else
62
+ close(client)
63
+ end
64
+ return nil
65
+ end
66
+
67
+ def send_updated_file(modified_file)
68
+ if modified_file =~ /\.rb(\.erb)?$/
69
+ if File.exist?(modified_file)
70
+ file_contents = File.read(modified_file).force_encoding(Encoding::UTF_8)
71
+ end
72
+ relative_path = Pathname.new(modified_file).relative_path_from(Pathname.new(Dir.pwd)).to_s
73
+ asset_path = nil
74
+ @directories.each do |directory|
75
+ next unless relative_path =~ /^#{directory}/
76
+ asset_path = relative_path.gsub("#{directory}/", '')
77
+ end
78
+ update = {
79
+ type: 'ruby',
80
+ filename: modified_file,
81
+ asset_path: asset_path,
82
+ source_code: file_contents || ''
83
+ }.to_json
84
+ end
85
+ if modified_file =~ /\.s?[ac]ss$/ && File.exist?(modified_file)
86
+ # Don't know how to remove css definitions at the moment
87
+ # TODO: Switch from hard-wired path assumptions to using SASS/sprockets config
88
+ relative_path = Pathname.new(modified_file).relative_path_from(Pathname.new(Dir.pwd))
89
+ url = relative_path.to_s
90
+ .sub('public/','')
91
+ .sub('/sass/','/')
92
+ .sub(/\.s[ac]ss/, '.css')
93
+ update = {
94
+ type: 'css',
95
+ filename: modified_file,
96
+ url: url
97
+ }.to_json
98
+ end
99
+ if update
100
+ @clients.each { |socket, client| client.send(update) }
101
+ end
102
+ end
103
+
104
+ PROGRAM = 'opal-hot-reloader'
105
+ def loop
106
+ listener = Listen.to(*@directories, only: %r{\.(rb(\.erb)?|s?[ac]ss)$}) do |modified, added, removed|
107
+ (removed + modified + added).each { |file| send_updated_file(file) }
108
+ puts "removed absolute path: #{removed}"
109
+ puts "modified absolute path: #{modified}"
110
+ puts "added absolute path: #{added}"
111
+ end
112
+ listener.start
113
+
114
+ puts "#{PROGRAM}: starting..."
115
+ while (!$quit)
116
+ run do |client|
117
+ client.onopen do
118
+ puts "#{PROGRAM}: client open"
119
+ end
120
+ client.onmessage do |mess|
121
+ puts "PROGRAM: message received: #{mess}" unless mess == ''
122
+ end
123
+ client.onclose do
124
+ puts "#{PROGRAM}: client closed"
125
+ end
126
+ end
127
+ sleep 0.2
128
+ end
129
+ end
130
+
131
+ def read(client)
132
+
133
+ pairs = client.socket.recvfrom(2000)
134
+ messages = []
135
+
136
+ if pairs[0].length == 0
137
+ close(client)
138
+ else
139
+ client.frame << pairs[0]
140
+
141
+ while f = client.frame.next
142
+ if (f.type == :close)
143
+ close(client)
144
+ return messages
145
+ else
146
+ messages.push f
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ return messages
153
+
154
+ end
155
+
156
+ def close(client)
157
+ @reading.delete client.socket
158
+ @clients.delete client.socket
159
+ begin
160
+ client.socket.close
161
+ rescue
162
+ end
163
+ client.closed = true
164
+ end
165
+
166
+ def run(time = 0, &blk)
167
+ readable, writable = IO.select(@reading, @writing, nil, 0)
168
+
169
+ if readable
170
+ readable.each do |socket|
171
+ client = @clients[socket]
172
+ if socket == @socket
173
+ client = accept
174
+ else
175
+ msg = read(client)
176
+ client.messaged = msg
177
+ end
178
+
179
+ blk.call(client) if client and blk
180
+ end
181
+ end
182
+
183
+ # Check for lazy send items
184
+ timer_start = Time.now
185
+ time_passed = 0
186
+ begin
187
+ @clients.each do |s, c|
188
+ c.send_some_lazy(5)
189
+ end
190
+ time_passed = Time.now - timer_start
191
+ end while time_passed < time
192
+ end
193
+
194
+ def stop
195
+ @socket.close
196
+ end
197
+ end
198
+
199
+ class Client
200
+ attr_accessor :socket, :handshake, :frame, :opened, :messaged, :closed
201
+
202
+ def initialize(socket, handshake, server)
203
+ @socket = socket
204
+ @handshake = handshake
205
+ @frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
206
+ @opened = false
207
+ @messaged = []
208
+ @lazy_queue = []
209
+ @lazy_current_queue = nil
210
+ @closed = false
211
+ @server = server
212
+ end
213
+
214
+ def write(data)
215
+ @socket.write data
216
+ end
217
+
218
+ def send(data)
219
+ frame = WebSocket::Frame::Outgoing::Server.new(:version => @handshake.version, :data => data, :type => :text)
220
+ begin
221
+ @socket.write frame
222
+ @socket.flush
223
+ rescue
224
+ @server.close(self) unless @closed
225
+ end
226
+ end
227
+
228
+ def lazy_send(data)
229
+ @lazy_queue.push data
230
+ end
231
+
232
+ def get_lazy_fiber
233
+ # Create the fiber if needed
234
+ if @lazy_fiber == nil or !@lazy_fiber.alive?
235
+ @lazy_fiber = Fiber.new do
236
+ @lazy_current_queue.each do |data|
237
+ send(data)
238
+ Fiber.yield unless @lazy_current_queue[-1] == data
239
+ end
240
+ end
241
+ end
242
+
243
+ return @lazy_fiber
244
+ end
245
+
246
+ def send_some_lazy(count)
247
+ # To save on cpu cycles, we don't want to be chopping and changing arrays, which could get quite large. Instead,
248
+ # we iterate over an array which we are sure won't change out from underneath us.
249
+ unless @lazy_current_queue
250
+ @lazy_current_queue = @lazy_queue
251
+ @lazy_queue = []
252
+ end
253
+
254
+ completed = 0
255
+ begin
256
+ get_lazy_fiber.resume
257
+ completed += 1
258
+ end while (@lazy_queue.count > 0 or @lazy_current_queue.count > 0) and completed < count
259
+
260
+ end
261
+
262
+ def onopen(&blk)
263
+ if @opened
264
+ begin
265
+ blk.call
266
+ ensure
267
+ @opened = false
268
+ end
269
+ end
270
+ end
271
+
272
+ def onmessage(&blk)
273
+ if @messaged.size > 0
274
+ begin
275
+ @messaged.each do |x|
276
+ blk.call(x.to_s)
277
+ end
278
+ ensure
279
+ @messaged = []
280
+ end
281
+ end
282
+ end
283
+
284
+ def onclose(&blk)
285
+ if @closed
286
+ begin
287
+ blk.call
288
+ ensure
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ line = 0
295
+ end
296
+ end