opal_hot_reloader 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/opal-hot-reloader +22 -0
- data/bin/setup +7 -0
- data/lib/opal_hot_reloader.rb +9 -0
- data/lib/opal_hot_reloader/server.rb +284 -0
- data/lib/opal_hot_reloader/version.rb +3 -0
- data/opal/opal_hot_reloader.rb +74 -0
- data/opal/opal_hot_reloader/css_reloader.rb +39 -0
- data/opal/opal_hot_reloader/foo.rb +6 -0
- data/opal/opal_hot_reloader/reactrb_patches.rb +60 -0
- data/opal/opal_hot_reloader/socket.rb +133 -0
- data/opal_hot_reloader.gemspec +30 -0
- data/spec-opal/opal_hot_reloader/css_reloader_spec.rb +82 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4556d3643d7dae2b15f95ee81bc895b3f4d7d5e9
|
4
|
+
data.tar.gz: dc4f19e56f5f325f98b2d317c6d05b74acd6e1b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b202a24741fd488838f95030164a223c9f344e60b772cd78d40a56ceafb764e90dc89869c7e45e3a1c334db5d6f05c026b9d66676254db5aca3e86437a38fc22
|
7
|
+
data.tar.gz: 608dd3b9986e0343b3d6d1bc5b876af7d86319b72fef758c924c560d49706e098bd94c3131084d7a69fac7aff89ece56b7fbab02456dcd0bb3ab5037a45de321
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Forrest Chang
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# OpalHotReloader
|
2
|
+
|
3
|
+
opal-hot-reloader is a hot reloader for [Opal](http://opalrb.org). It has built in [react.rb](http://reactrb.org) support and can be extended to support an arbitrary hook to be run after code is evaluted. It watches directories specified and when a file is modified it pushes the change via websocket to the client. opal-hot-reloader reloader will reload the following without reloading the whole page and destroying any state the page has.
|
4
|
+
- opal code
|
5
|
+
- css (currently supporting Rack::Sass:Place and Rails asset pipeline)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'opal_hot_reloader' # currently on github only, gem coming soon
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install opal_hot_reloader
|
22
|
+
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Server Setup
|
27
|
+
|
28
|
+
#### NOTE: fixed for Opal 0.10 as of commit d2dc849 - repull if not working for 0.10.0
|
29
|
+
|
30
|
+
After adding `gem "opal_hot_loader"` to your gemfile, you must start the server-side part. This will allow websocket connections, and whenever a file is changed it will send it via the socket to listening clients.
|
31
|
+
|
32
|
+
To start the server-side of the hotloader:
|
33
|
+
```
|
34
|
+
opal-hot-reloader -p 25222 -d dir1,dir2,dir3
|
35
|
+
|
36
|
+
Usage: opal-hot-reloader [options]
|
37
|
+
-p, --port [INTEGER] port to run on, defaults to 25222
|
38
|
+
-d, --directories x,y,z comma separated directories to watch
|
39
|
+
```
|
40
|
+
|
41
|
+
For a react.rb Rails app, opal-hot-reloader automatically includes app/assets/javascripts,app/views/components if they exist
|
42
|
+
|
43
|
+
Example adding 2 directories
|
44
|
+
```
|
45
|
+
opal-hot-reloader -d app/js,app/client/components
|
46
|
+
```
|
47
|
+
|
48
|
+
You may consider using [foreman](https://github.com/ddollar/foreman/)
|
49
|
+
and starting the Rails server and hot reloader at the same time. If
|
50
|
+
you are doing react.rb development w/Rails, you may already be doing
|
51
|
+
so with the Rails server and webpack.
|
52
|
+
|
53
|
+
|
54
|
+
### Client Setup
|
55
|
+
|
56
|
+
Require in an opal file (for opal-rails apps application.js.rb is a good place) and start listening for changes:
|
57
|
+
|
58
|
+
#### Note: OpalHotReloader.listen() deprecation
|
59
|
+
OpalHotReloader.listen() used to take a 2nd Boolean parameter to signify a reactrb app. This is deprecated and no longer needed.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'opal_hot_reloader'
|
63
|
+
|
64
|
+
# @param port [Integer] opal hot reloader port to connect to. Defaults to 25222 to match opal-hot-loader default
|
65
|
+
OpalHotReloader.listen(25222)
|
66
|
+
```
|
67
|
+
|
68
|
+
If you are using the default port then you can just call:
|
69
|
+
```
|
70
|
+
OpalHotReloader.listen
|
71
|
+
```
|
72
|
+
|
73
|
+
This will open up a websocket from the client to the server on the given port. The server-side should already be running.
|
74
|
+
|
75
|
+
Enjoy!
|
76
|
+
|
77
|
+
## Vision
|
78
|
+
|
79
|
+
Some of you might be asking? Why do this, isn't this reinventing the
|
80
|
+
wheel by programs like webpack, etc.? I should mention that
|
81
|
+
reinventing the wheel seems happens all the time in the Javascript
|
82
|
+
world.
|
83
|
+
|
84
|
+
Yes and no. opal-hot-reloader is an "All Ruby(Opal)", self contained
|
85
|
+
system, so if you're doing any kind of Opal frontend/Ruby backend
|
86
|
+
webserver type of project, you will be able to just drop in
|
87
|
+
opal-hot-reloader and it will work out of the box without having
|
88
|
+
install/configure webpack or similar.
|
89
|
+
|
90
|
+
I believe it will be most advantageous for Opal to be able to straddle
|
91
|
+
a hybrid approach where:
|
92
|
+
|
93
|
+
* With Opal and Rails, we use the existing mechanism, sprockets, to
|
94
|
+
serve up all the things it does in the "normal" Rails ecosystem. I.e
|
95
|
+
we want to work with the system. We want all the perks of Ruby and
|
96
|
+
Rails without have to hand cobble it ourselves
|
97
|
+
* We use webpack or similar for being a "1st class JS citizen". This
|
98
|
+
gives us access to all the frontend assets in npm, we want all those
|
99
|
+
options and perks.
|
100
|
+
|
101
|
+
While I do favor moving as much Javascript to webpack, following suit
|
102
|
+
to React.js's lead, I see an "all webpack solution" for Opal apps
|
103
|
+
being only one of a few permutations, and not particularly appealing
|
104
|
+
to most Rails programmers - who I think is the largest demographic
|
105
|
+
likely to want to do Opal programming.
|
106
|
+
|
107
|
+
While we wait for the other approaches to evolve and get implemented
|
108
|
+
this solution is here and works now. It works with an "All Ruby"
|
109
|
+
system, it works with a Rails app that is using webpack to provide
|
110
|
+
react.js components to react.rb.
|
111
|
+
|
112
|
+
### Goals
|
113
|
+
* Bring the benefits of "leading edge web development" to All Ruby
|
114
|
+
(via Opal) full stack development. One of my efforts to be more
|
115
|
+
like Einstein in “Opening up yet another fragment of the frontier of
|
116
|
+
beauty” - i.e. share the joy.
|
117
|
+
* Batteries included out of the box - make it (increasingingly) easy
|
118
|
+
for Rubyists to enjoy the previous goal. This is a manifestation of
|
119
|
+
the "Ruby Way" of making the programmer happy
|
120
|
+
* Try to add the least amount of additional dependencies to projects
|
121
|
+
it's used in
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
## Screencasts
|
126
|
+
|
127
|
+
* Quickie intro to opal-hot-reloader https://youtu.be/NQbzL7fNOks
|
128
|
+
|
129
|
+
## Development
|
130
|
+
|
131
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
132
|
+
|
133
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
|
137
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fkchang/opal_hot_reloader.
|
138
|
+
|
139
|
+
|
140
|
+
## License
|
141
|
+
|
142
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
143
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'opal'
|
3
|
+
require 'opal-rspec'
|
4
|
+
require 'opal_hot_reloader' # load this server side to setup opal paths
|
5
|
+
require 'opal/sprockets/environment'
|
6
|
+
require 'opal/rspec/rake_task'
|
7
|
+
|
8
|
+
require "rspec/core/rake_task"
|
9
|
+
|
10
|
+
|
11
|
+
Opal.append_path File.expand_path('../spec-opal', __FILE__)
|
12
|
+
Opal::RSpec::RakeTask.new("opal:spec") do |server, task|
|
13
|
+
task.files = FileList['spec-opal/**/*_spec.rb']
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new(:spec)
|
17
|
+
|
18
|
+
task :default => :spec
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "opal_hot_reloader"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require "opal_hot_reloader/server"
|
5
|
+
|
6
|
+
options = {:port => 25222, :directories => ['app']}
|
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 = OpalHotReloader::Server.new(options)
|
21
|
+
puts "Listening on port #{options[:port]}, watching for changes in #{options[:directories].join(', ')}"
|
22
|
+
server.loop
|
data/bin/setup
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'websocket'
|
2
|
+
require 'socket'
|
3
|
+
require 'fiber'
|
4
|
+
require 'listen'
|
5
|
+
require 'optparse'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module OpalHotReloader
|
9
|
+
# Most of this lifted from https://github.com/saward/Rubame
|
10
|
+
class Server
|
11
|
+
|
12
|
+
attr_reader :directories
|
13
|
+
def initialize(options)
|
14
|
+
Socket.do_not_reverse_lookup
|
15
|
+
@hostname = '0.0.0.0'
|
16
|
+
@port = options[:port]
|
17
|
+
setup_directories(options)
|
18
|
+
|
19
|
+
@reading = []
|
20
|
+
@writing = []
|
21
|
+
|
22
|
+
@clients = {} # Socket as key, and Client as value
|
23
|
+
|
24
|
+
@socket = TCPServer.new(@hostname, @port)
|
25
|
+
@reading.push @socket
|
26
|
+
end
|
27
|
+
|
28
|
+
# adds known directories automatically if they exist
|
29
|
+
# - rails js app/assets/javascripts
|
30
|
+
# - reactrb rails defaults app/views/components
|
31
|
+
# - you tell me and I'll add them
|
32
|
+
def setup_directories(options)
|
33
|
+
@directories = options[:directories] || []
|
34
|
+
[
|
35
|
+
'app/assets/javascripts',
|
36
|
+
'app/views/components'
|
37
|
+
].each { |known_dir|
|
38
|
+
if !@directories.include?(known_dir) && File.exists?(known_dir)
|
39
|
+
@directories << known_dir
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def accept
|
45
|
+
socket = @socket.accept_nonblock
|
46
|
+
@reading.push socket
|
47
|
+
handshake = WebSocket::Handshake::Server.new
|
48
|
+
client = Client.new(socket, handshake, self)
|
49
|
+
|
50
|
+
while line = socket.gets
|
51
|
+
client.handshake << line
|
52
|
+
break if client.handshake.finished?
|
53
|
+
end
|
54
|
+
if client.handshake.valid?
|
55
|
+
@clients[socket] = client
|
56
|
+
client.write handshake.to_s
|
57
|
+
client.opened = true
|
58
|
+
return client
|
59
|
+
else
|
60
|
+
close(client)
|
61
|
+
end
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def send_updated_file(modified_file)
|
67
|
+
if modified_file =~ /\.rb$/
|
68
|
+
file_contents = File.read(modified_file)
|
69
|
+
update = {
|
70
|
+
type: 'ruby',
|
71
|
+
filename: modified_file,
|
72
|
+
source_code: file_contents
|
73
|
+
}.to_json
|
74
|
+
end
|
75
|
+
if modified_file =~ /\.s?[ac]ss$/
|
76
|
+
# TODO: Switch from hard-wired path assumptions to using SASS/sprockets config
|
77
|
+
relative_path = Pathname.new(modified_file).relative_path_from(Pathname.new(Dir.pwd))
|
78
|
+
url = relative_path.to_s
|
79
|
+
.sub('public/','')
|
80
|
+
.sub('/sass/','/')
|
81
|
+
.sub(/\.s[ac]ss/, '.css')
|
82
|
+
update = {
|
83
|
+
type: 'css',
|
84
|
+
filename: modified_file,
|
85
|
+
url: url
|
86
|
+
}.to_json
|
87
|
+
end
|
88
|
+
if update
|
89
|
+
@clients.each { |socket, client| client.send(update) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
PROGRAM = 'opal-hot-reloader'
|
94
|
+
def loop
|
95
|
+
listener = Listen.to(*@directories, only: %r{\.(rb|s?[ac]ss)$}) do |modified, added, removed|
|
96
|
+
modified.each { |modified_file| send_updated_file(modified_file) }
|
97
|
+
puts "modified absolute path: #{modified}"
|
98
|
+
puts "added absolute path: #{added}"
|
99
|
+
puts "removed absolute path: #{removed}"
|
100
|
+
end
|
101
|
+
listener.start
|
102
|
+
|
103
|
+
puts "#{PROGRAM}: starting..."
|
104
|
+
while (!$quit)
|
105
|
+
run do |client|
|
106
|
+
client.onopen do
|
107
|
+
puts "#{PROGRAM}: client open"
|
108
|
+
end
|
109
|
+
client.onmessage do |mess|
|
110
|
+
puts "PROGRAM: message received: #{mess}"
|
111
|
+
end
|
112
|
+
client.onclose do
|
113
|
+
puts "#{PROGRAM}: client closed"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
sleep 0.2
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def read(client)
|
121
|
+
|
122
|
+
pairs = client.socket.recvfrom(2000)
|
123
|
+
messages = []
|
124
|
+
|
125
|
+
if pairs[0].length == 0
|
126
|
+
close(client)
|
127
|
+
else
|
128
|
+
client.frame << pairs[0]
|
129
|
+
|
130
|
+
while f = client.frame.next
|
131
|
+
if (f.type == :close)
|
132
|
+
close(client)
|
133
|
+
return messages
|
134
|
+
else
|
135
|
+
messages.push f
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
return messages
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
def close(client)
|
146
|
+
@reading.delete client.socket
|
147
|
+
@clients.delete client.socket
|
148
|
+
begin
|
149
|
+
client.socket.close
|
150
|
+
rescue
|
151
|
+
end
|
152
|
+
client.closed = true
|
153
|
+
end
|
154
|
+
|
155
|
+
def run(time = 0, &blk)
|
156
|
+
readable, writable = IO.select(@reading, @writing, nil, 0)
|
157
|
+
|
158
|
+
if readable
|
159
|
+
readable.each do |socket|
|
160
|
+
client = @clients[socket]
|
161
|
+
if socket == @socket
|
162
|
+
client = accept
|
163
|
+
else
|
164
|
+
msg = read(client)
|
165
|
+
client.messaged = msg
|
166
|
+
end
|
167
|
+
|
168
|
+
blk.call(client) if client and blk
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Check for lazy send items
|
173
|
+
timer_start = Time.now
|
174
|
+
time_passed = 0
|
175
|
+
begin
|
176
|
+
@clients.each do |s, c|
|
177
|
+
c.send_some_lazy(5)
|
178
|
+
end
|
179
|
+
time_passed = Time.now - timer_start
|
180
|
+
end while time_passed < time
|
181
|
+
end
|
182
|
+
|
183
|
+
def stop
|
184
|
+
@socket.close
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Client
|
189
|
+
attr_accessor :socket, :handshake, :frame, :opened, :messaged, :closed
|
190
|
+
|
191
|
+
def initialize(socket, handshake, server)
|
192
|
+
@socket = socket
|
193
|
+
@handshake = handshake
|
194
|
+
@frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
|
195
|
+
@opened = false
|
196
|
+
@messaged = []
|
197
|
+
@lazy_queue = []
|
198
|
+
@lazy_current_queue = nil
|
199
|
+
@closed = false
|
200
|
+
@server = server
|
201
|
+
end
|
202
|
+
|
203
|
+
def write(data)
|
204
|
+
@socket.write data
|
205
|
+
end
|
206
|
+
|
207
|
+
def send(data)
|
208
|
+
frame = WebSocket::Frame::Outgoing::Server.new(:version => @handshake.version, :data => data, :type => :text)
|
209
|
+
begin
|
210
|
+
@socket.write frame
|
211
|
+
@socket.flush
|
212
|
+
rescue
|
213
|
+
@server.close(self) unless @closed
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def lazy_send(data)
|
218
|
+
@lazy_queue.push data
|
219
|
+
end
|
220
|
+
|
221
|
+
def get_lazy_fiber
|
222
|
+
# Create the fiber if needed
|
223
|
+
if @lazy_fiber == nil or !@lazy_fiber.alive?
|
224
|
+
@lazy_fiber = Fiber.new do
|
225
|
+
@lazy_current_queue.each do |data|
|
226
|
+
send(data)
|
227
|
+
Fiber.yield unless @lazy_current_queue[-1] == data
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
return @lazy_fiber
|
233
|
+
end
|
234
|
+
|
235
|
+
def send_some_lazy(count)
|
236
|
+
# To save on cpu cycles, we don't want to be chopping and changing arrays, which could get quite large. Instead,
|
237
|
+
# we iterate over an array which we are sure won't change out from underneath us.
|
238
|
+
unless @lazy_current_queue
|
239
|
+
@lazy_current_queue = @lazy_queue
|
240
|
+
@lazy_queue = []
|
241
|
+
end
|
242
|
+
|
243
|
+
completed = 0
|
244
|
+
begin
|
245
|
+
get_lazy_fiber.resume
|
246
|
+
completed += 1
|
247
|
+
end while (@lazy_queue.count > 0 or @lazy_current_queue.count > 0) and completed < count
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
def onopen(&blk)
|
252
|
+
if @opened
|
253
|
+
begin
|
254
|
+
blk.call
|
255
|
+
ensure
|
256
|
+
@opened = false
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def onmessage(&blk)
|
262
|
+
if @messaged.size > 0
|
263
|
+
begin
|
264
|
+
@messaged.each do |x|
|
265
|
+
blk.call(x.to_s)
|
266
|
+
end
|
267
|
+
ensure
|
268
|
+
@messaged = []
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def onclose(&blk)
|
274
|
+
if @closed
|
275
|
+
begin
|
276
|
+
blk.call
|
277
|
+
ensure
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
line = 0
|
284
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'opal_hot_reloader/reactrb_patches'
|
2
|
+
require 'opal_hot_reloader/css_reloader'
|
3
|
+
require 'opal-parser' # gives me 'eval', for hot-loading code
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
# Opal client to support hot reloading
|
8
|
+
$eval_proc = proc { |s| eval s }
|
9
|
+
class OpalHotReloader
|
10
|
+
|
11
|
+
def connect_to_websocket(port)
|
12
|
+
host = `window.location.host`.sub(/:\d+/, '')
|
13
|
+
host = '127.0.0.1' if host == ''
|
14
|
+
ws_url = "#{host}:#{port}"
|
15
|
+
puts "Hot-Reloader connecting to #{ws_url}"
|
16
|
+
%x{
|
17
|
+
ws = new WebSocket('ws://' + #{ws_url});
|
18
|
+
// console.log(ws);
|
19
|
+
ws.onmessage = #{lambda { |e| reload(e) }}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def reload(e)
|
24
|
+
reload_request = JSON.parse(`e.data`)
|
25
|
+
if reload_request[:type] == "ruby"
|
26
|
+
puts "Reloading ruby #{reload_request[:filename]}"
|
27
|
+
$eval_proc.call reload_request[:source_code]
|
28
|
+
if @reload_post_callback
|
29
|
+
@reload_post_callback.call
|
30
|
+
else
|
31
|
+
puts "not reloading code"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
if reload_request[:type] == "css"
|
35
|
+
@css_reloader.reload(reload_request, `document`)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param port [Integer] opal hot reloader port to connect to
|
40
|
+
# @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!
|
41
|
+
def initialize(port=25222, &reload_post_callback)
|
42
|
+
@port = port
|
43
|
+
@reload_post_callback = reload_post_callback
|
44
|
+
@css_reloader = CssReloader.new
|
45
|
+
end
|
46
|
+
# Opens a websocket connection that evaluates new files and runs the optional @reload_post_callback
|
47
|
+
def listen
|
48
|
+
connect_to_websocket(@port)
|
49
|
+
end
|
50
|
+
|
51
|
+
# convenience method to start a listen w/one line
|
52
|
+
# @param port [Integer] opal hot reloader port to connect to. Defaults to 25222 to match opal-hot-loader default
|
53
|
+
# @deprecated reactrb - this flag no longer necessary and will be removed in gem release 0.2
|
54
|
+
def self.listen(port=25222, reactrb=false)
|
55
|
+
return if @server
|
56
|
+
if reactrb
|
57
|
+
warn "OpalHotReloader.listen(#{port}): reactrb flag is deprectated and will be removed in gem release 0.2. React will automatically be detected"
|
58
|
+
end
|
59
|
+
create_framework_aware_server(port)
|
60
|
+
end
|
61
|
+
# Automatically add in framework specific hooks
|
62
|
+
|
63
|
+
def self.create_framework_aware_server(port)
|
64
|
+
if defined? ::React
|
65
|
+
ReactrbPatches.patch!
|
66
|
+
@server = OpalHotReloader.new(port) { React::Component.force_update! }
|
67
|
+
else
|
68
|
+
puts "No framework detected"
|
69
|
+
@server = OpalHotReloader.new(port)
|
70
|
+
end
|
71
|
+
@server.listen
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'native'
|
2
|
+
class OpalHotReloader
|
3
|
+
class CssReloader
|
4
|
+
|
5
|
+
def reload(reload_request, document)
|
6
|
+
url = reload_request[:url]
|
7
|
+
puts "Reloading CSS: #{url}"
|
8
|
+
to_append = "t_hot_reload=#{Time.now.to_i}"
|
9
|
+
links = Native(`document.getElementsByTagName("link")`)
|
10
|
+
(0..links.length-1).each { |i|
|
11
|
+
link = links[i]
|
12
|
+
if link.rel == 'stylesheet' && is_matching_stylesheet?(link.href, url)
|
13
|
+
if link.href !~ /\?/
|
14
|
+
link.href += "?#{to_append}"
|
15
|
+
else
|
16
|
+
if link.href !~ /t_hot_reload/
|
17
|
+
link.href += "&#{to_append}"
|
18
|
+
else
|
19
|
+
link.href = link.href.sub(/t_hot_reload=\d+/, to_append)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_matching_stylesheet?(href, url)
|
27
|
+
# straight match, like in Rack::Sass::Plugin
|
28
|
+
if href.index(url)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
# Rails asset pipeline match
|
32
|
+
url_base = File.basename(url).sub(/\.s?css+/, '').sub(/\.s?css+/, '')
|
33
|
+
href_base = File.basename(href).sub(/\.self-.*.css.+/, '')
|
34
|
+
url_base == href_base
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# patches to support reloading react.rb
|
2
|
+
class ReactrbPatches
|
3
|
+
# React.rb needs to be patched so the we don't keep adding callbacks
|
4
|
+
def self.patch!
|
5
|
+
module ::React
|
6
|
+
module Callbacks
|
7
|
+
module ClassMethods
|
8
|
+
def define_callback(callback_name)
|
9
|
+
attribute_name = "_#{callback_name}_callbacks"
|
10
|
+
class_attribute(attribute_name)
|
11
|
+
self.send("#{attribute_name}=", [])
|
12
|
+
define_singleton_method(callback_name) do |*args, &block|
|
13
|
+
# puts "calling new and improved callbacks"
|
14
|
+
callbacks = []
|
15
|
+
callbacks.concat(args)
|
16
|
+
callbacks.push(block) if block_given?
|
17
|
+
self.send("#{attribute_name}=", callbacks)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ::React
|
25
|
+
module Callbacks
|
26
|
+
|
27
|
+
alias_method :original_run_callback, :run_callback
|
28
|
+
|
29
|
+
def run_callback(name, *args)
|
30
|
+
# monkey patch run callback because its easiest place to hook
|
31
|
+
# into all components lifecycles.
|
32
|
+
React::Component.add_to_global_component_list self if name == :before_mount
|
33
|
+
original_run_callback name, *args
|
34
|
+
React::Component.remove_from_global_component_list self if name == :before_unmount
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
module Component
|
40
|
+
|
41
|
+
def self.add_to_global_component_list instance
|
42
|
+
# puts "Adding #{instance} to component list"
|
43
|
+
(@global_component_list ||= Set.new).add instance
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.remove_from_global_component_list instance
|
47
|
+
# puts "Removing #{instance} from component list"
|
48
|
+
@global_component_list.delete instance
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.force_update!
|
52
|
+
# puts "Forcing global update"
|
53
|
+
@global_component_list && @global_component_list.each(&:force_update!)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'native'
|
2
|
+
require 'io/writable'
|
3
|
+
|
4
|
+
class OpalHotReloader
|
5
|
+
|
6
|
+
# Code taken from opal browser, did not want to force an opal-browser dependency
|
7
|
+
#
|
8
|
+
# A {Socket} allows the browser and a server to have a bidirectional data
|
9
|
+
# connection.
|
10
|
+
#
|
11
|
+
# @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
12
|
+
class Socket
|
13
|
+
def self.supported?
|
14
|
+
Browser.supports? :WebSocket
|
15
|
+
end
|
16
|
+
|
17
|
+
include Native
|
18
|
+
include IO::Writable
|
19
|
+
|
20
|
+
def on(str, &block)
|
21
|
+
puts "putting #{str}"
|
22
|
+
b = block
|
23
|
+
cmd = "foo.on#{str} = #{b}"
|
24
|
+
puts cmd
|
25
|
+
`#{cmd}`
|
26
|
+
# `foo.on#{str} = #{b}`
|
27
|
+
end
|
28
|
+
# include DOM::Event::Target
|
29
|
+
|
30
|
+
# target {|value|
|
31
|
+
#Socket.new(value) if Native.is_a?(value, `window.WebSocket`)
|
32
|
+
# }
|
33
|
+
|
34
|
+
# Create a connection to the given URL, optionally using the given protocol.
|
35
|
+
#
|
36
|
+
# @param url [String] the URL to connect to
|
37
|
+
# @param protocol [String] the protocol to use
|
38
|
+
#
|
39
|
+
# @yield if the block has no parameters it's `instance_exec`d, otherwise it's
|
40
|
+
# called with `self`
|
41
|
+
def initialize(url, protocol = nil, &block)
|
42
|
+
if native?(url)
|
43
|
+
super(url)
|
44
|
+
elsif protocol
|
45
|
+
super(`new window.WebSocket(#{url.to_s}, #{protocol.to_n})`)
|
46
|
+
else
|
47
|
+
super(`new window.WebSocket(#{url.to_s})`)
|
48
|
+
end
|
49
|
+
|
50
|
+
if block.arity == 0
|
51
|
+
instance_exec(&block)
|
52
|
+
else
|
53
|
+
block.call(self)
|
54
|
+
end if block
|
55
|
+
end
|
56
|
+
|
57
|
+
# @!attribute [r] protocol
|
58
|
+
# @return [String] the protocol of the socket
|
59
|
+
alias_native :protocol
|
60
|
+
|
61
|
+
# @!attribute [r] url
|
62
|
+
# @return [String] the URL the socket is connected to
|
63
|
+
alias_native :url
|
64
|
+
|
65
|
+
# @!attribute [r] buffered
|
66
|
+
# @return [Integer] the amount of buffered data.
|
67
|
+
alias_native :buffered, :bufferedAmount
|
68
|
+
|
69
|
+
# @!attribute [r] type
|
70
|
+
# @return [:blob, :buffer, :string] the type of the socket
|
71
|
+
def type
|
72
|
+
%x{
|
73
|
+
switch (#@native.binaryType) {
|
74
|
+
case "blob":
|
75
|
+
return "blob";
|
76
|
+
|
77
|
+
case "arraybuffer":
|
78
|
+
return "buffer";
|
79
|
+
|
80
|
+
default:
|
81
|
+
return "string";
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!attribute [r] state
|
87
|
+
# @return [:connecting, :open, :closing, :closed] the state of the socket
|
88
|
+
def state
|
89
|
+
%x{
|
90
|
+
switch (#@native.readyState) {
|
91
|
+
case window.WebSocket.CONNECTING:
|
92
|
+
return "connecting";
|
93
|
+
|
94
|
+
case window.WebSocket.OPEN:
|
95
|
+
return "open";
|
96
|
+
|
97
|
+
case window.WebSocket.CLOSING:
|
98
|
+
return "closing";
|
99
|
+
|
100
|
+
case window.WebSocket.CLOSED:
|
101
|
+
return "closed";
|
102
|
+
}
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
# @!attribute [r] extensions
|
107
|
+
# @return [Array<String>] the extensions used by the socket
|
108
|
+
def extensions
|
109
|
+
`#@native.extensions`.split(/\s*,\s*/)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check if the socket is alive.
|
113
|
+
def alive?
|
114
|
+
state == :open
|
115
|
+
end
|
116
|
+
|
117
|
+
# Send data to the socket.
|
118
|
+
#
|
119
|
+
# @param data [#to_n] the data to send
|
120
|
+
def write(data)
|
121
|
+
`#@native.send(#{data.to_n})`
|
122
|
+
end
|
123
|
+
|
124
|
+
# Close the socket.
|
125
|
+
#
|
126
|
+
# @param code [Integer, nil] the error code
|
127
|
+
# @param reason [String, nil] the reason for closing
|
128
|
+
def close(code = nil, reason = nil)
|
129
|
+
`#@native.close(#{code.to_n}, #{reason.to_n})`
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'opal_hot_reloader/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "opal_hot_reloader"
|
8
|
+
spec.version = OpalHotReloader::VERSION
|
9
|
+
spec.authors = ["Forrest Chang"]
|
10
|
+
spec.email = ["fchang@hedgeye.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Opal Hot reloader}
|
13
|
+
spec.description = %q{Opal Hot Reloader with reactrb suppot}
|
14
|
+
spec.homepage = "https://github.com/fkchang/opal_hot_reloader"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "opal-rspec", "~> 0.5.0"
|
26
|
+
|
27
|
+
|
28
|
+
spec.add_dependency 'listen', '~> 3.0'
|
29
|
+
spec.add_dependency 'websocket'
|
30
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'native'
|
2
|
+
require 'opal_hot_reloader'
|
3
|
+
require 'opal_hot_reloader/css_reloader'
|
4
|
+
describe OpalHotReloader::CssReloader do
|
5
|
+
# Creates a DOM stylesheet link
|
6
|
+
# @param href [String] the link url
|
7
|
+
def create_link( href)
|
8
|
+
%x|
|
9
|
+
var ss = document.createElement("link");
|
10
|
+
ss.type = "text/css";
|
11
|
+
ss.rel = "stylesheet";
|
12
|
+
ss.href = #{href};
|
13
|
+
return ss;
|
14
|
+
|
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a document test double and the link to check whether it has been altered right
|
18
|
+
# @param href [String] the link url
|
19
|
+
def fake_links_document(href)
|
20
|
+
link = create_link(href)
|
21
|
+
doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}`
|
22
|
+
{ link: link, document: doc}
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
context 'Rack::Sass::Plugin' do
|
27
|
+
it 'should append t_hot_reload to a css path' do
|
28
|
+
css_path = 'stylesheets/base.css'
|
29
|
+
doubles = fake_links_document(css_path)
|
30
|
+
link = Native(doubles[:link])
|
31
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
|
32
|
+
subject.reload({ url: css_path}, doubles[:document])
|
33
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}\?t_hot_reload=\d+/
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should update t_hot_reload argument if there is one already' do
|
37
|
+
css_path = 'stylesheets/base.css?t_hot_reload=1111111111111'
|
38
|
+
doubles = fake_links_document(css_path)
|
39
|
+
link = Native(doubles[:link])
|
40
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
|
41
|
+
subject.reload({ url: css_path}, doubles[:document])
|
42
|
+
expect(link[:href]).to match /#{Regexp.escape('stylesheets/base.css?t_hot_reload=')}(\d)+/
|
43
|
+
expect($1).to_not eq '1111111111111'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should append t_hot_reload if there are existing arguments' do
|
47
|
+
css_path = 'stylesheets/base.css?some-arg=1'
|
48
|
+
doubles = fake_links_document(css_path)
|
49
|
+
link = Native(doubles[:link])
|
50
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
|
51
|
+
subject.reload({ url: css_path}, doubles[:document])
|
52
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}\&t_hot_reload=(\d)+/
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "Rails asset pipeline" do
|
57
|
+
it 'should append t_hot_reload to a css path' do
|
58
|
+
css_path = "http://localhost:8080/assets/company.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css?body=1"
|
59
|
+
doubles = fake_links_document(css_path)
|
60
|
+
link = Native(doubles[:link])
|
61
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
|
62
|
+
raw_scss_path = "app/assets/stylesheets/company.css.css"
|
63
|
+
subject.reload({ url: raw_scss_path}, doubles[:document])
|
64
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}\&t_hot_reload=\d+/
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should update t_hot_reload arguments' do
|
68
|
+
css_path ="http://localhost:8080/assets/company.self-055b3f2f4bbc772b1161698989ee095020c65e0283f4e732c66153e06b266ca8.css?body=1&t_hot_reload=1464733023"
|
69
|
+
doubles = fake_links_document(css_path)
|
70
|
+
link = Native(doubles[:link])
|
71
|
+
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
|
72
|
+
raw_scss_path = "app/assets/stylesheets/company.css.css"
|
73
|
+
subject.reload({ url: raw_scss_path}, doubles[:document])
|
74
|
+
if link[:href] =~ /(.+)\&t_hot_reload=(\d+)/
|
75
|
+
new_timestamp = $2
|
76
|
+
expect(new_timestamp).to_not eq("1464733023")
|
77
|
+
else
|
78
|
+
fail("new link_path is broken")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opal_hot_reloader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Forrest Chang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: opal-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.5.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.5.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: listen
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: websocket
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Opal Hot Reloader with reactrb suppot
|
98
|
+
email:
|
99
|
+
- fchang@hedgeye.com
|
100
|
+
executables:
|
101
|
+
- console
|
102
|
+
- opal-hot-reloader
|
103
|
+
- setup
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- ".gitignore"
|
108
|
+
- ".rspec"
|
109
|
+
- ".travis.yml"
|
110
|
+
- Gemfile
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- bin/console
|
115
|
+
- bin/opal-hot-reloader
|
116
|
+
- bin/setup
|
117
|
+
- lib/opal_hot_reloader.rb
|
118
|
+
- lib/opal_hot_reloader/server.rb
|
119
|
+
- lib/opal_hot_reloader/version.rb
|
120
|
+
- opal/opal_hot_reloader.rb
|
121
|
+
- opal/opal_hot_reloader/css_reloader.rb
|
122
|
+
- opal/opal_hot_reloader/foo.rb
|
123
|
+
- opal/opal_hot_reloader/reactrb_patches.rb
|
124
|
+
- opal/opal_hot_reloader/socket.rb
|
125
|
+
- opal_hot_reloader.gemspec
|
126
|
+
- spec-opal/opal_hot_reloader/css_reloader_spec.rb
|
127
|
+
homepage: https://github.com/fkchang/opal_hot_reloader
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 2.5.1
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Opal Hot reloader
|
151
|
+
test_files: []
|