Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use rack 3 proc responses for SSE live reload #858

Merged
merged 5 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ PATH
kramdown-parser-gfm (~> 1.0)
liquid (~> 5.0)
listen (~> 3.0)
rack (>= 3.0)
rake (>= 13.0)
roda (~> 3.46)
rouge (>= 3.0, < 5.0)
Expand Down
1 change: 1 addition & 0 deletions bridgetown-core/bridgetown-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency("kramdown-parser-gfm", "~> 1.0")
s.add_runtime_dependency("liquid", "~> 5.0")
s.add_runtime_dependency("listen", "~> 3.0")
s.add_runtime_dependency("rack", ">= 3.0")
s.add_runtime_dependency("rake", ">= 13.0")
s.add_runtime_dependency("roda", "~> 3.46")
s.add_runtime_dependency("rouge", [">= 3.0", "< 5.0"])
Expand Down
59 changes: 24 additions & 35 deletions bridgetown-core/lib/bridgetown-core/rack/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@

module Bridgetown
module Rack
@interrupted = false

class << self
attr_accessor :interrupted
end

class Routes
include Bridgetown::Prioritizable

Expand Down Expand Up @@ -160,33 +154,40 @@ def load_all_routes(roda_app)

# @param app [Roda]
def setup_live_reload(app) # rubocop:disable Metrics
sleep_interval = 0.2
sleep_interval = 0.5
file_to_check = File.join(Bridgetown::Current.preloaded_configuration.destination,
"index.html")
errors_file = Bridgetown.build_errors_path

app.request.get "_bridgetown/live_reload" do
app.response["Content-Type"] = "text/event-stream"

@_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
app.stream async: true do |out|
# 5 second intervals so Puma's threads aren't all exausted
(5 / sleep_interval).to_i.times do
break if Bridgetown::Rack.interrupted

new_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0
if @_mod < new_mod
out << "data: reloaded!\n\n"
event_stream = proc do |stream|
Thread.new do
loop do
new_mod = File.exist?(file_to_check) ? File.stat(file_to_check).mtime.to_i : 0

if @_mod < new_mod
stream.write "data: reloaded!\n\n"
break
elsif File.exist?(errors_file)
stream.write "event: builderror\ndata: #{File.read(errors_file).to_json}\n\n"
else
stream.write "data: #{new_mod}\n\n"
end

sleep sleep_interval
rescue Errno::EPIPE # User refreshed the page
break
elsif File.exist?(errors_file)
out << "event: builderror\ndata: #{File.read(errors_file).to_json}\n\n"
else
out << "data: #{new_mod}\n\n"
end

sleep sleep_interval
ensure
stream.close
end
end

app.request.halt [200, {
"Content-Type" => "text/event-stream",
"cache-control" => "no-cache",
}, event_stream,]
end
end
end
Expand Down Expand Up @@ -219,15 +220,3 @@ def respond_to_missing?(method_name, include_private = false)
end
end
end

if defined?(Puma) && Bridgetown.env.development? &&
!Bridgetown::Current.preloaded_configuration.skip_live_reload
Puma::Launcher.class_eval do
alias_method :_old_stop, :stop
def stop
Bridgetown::Rack.interrupted = true

_old_stop
end
end
end
39 changes: 24 additions & 15 deletions bridgetown-core/lib/bridgetown-core/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,15 @@ def default_github_branch_name(repo_url)
def live_reload_js(site) # rubocop:disable Metrics/MethodLength
return "" unless Bridgetown.env.development? && !site.config.skip_live_reload

path = File.join(site.base_path, "/_bridgetown/live_reload")
code = <<~JAVASCRIPT
let lastmod = 0
function startReloadConnection() {
const evtSource = new EventSource("#{site.base_path(strip_slash_only: true)}/_bridgetown/live_reload")
evtSource.onmessage = event => {
let reconnectAttempts = 0
function startLiveReload() {
const connection = new EventSource("#{path}")

connection.addEventListener("message", event => {
reconnectAttempts = 0
if (document.querySelector("#bridgetown-build-error")) document.querySelector("#bridgetown-build-error").close()
if (event.data == "reloaded!") {
location.reload()
Expand All @@ -478,8 +482,9 @@ def live_reload_js(site) # rubocop:disable Metrics/MethodLength
lastmod = newmod
}
}
}
evtSource.addEventListener("builderror", event => {
})

connection.addEventListener("builderror", event => {
let dialog = document.querySelector("#bridgetown-build-error")
if (!dialog) {
dialog = document.createElement("dialog")
Expand All @@ -496,19 +501,23 @@ def live_reload_js(site) # rubocop:disable Metrics/MethodLength
}
dialog.querySelector("pre").textContent = JSON.parse(event.data)
})
evtSource.onerror = event => {
if (evtSource.readyState === 2) {
// reconnect with new object
evtSource.close()
console.warn("Live reload: attempting to reconnect in 3 seconds...")

setTimeout(() => startReloadConnection(), 3000)
connection.addEventListener("error", () => {
if (connection.readyState === 2) {
// reconnect with new object
connection.close()
reconnectAttempts++
if (reconnectAttempts < 25) {
console.warn("Live reload: attempting to reconnect in 3 seconds...")
setTimeout(() => startLiveReload(), 3000)
} else {
console.error("Too many live reload connections failed. Refresh the page to try again.")
}
}
}
})
}
setTimeout(() => {
startReloadConnection()
}, 500)

startLiveReload()
JAVASCRIPT

%(<script type="module">#{code}</script>).html_safe
Expand Down
1 change: 0 additions & 1 deletion bridgetown-core/lib/roda/plugins/bridgetown_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def self.load_dependencies(app) # rubocop:disable Metrics
app.plugin :json_parser
app.plugin :indifferent_params
app.plugin :cookies
app.plugin :streaming
app.plugin :public, root: Bridgetown::Current.preloaded_configuration.destination
app.plugin :not_found do
output_folder = Bridgetown::Current.preloaded_configuration.destination
Expand Down