rails_live_reload 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []