rails_live_reload 0.0.2 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +47 -22
- data/lib/rails_live_reload/checker.rb +3 -9
- data/lib/rails_live_reload/client.rb +48 -9
- data/lib/rails_live_reload/command.rb +12 -6
- data/lib/rails_live_reload/config.rb +51 -0
- data/lib/rails_live_reload/engine.rb +22 -16
- data/lib/rails_live_reload/instrument/metrics_collector.rb +0 -20
- data/lib/rails_live_reload/rails/middleware/base.rb +63 -0
- data/lib/rails_live_reload/rails/middleware/long_polling.rb +51 -0
- data/lib/rails_live_reload/rails/middleware/polling.rb +21 -0
- data/lib/rails_live_reload/version.rb +1 -1
- data/lib/rails_live_reload/watcher.rb +9 -6
- data/lib/rails_live_reload.rb +10 -26
- metadata +7 -4
- data/lib/rails_live_reload/rails/middleware.rb +0 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04c014926a43e8cba8e586fa5aca93a4e002357fffee35d2a553db0ec88f04a0
|
4
|
+
data.tar.gz: 7c5b64509ab169603cd32b62adc9390eb2f1d939d2e626e9bca960f325d45876
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e365d7ffad4d351c35c84f8839c4b55318c9769c2191c8aab53cd441253d26950cfabeb98f76c8b556e7bb80d9b329f0b6e5d81ce1c4b0841239d106b38a406
|
7
|
+
data.tar.gz: ec660e96b97075b0c7663e5d0b3be0e59d3a505805feda8c0e7262c2e9c7889f560f0a308edaecf1dc03aaeb7e3a987e709718196ae7b42d47b98df03074449f
|
data/README.md
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
# Rails Live Reload
|
2
2
|
|
3
|
-
|
3
|
+
[![RailsJazz](https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/my_other.svg?raw=true)](https://www.railsjazz.com)
|
4
4
|
|
5
|
-
|
5
|
+
![RailsLiveReload](docs/rails_live_reload.gif)
|
6
|
+
|
7
|
+
This is the simplest and probably the most robust way to add live reloading to your Rails app.
|
8
|
+
|
9
|
+
Just add the gem and thats it, now you have a live reloading. No 3rd party dependencies.
|
6
10
|
|
7
11
|
Works with:
|
8
12
|
|
9
|
-
- views (EBR/HAML/SLIM) (
|
13
|
+
- views (EBR/HAML/SLIM) (the page is reloaded only when changed views which were rendered on the page)
|
10
14
|
- partials
|
11
15
|
- CSS/JS
|
12
|
-
- helpers
|
13
|
-
-
|
16
|
+
- helpers (if configured)
|
17
|
+
- YAML locales (if configured)
|
18
|
+
- on the "crash" page, so it will be reloaded as soon as you make a fix
|
14
19
|
|
15
|
-
|
20
|
+
The page is reloaded fully with `window.location.reload()` to make sure that every chage will be displayed.
|
16
21
|
|
17
22
|
## Usage
|
18
23
|
|
@@ -35,31 +40,49 @@ $ bundle
|
|
35
40
|
|
36
41
|
## Configuration
|
37
42
|
|
38
|
-
|
43
|
+
### There are two modes:
|
44
|
+
1. `:long_polling` - This is a default mode, it uses [long polling](https://javascript.info/long-polling) techunique, client opens a connection that will hang until either change is detected, or timeout happens, if later, a new connection is oppened
|
45
|
+
2. `:polling` - This mode will use regular polling to detect changes, you can configure custom `polling_interval` (default is 100ms). We recommend using `:long_polling` as it makes much less requests to the server.
|
46
|
+
|
47
|
+
### Create initializer `config/initializers/rails_live_reload.rb`:
|
39
48
|
|
40
49
|
|
41
50
|
```ruby
|
42
|
-
RailsLiveReload.
|
43
|
-
config.url = "/rails/live/reload"
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
config.
|
49
|
-
|
51
|
+
RailsLiveReload.configure do |config|
|
52
|
+
# config.url = "/rails/live/reload"
|
53
|
+
# Available modes are: :long_polling (default) and :polling
|
54
|
+
# config.mode = :long_polling
|
55
|
+
|
56
|
+
# This is used with :long_polling mode
|
57
|
+
# config.timeout = 30
|
58
|
+
# config.long_polling_sleep_duration = 0.1
|
59
|
+
|
60
|
+
# This is used with :polling mode
|
61
|
+
# config.polling_interval = 100
|
62
|
+
|
63
|
+
# Default watched folders & files
|
64
|
+
# config.watch %r{app/views/.+\.(erb|haml|slim)$}
|
65
|
+
# config.watch %r{(app|vendor)/(assets|javascript)/\w+/(.+\.(css|js|html|png|jpg|ts|jsx)).*}, reload: :always
|
66
|
+
|
50
67
|
# More examples:
|
51
68
|
# config.watch %r{app/helpers/.+\.rb}, reload: :always
|
52
69
|
# config.watch %r{config/locales/.+\.yml}, reload: :always
|
53
|
-
|
70
|
+
|
71
|
+
# config.enabled = Rails.env.development?
|
72
|
+
end if defined?(RailsLiveReload)
|
54
73
|
```
|
55
74
|
|
56
75
|
## How it works
|
57
76
|
|
58
|
-
There are 3 main
|
77
|
+
There are 3 main parts:
|
59
78
|
|
60
|
-
1) listener
|
79
|
+
1) listener of file changes (using `listen` gem)
|
61
80
|
2) collector of rendered views (see rails instrumentation)
|
62
|
-
3) middleware which is responding to
|
81
|
+
3) middleware which is responding to polling JS calls
|
82
|
+
|
83
|
+
## Notes
|
84
|
+
|
85
|
+
The default configuration assumes that you either use asset pipeline, or that your assets compile quickly (on most applications asset compilation takes around 50-200ms), so it watches for changes in `app/assets` and `app/javascript` folders, this will not be a problem for 99% of users, but in case your asset compilation takes couple of seconds, this might not work propperly, in that case we would recommend you to add configuration to watch output folder.
|
63
86
|
|
64
87
|
## Contributing
|
65
88
|
|
@@ -68,17 +91,19 @@ You are welcome to contribute. See list of `TODO's` below.
|
|
68
91
|
## TODO
|
69
92
|
|
70
93
|
- reload CSS without reloading the whole page?
|
94
|
+
- add `:websocket` mode?
|
71
95
|
- smarter reload if there is a change in helper (check methods from rendered views?)
|
72
96
|
- generator for initializer
|
73
|
-
-
|
74
|
-
- maybe complex rules, e.g. if "user.rb" file is changed - automatically reload all "users" views
|
97
|
+
- more complex rules? e.g. if "user.rb" file is changed - reload all pages with rendered "users" views
|
75
98
|
- check with older Rails versions
|
76
99
|
- tests or specs
|
77
100
|
- CI (github actions)
|
101
|
+
- improve how JS code is injected into HTML
|
102
|
+
- improve work with turbo, turbolinks
|
78
103
|
|
79
104
|
## License
|
80
105
|
|
81
106
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
82
107
|
|
83
108
|
[<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
|
84
|
-
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=rails_live_reload)
|
109
|
+
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=rails_live_reload)
|
@@ -5,22 +5,16 @@ module RailsLiveReload
|
|
5
5
|
temp = []
|
6
6
|
|
7
7
|
# all changed files
|
8
|
-
RailsLiveReload.files.each do |file, fdt|
|
8
|
+
RailsLiveReload.watcher.files.each do |file, fdt|
|
9
9
|
temp << file if fdt && fdt > dt
|
10
10
|
end
|
11
11
|
|
12
|
-
# ::Rails.logger.info "Changed: #{temp}"
|
13
|
-
|
14
12
|
result = []
|
15
13
|
|
16
14
|
temp.each do |file|
|
17
|
-
# ::Rails.logger.info "Checking: #{file}"
|
18
15
|
RailsLiveReload.patterns.each do |pattern, rule|
|
19
|
-
|
20
|
-
|
21
|
-
# 2. checking if rendered file
|
22
|
-
rule_1 = file.match(pattern) && rule == :always
|
23
|
-
rule_2 = file.match(pattern) && rendered_files.include?(file)
|
16
|
+
rule_1 = file.match(pattern) && rule == :always # Used for CSS, JS, yaml, helpers, etc.
|
17
|
+
rule_2 = file.match(pattern) && rendered_files.include?(file) # Used to check if view was rendered
|
24
18
|
|
25
19
|
if rule_1 || rule_2
|
26
20
|
result << file
|
@@ -1,8 +1,49 @@
|
|
1
1
|
module RailsLiveReload
|
2
|
-
|
2
|
+
module Client
|
3
3
|
|
4
|
-
def Client.
|
5
|
-
|
4
|
+
def Client.long_polling_js
|
5
|
+
<<~HTML.html_safe
|
6
|
+
<script>
|
7
|
+
(function() {
|
8
|
+
const files = #{CurrentRequest.current.data.to_a.to_json};
|
9
|
+
let retries_count = 0;
|
10
|
+
function poll() {
|
11
|
+
const formData = new FormData();
|
12
|
+
formData.append('dt', #{Time.now.to_i})
|
13
|
+
formData.append('files', JSON.stringify(files))
|
14
|
+
|
15
|
+
fetch(
|
16
|
+
"#{RailsLiveReload.config.url}",
|
17
|
+
{
|
18
|
+
method: "post",
|
19
|
+
headers: { 'Accept': 'application/json', },
|
20
|
+
body: formData
|
21
|
+
}
|
22
|
+
)
|
23
|
+
.then(response => response.json())
|
24
|
+
.then(data => {
|
25
|
+
retries_count = 0;
|
26
|
+
if(data['command'] === 'RELOAD') {
|
27
|
+
window.location.reload();
|
28
|
+
} else {
|
29
|
+
poll();
|
30
|
+
}
|
31
|
+
}).catch(() => {
|
32
|
+
retries_count++;
|
33
|
+
|
34
|
+
if(retries_count < 10) {
|
35
|
+
setTimeout(poll, 5000)
|
36
|
+
}
|
37
|
+
})
|
38
|
+
}
|
39
|
+
poll();
|
40
|
+
})();
|
41
|
+
</script>
|
42
|
+
HTML
|
43
|
+
end
|
44
|
+
|
45
|
+
def Client.polling_js
|
46
|
+
<<~HTML.html_safe
|
6
47
|
<script>
|
7
48
|
const files = #{CurrentRequest.current.data.to_a.to_json};
|
8
49
|
const timer = setInterval(
|
@@ -10,9 +51,8 @@ module RailsLiveReload
|
|
10
51
|
const formData = new FormData();
|
11
52
|
formData.append('dt', #{Time.now.to_i})
|
12
53
|
formData.append('files', JSON.stringify(files))
|
13
|
-
|
14
54
|
fetch(
|
15
|
-
"#{RailsLiveReload.url}",
|
55
|
+
"#{RailsLiveReload.config.url}",
|
16
56
|
{
|
17
57
|
method: "post",
|
18
58
|
headers: { 'Accept': 'application/json', },
|
@@ -26,11 +66,10 @@ module RailsLiveReload
|
|
26
66
|
window.location.reload();
|
27
67
|
}
|
28
68
|
})
|
29
|
-
}, #{RailsLiveReload.
|
69
|
+
}, #{RailsLiveReload.config.polling_interval}
|
30
70
|
)
|
31
71
|
</script>
|
32
|
-
|
72
|
+
HTML
|
33
73
|
end
|
34
|
-
|
35
74
|
end
|
36
|
-
end
|
75
|
+
end
|
@@ -4,16 +4,22 @@ module RailsLiveReload
|
|
4
4
|
|
5
5
|
def initialize(params)
|
6
6
|
@dt = params["dt"].to_i
|
7
|
-
@files = JSON.parse(params["files"])
|
7
|
+
@files = JSON.parse(params["files"]) rescue []
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
10
|
+
def changes
|
11
|
+
RailsLiveReload::Checker.scan(dt, files)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def reload?
|
15
|
+
!changes.size.zero?
|
16
|
+
end
|
17
|
+
|
18
|
+
def payload
|
19
|
+
if reload?
|
16
20
|
{ command: "RELOAD" }
|
21
|
+
else
|
22
|
+
{ command: "NO_CHANGES" }
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
class << self
|
3
|
+
def configure
|
4
|
+
yield config
|
5
|
+
end
|
6
|
+
|
7
|
+
def config
|
8
|
+
@_config ||= Config.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def patterns
|
12
|
+
config.patterns
|
13
|
+
end
|
14
|
+
|
15
|
+
def enabled?
|
16
|
+
config.enabled
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Config
|
21
|
+
attr_reader :patterns
|
22
|
+
attr_accessor :mode, :polling_interval, :timeout, :url, :watcher, :files, :enabled, :long_polling_sleep_duration
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@mode = :long_polling
|
26
|
+
@timeout = 30
|
27
|
+
@long_polling_sleep_duration = 0.1
|
28
|
+
@polling_interval = 100
|
29
|
+
@url = "/rails/live/reload"
|
30
|
+
@watcher = nil
|
31
|
+
@files = {}
|
32
|
+
@enabled = ::Rails.env.development?
|
33
|
+
|
34
|
+
# These configs work for 95% apps, see README for more info
|
35
|
+
@patterns = {
|
36
|
+
%r{app/views/.+\.(erb|haml|slim)$} => :on_change,
|
37
|
+
%r{(app|vendor)/(assets|javascript)/\w+/(.+\.(css|js|html|png|jpg|ts|jsx)).*} => :always
|
38
|
+
}
|
39
|
+
@default_patterns_changed = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def watch(pattern, reload: :on_change)
|
43
|
+
unless @default_patterns_changed
|
44
|
+
@default_patterns_changed = true
|
45
|
+
@patterns = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
patterns[pattern] = reload
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,26 +1,32 @@
|
|
1
1
|
module RailsLiveReload
|
2
2
|
class Railtie < ::Rails::Engine
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
if RailsLiveReload.enabled? && defined?(::Rails::Server)
|
5
|
+
initializer "rails_live_reload.middleware" do |app|
|
6
|
+
if ::Rails::VERSION::MAJOR.to_i >= 5
|
7
|
+
app.middleware.insert_after ActionDispatch::Executor, RailsLiveReload.middleware
|
8
|
+
else
|
9
|
+
begin
|
10
|
+
app.middleware.insert_after ActionDispatch::Static, RailsLiveReload.middleware
|
11
|
+
rescue
|
12
|
+
app.middleware.insert_after Rack::SendFile, RailsLiveReload.middleware
|
13
|
+
end
|
12
14
|
end
|
15
|
+
|
16
|
+
RailsLiveReload::Watcher.init
|
13
17
|
end
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
initializer :configure_metrics, after: :initialize_logger do
|
20
|
+
ActiveSupport::Notifications.subscribe(
|
21
|
+
/\.action_view/,
|
22
|
+
RailsLiveReload::Instrument::MetricsCollector.new
|
23
|
+
)
|
24
|
+
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
)
|
26
|
+
initializer :reset_current_request, after: :initialize_logger do |app|
|
27
|
+
app.executor.to_run { CurrentRequest.cleanup }
|
28
|
+
app.executor.to_complete { CurrentRequest.cleanup }
|
29
|
+
end
|
23
30
|
end
|
24
|
-
|
25
31
|
end
|
26
32
|
end
|
@@ -7,23 +7,3 @@ module RailsLiveReload
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
11
|
-
# render_template.action_view
|
12
|
-
# {
|
13
|
-
# identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
|
14
|
-
# layout: "layouts/application"
|
15
|
-
# }
|
16
|
-
# render_partial.action_view
|
17
|
-
# {
|
18
|
-
# identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb"
|
19
|
-
# }
|
20
|
-
# render_collection.action_view
|
21
|
-
# {
|
22
|
-
# identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
|
23
|
-
# count: 3,
|
24
|
-
# cache_hits: 0
|
25
|
-
# }
|
26
|
-
# render_layout.action_view
|
27
|
-
# {
|
28
|
-
# identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
|
29
|
-
# }
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
module Rails
|
3
|
+
module Middleware
|
4
|
+
class Base
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
dup.call!(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call!(env)
|
14
|
+
request = Rack::Request.new(env)
|
15
|
+
|
16
|
+
if env["REQUEST_PATH"] == RailsLiveReload.config.url
|
17
|
+
rails_live_response(request)
|
18
|
+
else
|
19
|
+
@status, @headers, @response = @app.call(env)
|
20
|
+
|
21
|
+
if html? && @response.respond_to?(:[]) && (@status == 500 || (@status.to_s =~ /20./ && request.get?))
|
22
|
+
new_response = make_new_response(@response[0])
|
23
|
+
@headers['Content-Length'] = new_response.bytesize.to_s
|
24
|
+
@response = [new_response]
|
25
|
+
end
|
26
|
+
|
27
|
+
[@status, @headers, @response]
|
28
|
+
end
|
29
|
+
rescue Exception => ex
|
30
|
+
puts ex.message
|
31
|
+
puts ex.backtrace.take(10)
|
32
|
+
raise ex
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def rails_live_response(request)
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
def client_javascript(request)
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def make_new_response(body)
|
46
|
+
body.sub("</head>", "</head>#{client_javascript}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def html?
|
50
|
+
@headers["Content-Type"].to_s.include?("text/html")
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_thread
|
54
|
+
Thread.new {
|
55
|
+
t2 = Thread.current
|
56
|
+
t2.abort_on_exception = true
|
57
|
+
yield
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
module Rails
|
3
|
+
module Middleware
|
4
|
+
class LongPolling < Base
|
5
|
+
private
|
6
|
+
|
7
|
+
def rails_live_response(request)
|
8
|
+
params = request.params
|
9
|
+
body = lambda do |stream|
|
10
|
+
new_thread do
|
11
|
+
counter = 0
|
12
|
+
|
13
|
+
loop do
|
14
|
+
command = RailsLiveReload::Command.new(params)
|
15
|
+
|
16
|
+
if command.reload?
|
17
|
+
stream.write(command.payload.to_json) and break
|
18
|
+
end
|
19
|
+
|
20
|
+
sleep(RailsLiveReload.config.long_polling_sleep_duration)
|
21
|
+
counter += 1
|
22
|
+
|
23
|
+
stream.write(command.payload.to_json) and break if counter >= max_sleeps_count
|
24
|
+
end
|
25
|
+
ensure
|
26
|
+
stream.close
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
[ 200, { 'Content-Type' => 'application/json', 'rack.hijack' => body }, nil ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def client_javascript
|
34
|
+
RailsLiveReload::Client.long_polling_js
|
35
|
+
end
|
36
|
+
|
37
|
+
def max_sleeps_count
|
38
|
+
RailsLiveReload.config.timeout * (1 / RailsLiveReload.config.long_polling_sleep_duration)
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_thread
|
42
|
+
Thread.new {
|
43
|
+
t2 = Thread.current
|
44
|
+
t2.abort_on_exception = true
|
45
|
+
yield
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
module Rails
|
3
|
+
module Middleware
|
4
|
+
class Polling < Base
|
5
|
+
private
|
6
|
+
|
7
|
+
def rails_live_response(request)
|
8
|
+
[
|
9
|
+
200,
|
10
|
+
{ 'Content-Type' => 'application/json' },
|
11
|
+
[ RailsLiveReload::Command.new(request.params).payload.to_json ]
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
def client_javascript
|
16
|
+
RailsLiveReload::Client.polling_js
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module RailsLiveReload
|
2
2
|
class Watcher
|
3
|
-
attr_reader :root
|
3
|
+
attr_reader :root, :files
|
4
4
|
|
5
5
|
def Watcher.init
|
6
6
|
watcher = new
|
@@ -9,12 +9,15 @@ module RailsLiveReload
|
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@root = ::Rails.application.root
|
12
|
+
@files = {}
|
13
|
+
|
12
14
|
puts "Watching: #{root}"
|
13
15
|
RailsLiveReload.patterns.each do |pattern, rule|
|
14
16
|
puts " #{pattern} => #{rule}"
|
15
17
|
end
|
16
|
-
|
17
|
-
|
18
|
+
|
19
|
+
build_tree
|
20
|
+
start_listener
|
18
21
|
end
|
19
22
|
|
20
23
|
def start_listener
|
@@ -22,7 +25,7 @@ module RailsLiveReload
|
|
22
25
|
listener = Listen.to(root) do |modified, added, removed|
|
23
26
|
all = modified + added + removed
|
24
27
|
all.each do |file|
|
25
|
-
|
28
|
+
files[file] = File.mtime(file).to_i rescue nil
|
26
29
|
end
|
27
30
|
end
|
28
31
|
listener.start
|
@@ -31,8 +34,8 @@ module RailsLiveReload
|
|
31
34
|
|
32
35
|
def build_tree
|
33
36
|
Dir.glob(File.join(root, '**', '*')).select{|file| File.file?(file)}.each do |file|
|
34
|
-
|
37
|
+
files[file] = File.mtime(file).to_i rescue nil
|
35
38
|
end
|
36
39
|
end
|
37
40
|
end
|
38
|
-
end
|
41
|
+
end
|
data/lib/rails_live_reload.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require "listen"
|
2
2
|
require "rails_live_reload/version"
|
3
|
+
require "rails_live_reload/config"
|
3
4
|
require "rails_live_reload/client"
|
4
5
|
require "rails_live_reload/watcher"
|
5
|
-
require "rails_live_reload/rails/middleware"
|
6
|
+
require "rails_live_reload/rails/middleware/base"
|
7
|
+
require "rails_live_reload/rails/middleware/long_polling"
|
8
|
+
require "rails_live_reload/rails/middleware/polling"
|
6
9
|
require "rails_live_reload/instrument/metrics_collector"
|
7
10
|
require "rails_live_reload/thread/current_request"
|
8
11
|
require "rails_live_reload/checker"
|
@@ -10,32 +13,13 @@ require "rails_live_reload/command"
|
|
10
13
|
require "rails_live_reload/engine"
|
11
14
|
|
12
15
|
module RailsLiveReload
|
13
|
-
mattr_accessor :files
|
14
|
-
@@files = {}
|
15
|
-
|
16
16
|
mattr_accessor :watcher
|
17
|
-
@@watcher =
|
18
|
-
|
19
|
-
mattr_accessor :url
|
20
|
-
@@url = "/rails/live/reload"
|
21
|
-
|
22
|
-
mattr_accessor :patterns
|
23
|
-
@@patterns = {}
|
24
|
-
|
25
|
-
mattr_accessor :timeout
|
26
|
-
@@timeout = 100
|
17
|
+
@@watcher = {}
|
27
18
|
|
28
|
-
def self.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
RailsLiveReload.patterns[pattern] = reload
|
19
|
+
def self.middleware
|
20
|
+
case config.mode
|
21
|
+
when :polling then RailsLiveReload::Rails::Middleware::Polling
|
22
|
+
when :long_polling then RailsLiveReload::Rails::Middleware::LongPolling
|
23
|
+
end
|
34
24
|
end
|
35
25
|
end
|
36
|
-
|
37
|
-
# default watch settings
|
38
|
-
RailsLiveReload.setup do |config|
|
39
|
-
config.watch %r{app/views/.+\.(erb|haml|slim)$}
|
40
|
-
config.watch %r{(app|vendor)/(assets|javascript)/\w+/(.+\.(css|js|html|png|jpg|ts|jsx)).*}, reload: :always
|
41
|
-
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_live_reload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Kasyanchuk
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-05-
|
12
|
+
date: 2022-05-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -125,9 +125,12 @@ files:
|
|
125
125
|
- lib/rails_live_reload/checker.rb
|
126
126
|
- lib/rails_live_reload/client.rb
|
127
127
|
- lib/rails_live_reload/command.rb
|
128
|
+
- lib/rails_live_reload/config.rb
|
128
129
|
- lib/rails_live_reload/engine.rb
|
129
130
|
- lib/rails_live_reload/instrument/metrics_collector.rb
|
130
|
-
- lib/rails_live_reload/rails/middleware.rb
|
131
|
+
- lib/rails_live_reload/rails/middleware/base.rb
|
132
|
+
- lib/rails_live_reload/rails/middleware/long_polling.rb
|
133
|
+
- lib/rails_live_reload/rails/middleware/polling.rb
|
131
134
|
- lib/rails_live_reload/thread/current_request.rb
|
132
135
|
- lib/rails_live_reload/version.rb
|
133
136
|
- lib/rails_live_reload/watcher.rb
|
@@ -150,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
153
|
- !ruby/object:Gem::Version
|
151
154
|
version: '0'
|
152
155
|
requirements: []
|
153
|
-
rubygems_version: 3.
|
156
|
+
rubygems_version: 3.3.7
|
154
157
|
signing_key:
|
155
158
|
specification_version: 4
|
156
159
|
summary: Ruby on Rails Live Reload
|
@@ -1,54 +0,0 @@
|
|
1
|
-
module RailsLiveReload
|
2
|
-
module Rails
|
3
|
-
class Middleware
|
4
|
-
def initialize(app)
|
5
|
-
@app = app
|
6
|
-
end
|
7
|
-
|
8
|
-
def call(env)
|
9
|
-
dup.call!(env)
|
10
|
-
end
|
11
|
-
|
12
|
-
def call!(env)
|
13
|
-
request = Rack::Request.new(env)
|
14
|
-
|
15
|
-
if env["REQUEST_PATH"] == RailsLiveReload.url
|
16
|
-
rails_live_response(request)
|
17
|
-
else
|
18
|
-
@status, @headers, @response = @app.call(env)
|
19
|
-
|
20
|
-
if html? && @response.respond_to?(:[]) && (@status == 500 || (@status.to_s =~ /20./ && request.get?))
|
21
|
-
new_response = make_new_response(@response[0])
|
22
|
-
@headers['Content-Length'] = new_response.bytesize.to_s
|
23
|
-
@response = [new_response]
|
24
|
-
end
|
25
|
-
|
26
|
-
[@status, @headers, @response]
|
27
|
-
end
|
28
|
-
rescue Exception => ex
|
29
|
-
puts ex.message
|
30
|
-
puts ex.backtrace.take(10)
|
31
|
-
raise ex
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def rails_live_response(request)
|
37
|
-
[
|
38
|
-
200,
|
39
|
-
{ 'Content-Type' => 'application/json' },
|
40
|
-
[ RailsLiveReload::Command.new(request.params).command.to_json ]
|
41
|
-
]
|
42
|
-
end
|
43
|
-
|
44
|
-
def make_new_response(body)
|
45
|
-
body.sub("</head>", "</head>#{RailsLiveReload::Client.js_code}")
|
46
|
-
end
|
47
|
-
|
48
|
-
def html?
|
49
|
-
@headers["Content-Type"].include?("text/html")
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|