opal_hot_reloader 0.1.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 +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: []
|