Skip to content

Commit 10375b0

Browse files
committed
integrated hot reloader
1 parent 3bbfb6e commit 10375b0

File tree

16 files changed

+3679
-3
lines changed

16 files changed

+3679
-3
lines changed

ruby/hyper-component/lib/hyperstack/internal/component/callbacks.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def define_callback(callback_name, &after_define_hook)
2727
define_singleton_method(callback_name) do |*args, &block|
2828
send(wrapper_name).concat(args)
2929
send(wrapper_name).push(block) if block_given?
30+
HotLoader.record(self, "@#{wrapper_name}", 4, *args, block)
3031
after_define_hook.call(*args, &block) if after_define_hook
3132
end
3233
end

ruby/hyperstack-config/Gemfile.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ PATH
2222
specs:
2323
hyperstack-config (0.1)
2424
libv8 (~> 6.3.0)
25+
listen (~> 3.0)
2526
mini_racer (~> 0.1.15)
2627
opal (>= 0.11.0, < 0.12.0)
2728
opal-browser (~> 0.2.0)
2829
uglifier
30+
websocket
2931

3032
GEM
3133
remote: https://rubygems.org/
@@ -117,6 +119,10 @@ GEM
117119
railties (>= 4.2.0)
118120
thor (>= 0.14, < 2.0)
119121
libv8 (6.3.292.48.1-x86_64-darwin-15)
122+
listen (3.1.5)
123+
rb-fsevent (~> 0.9, >= 0.9.4)
124+
rb-inotify (~> 0.9, >= 0.9.7)
125+
ruby_dep (~> 1.2)
120126
loofah (2.2.2)
121127
crass (~> 1.0.2)
122128
nokogiri (>= 1.5.9)
@@ -201,6 +207,9 @@ GEM
201207
rainbow (2.2.2)
202208
rake
203209
rake (12.3.1)
210+
rb-fsevent (0.10.3)
211+
rb-inotify (0.9.10)
212+
ffi (>= 0.5.0, < 2)
204213
rspec (3.7.0)
205214
rspec-core (~> 3.7.0)
206215
rspec-expectations (~> 3.7.0)
@@ -230,6 +239,7 @@ GEM
230239
ruby-progressbar (~> 1.7)
231240
unicode-display_width (~> 1.0, >= 1.0.1)
232241
ruby-progressbar (1.10.0)
242+
ruby_dep (1.5.0)
233243
rubyzip (1.2.2)
234244
selenium-webdriver (3.14.1)
235245
childprocess (~> 0.5)
@@ -264,6 +274,7 @@ GEM
264274
nokogiri (~> 1.6)
265275
rubyzip (~> 1.0)
266276
selenium-webdriver (~> 3.0)
277+
websocket (1.2.8)
267278
websocket-driver (0.7.0)
268279
websocket-extensions (>= 0.1.0)
269280
websocket-extensions (0.1.3)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'bundler/setup'
4+
require "hyperstack/hot_loader/server"
5+
6+
options = {:port => 25222, :directories => []}
7+
OptionParser.new do |opts|
8+
opts.banner = "Usage: opal-hot-reloader [options]"
9+
10+
opts.on("-p", '--port [INTEGER]', Integer, 'port to run on, defaults to 25222') do |v|
11+
options[:port] = v
12+
end
13+
14+
opts.on("-d", '--directories x,y,z', Array, "comma separated directories to watch. Ex. to add 2 directories '-d app/assets/js,app/client/components'. Directoriess automatically included if they exist are:\n\t\t* app/assets/javascripts\n\t\t* app/views/components") do |v|
15+
options[:directories] = v
16+
end
17+
18+
end.parse!
19+
20+
server = Hyperstack::HotLoader::Server.new(options)
21+
puts "Listening on port #{options[:port]}, watching for changes in #{options[:directories].join(', ')}"
22+
server.loop

ruby/hyperstack-config/hyperstack-config.gemspec

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ Gem::Specification.new do |spec|
1717
# }
1818

1919
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20-
spec.bindir = 'exe'
21-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20+
#spec.bindir = 'exe'
21+
#spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22+
spec.executables << 'hyperstack-hotloader'
2223
spec.require_paths = ['lib']
2324

2425
spec.add_dependency 'libv8', '~> 6.3.0' # see https://github.com/discourse/mini_racer/issues/92
26+
spec.add_dependency 'listen', '~> 3.0' # for hot loader
2527
spec.add_dependency 'mini_racer', '~> 0.1.15'
2628
spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
2729
spec.add_dependency 'opal-browser', '~> 0.2.0'
2830
spec.add_dependency 'uglifier'
31+
spec.add_dependency 'websocket' # for hot loader
2932

3033

3134
spec.add_development_dependency 'bundler', '~> 1.16.0'

ruby/hyperstack-config/lib/hyperstack-config.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'hyperstack/on_client'
99
require 'hyperstack/active_support_string_inquirer.rb'
1010
require 'hyperstack_env'
11+
require 'hyperstack/hot_loader/stub'
1112
else
1213
require 'opal'
1314
require 'opal-browser'
@@ -23,12 +24,15 @@
2324
require 'hyperstack/js_imports'
2425
require 'hyperstack/client_readers'
2526
require 'hyperstack/on_client'
27+
2628
if defined? Rails
2729
require 'hyperstack/rail_tie'
2830
end
2931
require 'hyperstack/active_support_string_inquirer.rb' unless defined? ActiveSupport
3032
require 'hyperstack/env'
3133
require 'hyperstack/on_error'
34+
Hyperstack.define_setting :hotloader_port, 25222
35+
Hyperstack.define_setting :hotloader_ping, nil
3236
Hyperstack.import 'opal', gem: true
3337
Hyperstack.import 'browser', client_only: true
3438
Hyperstack.import 'hyperstack-config', gem: true
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
console.log('hotloader config doing its thing')
2+
Hyperstack.hotloader.port = <%= Hyperstack.hotloader_port %>
3+
Hyperstack.hotloader.ping = <%= !!Hyperstack.hotloader_ping %>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//= require hyperstack-loader-system-code
22
//= require hyperstack-loader-application
3+
//= require hyperstack-hotloader-config
34
Opal.load('hyperstack-loader-system-code')
45
Opal.load('hyperstack-loader-application')
6+
Hyperstack.hotloader(Hyperstack.hotloader.port, Hyperstack.hotloader.ping)

ruby/hyperstack-config/lib/hyperstack/context.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def self.reset!(reboot = true)
2525
# @context.
2626
if @context
2727
@context.each do |ctx, vars|
28-
vars.each { |var, init| ctx.instance_variable_set(var, init) }
28+
vars.each { |var, init| ctx.instance_variable_set(var, init.dup) }
2929
end
3030
Hyperstack::Application::Boot.run if reboot
3131
else
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
require 'hyperstack/hot_loader/add_error_boundry'
2+
require 'hyperstack/hot_loader/stack-trace.js'
3+
require 'hyperstack/hot_loader/css_reloader'
4+
require 'opal-parser' # gives me 'eval', for hot-loading code
5+
6+
require 'json'
7+
require 'hyperstack/hot_loader/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
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)