rails_live_reload 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a12bfdd549b010582467b39ea3f897cd1576b6e85f9595893482cc6d9738a8bf
4
+ data.tar.gz: 0e1340b8b309f291c4e0eeae134ba70409c26bd483316c512670c815e056f738
5
+ SHA512:
6
+ metadata.gz: 64ccc99a6ae7c6e858bc6b623aa38b2f52000b2aa4131e01e2f54438dd186913a625a8e6bc9a45ae80e72012d25ce4be3a701f5bd1df59eb8585ccccbd0ac8d1
7
+ data.tar.gz: dff2d37006b4316bcd4d5dcfd2fd97f5f116c300fe1fb2313082abe226b6f5443f883477338bc08ca19da795a4ef61c96849b20e4be3981903ebecf704956818
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Rails Live Reload
2
+
3
+ This is the simplest and probably the most robust way to add a support for a live reload to your Rails app.
4
+
5
+ Just add the gem and thats all - congratulation, now you have a live reload.
6
+
7
+ Works with:
8
+
9
+ - views (EBR/HAML/SLIM) (reloading page only if you editing views which were rendered)
10
+ - partials
11
+ - CSS/JS
12
+ - helpers, locales (if you configure)
13
+ - on a "crash" page, so once you made a fix page it will be reloaded
14
+
15
+ Page is reloaded with `window.location.reload()` so it's the most robust way to reload the page because of CSS/JS/etc.
16
+
17
+ ## Usage
18
+
19
+ Just add this gem to the Gemfile (in development environment) and start the `rails s`.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ group :development do
27
+ gem "rails_live_reload"
28
+ end
29
+ ```
30
+
31
+ And then execute:
32
+ ```bash
33
+ $ bundle
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ Default configuration `config/initializers/rails_live_reload.rb`:
39
+
40
+
41
+ ```ruby
42
+ RailsLiveReload.setup do |config|
43
+ config.url = "/rails/live/reload"
44
+ config.timeout = 100
45
+
46
+ # Watched folders & files
47
+ config.watch %r{app/views/.+\.(erb|haml|slim)$}
48
+ config.watch %r{(app|vendor)/(assets|javascript)/\w+/(.+\.(css|js|html|png|jpg|ts|jsx)).*}, reload: :always
49
+
50
+ # More examples:
51
+ # config.watch %r{app/helpers/.+\.rb}, reload: :always
52
+ # config.watch %r{config/locales/.+\.yml}, reload: :always
53
+ end
54
+ ```
55
+
56
+ ## How it works
57
+
58
+ There are 3 main pieces how it works:
59
+
60
+ 1) listener for file changes using `listen` gem
61
+ 2) collector of rendered views (see rails instrumentation)
62
+ 3) middleware which is responding to setInterval JS calls
63
+
64
+ ## Contributing
65
+
66
+ You are welcome to contribute. See list of `TODO's` below.
67
+
68
+ ## TODO
69
+
70
+ - reload CSS without reloading the whole page?
71
+ - smarter reload if there is a change in helper (check methods from rendered views?)
72
+ - generator for initializer
73
+ - check how it works with webpacker/importmaps/etc
74
+ - maybe complex rules, e.g. if "user.rb" file is changed - automatically reload all "users" views
75
+ - check with older Rails versions
76
+ - tests or specs
77
+ - CI (github actions)
78
+
79
+ ## License
80
+
81
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
82
+
83
+ [<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)
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,36 @@
1
+ module RailsLiveReload
2
+ class Checker
3
+
4
+ def self.scan(dt, rendered_files)
5
+ temp = []
6
+
7
+ # all changed files
8
+ RailsLiveReload.files.each do |file, fdt|
9
+ temp << file if fdt && fdt > dt
10
+ end
11
+
12
+ # ::Rails.logger.info "Changed: #{temp}"
13
+
14
+ result = []
15
+
16
+ temp.each do |file|
17
+ # ::Rails.logger.info "Checking: #{file}"
18
+ RailsLiveReload.patterns.each do |pattern, rule|
19
+ #puts "pattern = #{pattern} & rule = #{rule}"
20
+ # 1. CSS, JS, yaml, helper .rb
21
+ # 2. checking if rendered file
22
+ rule_1 = file.match(pattern) && rule == :always
23
+ rule_2 = file.match(pattern) && rendered_files.include?(file)
24
+
25
+ if rule_1 || rule_2
26
+ result << file
27
+ break
28
+ end
29
+ end
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module RailsLiveReload
2
+ class Client
3
+
4
+ def Client.js_code
5
+ %Q{
6
+ <script>
7
+ const files = #{CurrentRequest.current.data.to_a.to_json};
8
+ setInterval(
9
+ () => {
10
+ const formData = new FormData();
11
+ formData.append('dt', #{Time.now.to_i})
12
+ formData.append('files', JSON.stringify(files))
13
+
14
+ fetch(
15
+ "#{RailsLiveReload.url}",
16
+ {
17
+ method: "post",
18
+ headers: { 'Accept': 'application/json', },
19
+ body: formData
20
+ }
21
+ )
22
+ .then(response => response.json())
23
+ .then(data => {
24
+ if(data['command'] === 'RELOAD') {
25
+ window.location.reload()
26
+ }
27
+ })
28
+ }, #{RailsLiveReload.timeout}
29
+ )
30
+ </script>
31
+ }.html_safe
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module RailsLiveReload
2
+ class Command
3
+ attr_reader :dt, :files
4
+
5
+ def initialize(params)
6
+ @dt = params["dt"].to_i
7
+ @files = JSON.parse(params["files"])
8
+ end
9
+
10
+ def command
11
+ changes = RailsLiveReload::Checker.scan(dt, files)
12
+
13
+ if changes.size == 0
14
+ { command: "NO_CHANGES" }
15
+ else
16
+ { command: "RELOAD" }
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module RailsLiveReload
2
+ class Railtie < ::Rails::Engine
3
+
4
+ initializer "rails_live_reload.middleware" do |app|
5
+ if ::Rails::VERSION::MAJOR.to_i >= 5
6
+ app.middleware.insert_after ActionDispatch::Executor, RailsLiveReload::Rails::Middleware
7
+ else
8
+ begin
9
+ app.middleware.insert_after ActionDispatch::Static, RailsLiveReload::Rails::Middleware
10
+ rescue
11
+ app.middleware.insert_after Rack::SendFile, RailsLiveReload::Rails::Middleware
12
+ end
13
+ end
14
+
15
+ RailsLiveReload::Watcher.init
16
+ end
17
+
18
+ initializer :configure_metrics, after: :initialize_logger do
19
+ ActiveSupport::Notifications.subscribe(
20
+ /\.action_view/,
21
+ RailsLiveReload::Instrument::MetricsCollector.new
22
+ )
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module RailsLiveReload
2
+ module Instrument
3
+ class MetricsCollector
4
+ def call(event_name, started, finished, event_id, payload)
5
+ CurrentRequest.current.data.add(payload[:identifier])
6
+ end
7
+ end
8
+ end
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,54 @@
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
@@ -0,0 +1,23 @@
1
+ module RailsLiveReload
2
+ class CurrentRequest
3
+ attr_accessor :data, :record
4
+ attr_reader :request_id
5
+
6
+ def CurrentRequest.init
7
+ Thread.current[:rc_current_request] ||= CurrentRequest.new(SecureRandom.hex(16))
8
+ end
9
+
10
+ def CurrentRequest.current
11
+ CurrentRequest.init
12
+ end
13
+
14
+ def CurrentRequest.cleanup
15
+ Thread.current[:rc_current_request] = nil
16
+ end
17
+
18
+ def initialize(request_id)
19
+ @request_id = request_id
20
+ @data = Set.new
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module RailsLiveReload
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,38 @@
1
+ module RailsLiveReload
2
+ class Watcher
3
+ attr_reader :root
4
+
5
+ def Watcher.init
6
+ watcher = new
7
+ RailsLiveReload.watcher = watcher
8
+ end
9
+
10
+ def initialize
11
+ @root = ::Rails.application.root
12
+ puts "Watching: #{root}"
13
+ RailsLiveReload.patterns.each do |pattern, rule|
14
+ puts " #{pattern} => #{rule}"
15
+ end
16
+ self.build_tree
17
+ self.start_listener
18
+ end
19
+
20
+ def start_listener
21
+ Thread.new do
22
+ listener = Listen.to(root) do |modified, added, removed|
23
+ all = modified + added + removed
24
+ all.each do |file|
25
+ RailsLiveReload.files[file] = File.mtime(file).to_i rescue nil
26
+ end
27
+ end
28
+ listener.start
29
+ end
30
+ end
31
+
32
+ def build_tree
33
+ Dir.glob(File.join(root, '**', '*')).select{|file| File.file?(file)}.each do |file|
34
+ RailsLiveReload.files[file] = File.mtime(file).to_i rescue nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ require "listen"
2
+ require "rails_live_reload/version"
3
+ require "rails_live_reload/client"
4
+ require "rails_live_reload/watcher"
5
+ require "rails_live_reload/rails/middleware"
6
+ require "rails_live_reload/instrument/metrics_collector"
7
+ require "rails_live_reload/thread/current_request"
8
+ require "rails_live_reload/checker"
9
+ require "rails_live_reload/command"
10
+ require "rails_live_reload/engine"
11
+
12
+ module RailsLiveReload
13
+ mattr_accessor :files
14
+ @@files = {}
15
+
16
+ mattr_accessor :watcher
17
+ @@watcher = nil
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
27
+
28
+ def self.setup
29
+ yield(self)
30
+ end
31
+
32
+ def self.watch(pattern, reload: :on_change)
33
+ RailsLiveReload.patterns[pattern] = reload
34
+ end
35
+ 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 ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_live_reload
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Igor Kasyanchuk
8
+ - Liubomyr Manastyretskyi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-05-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: listen
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: puma
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: pry-nav
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: sprockets-rails
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: wrapped_print
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ description: Ruby on Rails Live Reload with just a single line of code - just add
113
+ the gem to Gemfile.
114
+ email:
115
+ - igorkasyanchuk@gmail.com
116
+ - manastyretskyi@gmail.com
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - MIT-LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - lib/rails_live_reload.rb
125
+ - lib/rails_live_reload/checker.rb
126
+ - lib/rails_live_reload/client.rb
127
+ - lib/rails_live_reload/command.rb
128
+ - lib/rails_live_reload/engine.rb
129
+ - lib/rails_live_reload/instrument/metrics_collector.rb
130
+ - lib/rails_live_reload/rails/middleware.rb
131
+ - lib/rails_live_reload/thread/current_request.rb
132
+ - lib/rails_live_reload/version.rb
133
+ - lib/rails_live_reload/watcher.rb
134
+ homepage: https://github.com/railsjazz/rails_live_reload
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubygems_version: 3.3.3
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Ruby on Rails Live Reload
157
+ test_files: []