staticky 0.1.1 → 0.3.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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +14 -0
- data/README.md +378 -18
- data/lib/staticky/application.rb +36 -0
- data/lib/staticky/builder.rb +4 -2
- data/lib/staticky/cli/commands/build.rb +13 -0
- data/lib/staticky/cli/commands/generate.rb +67 -0
- data/lib/staticky/cli/commands/version.rb +13 -0
- data/lib/staticky/cli/commands.rb +13 -0
- data/lib/staticky/cli.rb +0 -60
- data/lib/staticky/deps.rb +1 -1
- data/lib/staticky/filesystem.rb +0 -3
- data/lib/staticky/phlex/view_helpers.rb +12 -2
- data/lib/staticky/pluggable.rb +35 -0
- data/lib/staticky/resource.rb +14 -15
- data/lib/staticky/resources/plugins/phlex.rb +42 -0
- data/lib/staticky/resources/plugins/prelude.rb +76 -0
- data/lib/staticky/resources/plugins.rb +9 -0
- data/lib/staticky/router.rb +13 -18
- data/lib/staticky/routing/plugins/prelude.rb +97 -0
- data/lib/staticky/routing/plugins.rb +9 -0
- data/lib/staticky/server.rb +5 -16
- data/lib/staticky/server_plugin.rb +37 -0
- data/lib/staticky/server_plugins/live_reloading.rb +58 -0
- data/lib/staticky/utils.rb +63 -0
- data/lib/staticky/version.rb +1 -1
- data/lib/staticky.rb +23 -11
- data/site_template/.gitignore +14 -0
- data/site_template/.ruby-version +1 -1
- data/site_template/Gemfile +5 -5
- data/site_template/Procfile.dev +2 -2
- data/site_template/README.md +27 -7
- data/site_template/Rakefile +33 -2
- data/site_template/{lib/component.rb → app/views/application_component.rb} +1 -1
- data/site_template/app/views/application_layout.rb +4 -0
- data/site_template/app/views/application_page.rb +5 -0
- data/site_template/app/views/errors/not_found.rb +1 -1
- data/site_template/app/views/errors/service_error.rb +1 -1
- data/site_template/app/views/layouts/error.rb +4 -6
- data/site_template/app/views/layouts/head.rb +5 -3
- data/site_template/app/views/layouts/site.rb +10 -23
- data/site_template/app/views/pages/home.rb +1 -1
- data/site_template/app/views/ui/footer.rb +1 -1
- data/site_template/app/views/ui/navbar.rb +1 -1
- data/site_template/bin/{lint → setup} +8 -2
- data/site_template/config/boot.rb +1 -2
- data/site_template/config/puma.rb +11 -0
- data/site_template/config/staticky.rb +3 -0
- data/site_template/config/vite.json +3 -1
- data/site_template/frontend/entrypoints/application.js +0 -1
- data/site_template/frontend/tailwindcss/variable_font_plugin.js +1 -1
- data/site_template/lib/icon.rb +2 -2
- data/site_template/nginx.conf +8 -1
- data/site_template/package.json +10 -15
- data/site_template/tailwind.config.js +12 -8
- data/site_template/vite.config.ts +0 -5
- metadata +50 -11
- data/lib/staticky/container.rb +0 -26
- data/lib/staticky/router/definition.rb +0 -49
- data/lib/staticky/view_context.rb +0 -17
- data/site_template/frontend/turbo_transitions.js +0 -54
- data/site_template/lib/layout.rb +0 -4
- data/site_template/lib/page.rb +0 -11
data/lib/staticky/cli.rb
CHANGED
@@ -4,66 +4,6 @@ require "dry/cli"
|
|
4
4
|
|
5
5
|
module Staticky
|
6
6
|
module CLI
|
7
|
-
module Commands
|
8
|
-
extend Dry::CLI::Registry
|
9
|
-
|
10
|
-
class Version < Dry::CLI::Command
|
11
|
-
desc "Print version"
|
12
|
-
|
13
|
-
def call(*) = puts VERSION
|
14
|
-
end
|
15
|
-
|
16
|
-
class Build < Dry::CLI::Command
|
17
|
-
desc "Build site"
|
18
|
-
|
19
|
-
def call(*) = Staticky.builder.call
|
20
|
-
end
|
21
|
-
|
22
|
-
class Generate < Dry::CLI::Command
|
23
|
-
desc "Create new site"
|
24
|
-
|
25
|
-
argument :path,
|
26
|
-
required: true,
|
27
|
-
desc: "Relative path where the site will be generated"
|
28
|
-
|
29
|
-
option :url,
|
30
|
-
default: "https://example.com",
|
31
|
-
desc: "Site URL",
|
32
|
-
aliases: ["-u"]
|
33
|
-
option :title,
|
34
|
-
default: "Example",
|
35
|
-
desc: "Site title",
|
36
|
-
aliases: ["-t"]
|
37
|
-
option :description,
|
38
|
-
default: "Example site",
|
39
|
-
desc: "Site description",
|
40
|
-
aliases: ["-d"]
|
41
|
-
option :twitter,
|
42
|
-
default: "",
|
43
|
-
desc: "Twitter handle",
|
44
|
-
aliases: ["-t"]
|
45
|
-
|
46
|
-
def call(path:, **)
|
47
|
-
path = Pathname.new(path).expand_path
|
48
|
-
|
49
|
-
Staticky.generator.call(path, **)
|
50
|
-
|
51
|
-
commands = [
|
52
|
-
"bundle install",
|
53
|
-
"bundle binstubs bundler rake rspec-core vite_ruby",
|
54
|
-
"yarn install",
|
55
|
-
"bin/rspec"
|
56
|
-
].join(" && ")
|
57
|
-
|
58
|
-
system(commands, chdir: path) || abort("install failed")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
register "version", Version
|
63
|
-
register "build", Build
|
64
|
-
register "new", Generate
|
65
|
-
end
|
66
|
-
|
67
7
|
def self.new(...)
|
68
8
|
Dry::CLI.new(Commands)
|
69
9
|
end
|
data/lib/staticky/deps.rb
CHANGED
data/lib/staticky/filesystem.rb
CHANGED
@@ -3,10 +3,20 @@
|
|
3
3
|
module Staticky
|
4
4
|
module Phlex
|
5
5
|
module ViewHelpers
|
6
|
-
def
|
6
|
+
def helpers
|
7
|
+
@_view_context
|
8
|
+
end
|
9
|
+
|
10
|
+
def staticky_live_reload_js(base_path = "/")
|
11
|
+
script(type: :module) do
|
12
|
+
unsafe_raw Staticky::Utils.live_reload_js(base_path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def link_to(text = nil, href, **, &block) # rubocop:disable Style/OptionalArguments
|
7
17
|
block ||= proc { text }
|
8
18
|
href = Staticky.router.resolve(href)
|
9
|
-
href = href.
|
19
|
+
href = href.uri.to_s unless href.is_a?(String)
|
10
20
|
|
11
21
|
a(href:, **, &block)
|
12
22
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Pluggable
|
5
|
+
class Resolver < Dry::Container::Resolver
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(container, key)
|
11
|
+
container.fetch(key.to_s).call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def load_plugin(key)
|
17
|
+
resolve(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_plugin(key, klass)
|
21
|
+
register(key.to_s, klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(base)
|
26
|
+
base.extend Dry::Container::Mixin
|
27
|
+
base.extend ClassMethods
|
28
|
+
base.config.resolver = Resolver.new(base)
|
29
|
+
|
30
|
+
base.define_singleton_method :namespace do
|
31
|
+
base.name.split("::")[0..-2].join("::")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/staticky/resource.rb
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Staticky
|
4
|
-
Resource
|
5
|
-
def
|
6
|
-
|
7
|
-
|
4
|
+
class Resource
|
5
|
+
def self.plugin(plugin, ...)
|
6
|
+
plugin = Resources::Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
7
|
+
unless plugin.is_a?(Module)
|
8
|
+
raise ArgumentError, "Invalid plugin type: #{plugin.class.inspect}"
|
9
|
+
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
if plugin.respond_to?(:load_dependencies)
|
12
|
+
plugin.load_dependencies(self, ...)
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
15
|
+
include plugin::InstanceMethods if defined?(plugin::InstanceMethods)
|
16
|
+
extend plugin::ClassMethods if defined?(plugin::ClassMethods)
|
16
17
|
|
17
|
-
|
18
|
-
url == "/"
|
18
|
+
plugin.configure(self, ...) if plugin.respond_to?(:configure)
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
21
|
+
plugin :prelude
|
22
|
+
plugin :phlex
|
24
23
|
end
|
25
24
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Resources
|
5
|
+
module Plugins
|
6
|
+
module Phlex
|
7
|
+
class ViewContext < SimpleDelegator
|
8
|
+
def initialize(resource)
|
9
|
+
super
|
10
|
+
@resource = resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def root?
|
14
|
+
@resource.root?
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_path
|
18
|
+
@resource.url
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
def component=(component)
|
24
|
+
@component = component
|
25
|
+
end
|
26
|
+
|
27
|
+
def component
|
28
|
+
return @component if defined?(@component)
|
29
|
+
|
30
|
+
raise ArgumentError, "component is required"
|
31
|
+
end
|
32
|
+
|
33
|
+
def build(view_context: ViewContext.new(self))
|
34
|
+
component.call(view_context:)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
register_plugin(:phlex, Phlex)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Resources
|
5
|
+
module Plugins
|
6
|
+
module Prelude
|
7
|
+
module ClassMethods
|
8
|
+
def new(**env)
|
9
|
+
super().tap do |resource|
|
10
|
+
env.each do |key, value|
|
11
|
+
resource.send(:"#{key}=", value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def build_path
|
19
|
+
destination.join(filepath)
|
20
|
+
end
|
21
|
+
|
22
|
+
def filepath
|
23
|
+
return Pathname.new("index.html") if root?
|
24
|
+
|
25
|
+
Pathname.new("#{uri.path.gsub(%r{^/}, "")}.html")
|
26
|
+
end
|
27
|
+
|
28
|
+
def read
|
29
|
+
Staticky.files.read(build_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def root?
|
33
|
+
url == "/"
|
34
|
+
end
|
35
|
+
|
36
|
+
def uri
|
37
|
+
return @uri if defined?(@uri)
|
38
|
+
|
39
|
+
raise ArgumentError, "url is required"
|
40
|
+
end
|
41
|
+
|
42
|
+
def destination
|
43
|
+
@destination ||= Staticky.build_path
|
44
|
+
end
|
45
|
+
|
46
|
+
def destination=(destination)
|
47
|
+
@destination = Pathname(destination)
|
48
|
+
end
|
49
|
+
|
50
|
+
def url
|
51
|
+
return @url if defined?(@url)
|
52
|
+
|
53
|
+
raise ArgumentError, "url is required"
|
54
|
+
end
|
55
|
+
|
56
|
+
def url=(url)
|
57
|
+
@url = url
|
58
|
+
@uri = parse_url(url)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def parse_url(url)
|
64
|
+
URI(url).tap do |uri|
|
65
|
+
uri.path = "/#{uri.path}" unless uri.path.start_with?("/")
|
66
|
+
end
|
67
|
+
rescue URI::InvalidURIError => e
|
68
|
+
raise ArgumentError, e.message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
register_plugin(:prelude, Prelude)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/staticky/router.rb
CHANGED
@@ -10,29 +10,24 @@ module Staticky
|
|
10
10
|
# In Staticky when we build we need to do a lot of introspection to link
|
11
11
|
# routes to resources on the file system.
|
12
12
|
|
13
|
-
|
14
|
-
@definition = Staticky::Router::Definition.new
|
15
|
-
end
|
13
|
+
Error = Class.new(Staticky::Error)
|
16
14
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
15
|
+
def self.plugin(plugin, ...)
|
16
|
+
plugin = Routing::Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
17
|
+
unless plugin.is_a?(Module)
|
18
|
+
raise ArgumentError, "Invalid plugin type: #{plugin.class.inspect}"
|
20
19
|
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def filepaths
|
24
|
-
@definition.filepaths
|
25
|
-
end
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
21
|
+
if plugin.respond_to?(:load_dependencies)
|
22
|
+
plugin.load_dependencies(self, ...)
|
23
|
+
end
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
return path if path.is_a?(String) && path.start_with?("http")
|
25
|
+
include plugin::InstanceMethods if defined?(plugin::InstanceMethods)
|
26
|
+
extend plugin::ClassMethods if defined?(plugin::ClassMethods)
|
34
27
|
|
35
|
-
|
28
|
+
plugin.configure(self, ...) if plugin.respond_to?(:configure)
|
36
29
|
end
|
30
|
+
|
31
|
+
plugin :prelude
|
37
32
|
end
|
38
33
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Routing
|
5
|
+
module Plugins
|
6
|
+
module Prelude
|
7
|
+
module ClassMethods
|
8
|
+
def new(**env)
|
9
|
+
super().tap do |router|
|
10
|
+
env.each do |key, value|
|
11
|
+
router.send(:"#{key}=", value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def define(&block)
|
19
|
+
tap do
|
20
|
+
instance_eval(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def match(path, to:, as: Resource)
|
25
|
+
path = strip_leading_slash(path)
|
26
|
+
|
27
|
+
resource = case to
|
28
|
+
when ->(x) { x.is_a?(Class) || x.is_a?(::Phlex::HTML) }
|
29
|
+
component = ensure_instance(to)
|
30
|
+
as.new(component:, url: path)
|
31
|
+
else
|
32
|
+
raise Router::Error, "Invalid route target: #{to.inspect}"
|
33
|
+
end
|
34
|
+
|
35
|
+
resources << resource
|
36
|
+
index_resource(path, resource)
|
37
|
+
end
|
38
|
+
|
39
|
+
def root(to:)
|
40
|
+
match("/", to:)
|
41
|
+
end
|
42
|
+
|
43
|
+
def resolve(path)
|
44
|
+
return path if path.is_a?(String) && path.start_with?("#")
|
45
|
+
return lookup(path) if path.is_a?(Class)
|
46
|
+
|
47
|
+
path = strip_leading_slash(path)
|
48
|
+
uri = URI(path)
|
49
|
+
# Return absolute paths as is
|
50
|
+
return path if uri.absolute?
|
51
|
+
|
52
|
+
if uri.path.size > 1 && uri.path.start_with?("/")
|
53
|
+
uri.path = uri.path[1..]
|
54
|
+
end
|
55
|
+
|
56
|
+
lookup(uri.path)
|
57
|
+
rescue URI::InvalidURIError
|
58
|
+
raise Router::Error, "Invalid path: #{path}"
|
59
|
+
rescue KeyError
|
60
|
+
raise Router::Error, "No route matches #{path}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def resources = @resources ||= []
|
64
|
+
def filepaths = resources.map(&:filepath)
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def lookup(path)
|
69
|
+
@routes_by_path.fetch(path) do
|
70
|
+
@routes_by_component.fetch(path)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def index_resource(path, resource)
|
75
|
+
routes_by_path[path] = resource
|
76
|
+
routes_by_component[resource.component.class] = resource
|
77
|
+
end
|
78
|
+
|
79
|
+
def routes_by_path = @routes_by_path ||= {}
|
80
|
+
def routes_by_component = @routes_by_component ||= {}
|
81
|
+
|
82
|
+
def ensure_instance(component)
|
83
|
+
component.is_a?(Class) ? component.new : component
|
84
|
+
end
|
85
|
+
|
86
|
+
def strip_leading_slash(path)
|
87
|
+
return path if path == "/"
|
88
|
+
|
89
|
+
path.to_s.gsub(%r{^/}, "")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
register_plugin(:prelude, Prelude)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/staticky/server.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "roda"
|
4
4
|
|
5
5
|
require_relative "../staticky"
|
6
|
+
require_relative "server_plugin"
|
6
7
|
|
7
8
|
module Staticky
|
8
9
|
class Server < Roda
|
@@ -12,25 +13,13 @@ module Staticky
|
|
12
13
|
|
13
14
|
NotFound = Class.new(Staticky::Error)
|
14
15
|
|
15
|
-
plugin :
|
16
|
-
plugin :render, engine: "html"
|
17
|
-
plugin :public
|
18
|
-
|
19
|
-
plugin :not_found do
|
20
|
-
raise NotFound if Staticky.env.test?
|
21
|
-
|
22
|
-
Staticky.build_path.join("404.html").read
|
23
|
-
end
|
24
|
-
|
25
|
-
plugin :error_handler do |e|
|
26
|
-
raise e if Staticky.env.test?
|
27
|
-
|
28
|
-
Staticky.build_path.join("500.html").read
|
29
|
-
end
|
16
|
+
plugin :staticky_server
|
30
17
|
|
31
18
|
route do |r|
|
19
|
+
r.staticky
|
20
|
+
|
32
21
|
Staticky.resources.each do |resource|
|
33
|
-
case resource.filepath
|
22
|
+
case resource.filepath.to_s
|
34
23
|
when "index.html"
|
35
24
|
r.root do
|
36
25
|
render(inline: resource.read)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module ServerPlugin
|
5
|
+
def self.load_dependencies(app)
|
6
|
+
app.plugin :common_logger, Staticky.server_logger, method: :debug
|
7
|
+
app.plugin :render, engine: "html"
|
8
|
+
app.plugin :public
|
9
|
+
app.plugin :exception_page
|
10
|
+
|
11
|
+
app.plugin :not_found do
|
12
|
+
raise Staticky::Server::NotFound if Staticky.env.test?
|
13
|
+
|
14
|
+
Staticky.build_path.join("404.html").read
|
15
|
+
end
|
16
|
+
|
17
|
+
app.plugin :error_handler do |error|
|
18
|
+
raise error if Staticky.env.test?
|
19
|
+
next exception_page(error) if Staticky.env.development?
|
20
|
+
|
21
|
+
Staticky.build_path.join("500.html").read
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module RequestMethods
|
26
|
+
def staticky
|
27
|
+
unless Staticky.env.development? && Staticky.config.live_reloading
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
ServerPlugins::LiveReloading.setup_live_reload(scope)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Roda::RodaPlugins.register_plugin(:staticky_server, self)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module ServerPlugins
|
5
|
+
module LiveReloading
|
6
|
+
def self.setup_live_reload(app) # rubocop:disable Metrics
|
7
|
+
sleep_interval = 0.5
|
8
|
+
file_to_check = Staticky.build_path.join("index.html")
|
9
|
+
errors_file = Staticky.build_path.join("errors.json")
|
10
|
+
|
11
|
+
app.request.get "_staticky/live_reload" do # rubocop:disable Metrics/BlockLength
|
12
|
+
@_mod = if Staticky.files.exist?(file_to_check)
|
13
|
+
file_to_check.mtime.to_i
|
14
|
+
else
|
15
|
+
0
|
16
|
+
end
|
17
|
+
|
18
|
+
event_stream = proc do |stream|
|
19
|
+
Thread.new do
|
20
|
+
loop do
|
21
|
+
new_mod = if Staticky.files.exist?(file_to_check)
|
22
|
+
file_to_check.mtime.to_i
|
23
|
+
else
|
24
|
+
0
|
25
|
+
end
|
26
|
+
|
27
|
+
if @_mod < new_mod
|
28
|
+
stream.write "data: reloaded!\n\n"
|
29
|
+
break
|
30
|
+
elsif Staticky.files.exist?(errors_file)
|
31
|
+
stream.write "event: builderror\n" \
|
32
|
+
"data: #{errors_file.read.to_json}\n\n"
|
33
|
+
else
|
34
|
+
stream.write "data: #{new_mod}\n\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
sleep sleep_interval
|
38
|
+
rescue Errno::EPIPE # User refreshed the page
|
39
|
+
break
|
40
|
+
end
|
41
|
+
ensure
|
42
|
+
stream.close
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
app.request.halt [
|
47
|
+
200,
|
48
|
+
{
|
49
|
+
"Content-Type" => "text/event-stream",
|
50
|
+
"cache-control" => "no-cache"
|
51
|
+
},
|
52
|
+
event_stream
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Staticky
|
4
|
+
module Utils
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def live_reload_js(base_path) # rubocop:disable Metrics/MethodLength
|
8
|
+
return "" unless Staticky.env.development?
|
9
|
+
|
10
|
+
path = File.join(base_path, "/_staticky/live_reload")
|
11
|
+
|
12
|
+
<<~JAVASCRIPT
|
13
|
+
let lastmod = 0
|
14
|
+
let reconnectAttempts = 0
|
15
|
+
|
16
|
+
function statickyReload() {
|
17
|
+
if (window.Turbo) {
|
18
|
+
Turbo.visit(window.location)
|
19
|
+
} else {
|
20
|
+
location.reload()
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
function startLiveReload() {
|
25
|
+
const connection = new EventSource("#{path}")
|
26
|
+
|
27
|
+
connection.addEventListener("message", event => {
|
28
|
+
reconnectAttempts = 0
|
29
|
+
|
30
|
+
if (event.data == "reloaded!") {
|
31
|
+
statickyReload()
|
32
|
+
} else {
|
33
|
+
const newmod = Number(event.data)
|
34
|
+
|
35
|
+
if (lastmod < newmod) {
|
36
|
+
statickyReload()
|
37
|
+
lastmod = newmod
|
38
|
+
}
|
39
|
+
}
|
40
|
+
})
|
41
|
+
|
42
|
+
connection.addEventListener("error", () => {
|
43
|
+
if (connection.readyState === 2) {
|
44
|
+
// reconnect with new object
|
45
|
+
connection.close()
|
46
|
+
reconnectAttempts++
|
47
|
+
if (reconnectAttempts < 25) {
|
48
|
+
console.warn("Live reload: attempting to reconnect in 3 seconds...")
|
49
|
+
setTimeout(() => startLiveReload(), 3000)
|
50
|
+
} else {
|
51
|
+
console.error(
|
52
|
+
"Too many live reload connections failed. Refresh the page to try again."
|
53
|
+
)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
})
|
57
|
+
}
|
58
|
+
|
59
|
+
startLiveReload()
|
60
|
+
JAVASCRIPT
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/staticky/version.rb
CHANGED