hotwire-spark 0.1.9 → 0.1.10
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/README.md +27 -10
- data/app/assets/javascripts/hotwire_spark.js +101 -69
- data/app/assets/javascripts/hotwire_spark.min.js +1 -1
- data/app/assets/javascripts/hotwire_spark.min.js.map +1 -1
- data/app/controllers/hotwire/spark/source_files_controller.rb +14 -0
- data/app/javascript/hotwire/spark/channels/monitoring_channel.js +10 -9
- data/app/javascript/hotwire/spark/index.js +10 -2
- data/app/javascript/hotwire/spark/reloaders/{html_reloader.js → morph_html_reloader.js} +8 -8
- data/app/javascript/hotwire/spark/reloaders/replace_html_reloader.js +31 -0
- data/app/javascript/hotwire/spark/reloaders/stimulus_reloader.js +49 -59
- data/config/routes.rb +1 -0
- data/lib/hotwire/spark/default_options.rb +36 -0
- data/lib/hotwire/spark/engine.rb +2 -5
- data/lib/hotwire/spark/file_watcher.rb +5 -1
- data/lib/hotwire/spark/installer.rb +17 -6
- data/lib/hotwire/spark/middleware.rb +12 -7
- data/lib/hotwire/spark/version.rb +1 -1
- data/lib/hotwire-spark.rb +6 -3
- metadata +8 -5
@@ -0,0 +1,31 @@
|
|
1
|
+
import { log } from "../logger.js"
|
2
|
+
|
3
|
+
export class ReplaceHtmlReloader {
|
4
|
+
static async reload() {
|
5
|
+
return new ReplaceHtmlReloader().reload()
|
6
|
+
}
|
7
|
+
|
8
|
+
async reload() {
|
9
|
+
await this.#reloadHtml()
|
10
|
+
}
|
11
|
+
|
12
|
+
async #reloadHtml() {
|
13
|
+
log("Reload html with Turbo...")
|
14
|
+
|
15
|
+
this.#maintainScrollPosition()
|
16
|
+
await this.#visitCurrentPage()
|
17
|
+
}
|
18
|
+
|
19
|
+
#maintainScrollPosition() {
|
20
|
+
document.addEventListener("turbo:before-render", () => {
|
21
|
+
Turbo.navigator.currentVisit.scrolled = true
|
22
|
+
}, { once: true })
|
23
|
+
}
|
24
|
+
|
25
|
+
#visitCurrentPage() {
|
26
|
+
return new Promise(resolve => {
|
27
|
+
document.addEventListener("turbo:load", () => resolve(document), { once: true })
|
28
|
+
window.Turbo.visit(window.location)
|
29
|
+
})
|
30
|
+
}
|
31
|
+
}
|
@@ -1,15 +1,21 @@
|
|
1
1
|
import { log } from "../logger.js"
|
2
|
-
import { cacheBustedUrl, reloadHtmlDocument } from "../helpers.js"
|
3
2
|
|
4
3
|
export class StimulusReloader {
|
5
|
-
static async reload(
|
6
|
-
|
7
|
-
return new StimulusReloader(document, filePattern).reload()
|
4
|
+
static async reload(path) {
|
5
|
+
return new StimulusReloader(path).reload()
|
8
6
|
}
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
static async reloadAll() {
|
9
|
+
Stimulus.controllers.forEach(controller => {
|
10
|
+
Stimulus.unload(controller.identifier)
|
11
|
+
Stimulus.register(controller.identifier, controller.constructor)
|
12
|
+
})
|
13
|
+
|
14
|
+
return Promise.resolve()
|
15
|
+
}
|
16
|
+
|
17
|
+
constructor(changedPath) {
|
18
|
+
this.changedPath = changedPath
|
13
19
|
this.application = window.Stimulus
|
14
20
|
}
|
15
21
|
|
@@ -18,70 +24,45 @@ export class StimulusReloader {
|
|
18
24
|
|
19
25
|
this.application.stop()
|
20
26
|
|
21
|
-
|
22
|
-
|
27
|
+
try {
|
28
|
+
await this.#reloadChangedController()
|
29
|
+
}
|
30
|
+
catch(error) {
|
31
|
+
if (error instanceof SourceFileNotFound) {
|
32
|
+
this.#deregisterChangedController()
|
33
|
+
} else {
|
34
|
+
console.error("Error reloading controller", error)
|
35
|
+
}
|
36
|
+
}
|
23
37
|
|
24
38
|
this.application.start()
|
25
39
|
}
|
26
40
|
|
27
|
-
async #
|
28
|
-
await
|
29
|
-
|
30
|
-
)
|
31
|
-
}
|
32
|
-
|
33
|
-
get #stimulusControllerPathsToReload() {
|
34
|
-
this.controllerPathsToReload = this.controllerPathsToReload || this.#stimulusControllerPaths.filter(path => this.#shouldReloadController(path))
|
35
|
-
return this.controllerPathsToReload
|
36
|
-
}
|
37
|
-
|
38
|
-
get #stimulusControllerPaths() {
|
39
|
-
return Object.keys(this.#stimulusPathsByModule).filter(path => path.endsWith("_controller"))
|
40
|
-
}
|
41
|
-
|
42
|
-
#shouldReloadController(path) {
|
43
|
-
return this.filePattern.test(path)
|
44
|
-
}
|
45
|
-
|
46
|
-
get #stimulusPathsByModule() {
|
47
|
-
this.pathsByModule = this.pathsByModule || this.#parseImportmapJson()
|
48
|
-
return this.pathsByModule
|
41
|
+
async #reloadChangedController() {
|
42
|
+
const module = await this.#importControllerFromSource(this.changedPath)
|
43
|
+
await this.#registerController(this.#changedControllerIdentifier, module)
|
49
44
|
}
|
50
45
|
|
51
|
-
#
|
52
|
-
const
|
53
|
-
return JSON.parse(importmapScript.text).imports
|
54
|
-
}
|
55
|
-
|
56
|
-
async #reloadStimulusController(moduleName) {
|
57
|
-
log(`\t${moduleName}`)
|
46
|
+
async #importControllerFromSource(path) {
|
47
|
+
const response = await fetch(`/spark/source_files/?path=${path}`)
|
58
48
|
|
59
|
-
|
60
|
-
|
49
|
+
if (response.status === 404) {
|
50
|
+
throw new SourceFileNotFound(`Source file not found: ${path}`)
|
51
|
+
}
|
61
52
|
|
62
|
-
const
|
53
|
+
const sourceCode = await response.text()
|
63
54
|
|
64
|
-
|
65
|
-
|
55
|
+
const blob = new Blob([sourceCode], { type: "application/javascript" })
|
56
|
+
const moduleUrl = URL.createObjectURL(blob)
|
57
|
+
const module = await import(moduleUrl)
|
58
|
+
URL.revokeObjectURL(moduleUrl)
|
66
59
|
|
67
|
-
|
68
|
-
this.#controllersToUnload.forEach(controller => this.#deregisterController(controller.identifier))
|
69
|
-
}
|
70
|
-
|
71
|
-
get #controllersToUnload() {
|
72
|
-
if (this.#didChangeTriggerAReload) {
|
73
|
-
return []
|
74
|
-
} else {
|
75
|
-
return this.application.controllers.filter(controller => this.filePattern.test(`${controller.identifier}_controller`))
|
76
|
-
}
|
60
|
+
return module
|
77
61
|
}
|
78
62
|
|
79
|
-
get #
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
#pathForModuleName(moduleName) {
|
84
|
-
return this.#stimulusPathsByModule[moduleName]
|
63
|
+
get #changedControllerIdentifier() {
|
64
|
+
this.changedControllerIdentifier = this.changedControllerIdentifier || this.#extractControllerName(this.changedPath)
|
65
|
+
return this.changedControllerIdentifier
|
85
66
|
}
|
86
67
|
|
87
68
|
#extractControllerName(path) {
|
@@ -90,9 +71,16 @@ export class StimulusReloader {
|
|
90
71
|
.replace("_controller", "")
|
91
72
|
.replace(/\//g, "--")
|
92
73
|
.replace(/_/g, "-")
|
74
|
+
.replace(/\.js$/, "")
|
75
|
+
}
|
76
|
+
|
77
|
+
#deregisterChangedController() {
|
78
|
+
this.#deregisterController(this.#changedControllerIdentifier)
|
93
79
|
}
|
94
80
|
|
95
81
|
#registerController(name, module) {
|
82
|
+
log("\tReloading controller", name)
|
83
|
+
|
96
84
|
this.application.unload(name)
|
97
85
|
this.application.register(name, module.default)
|
98
86
|
}
|
@@ -102,3 +90,5 @@ export class StimulusReloader {
|
|
102
90
|
this.application.unload(name)
|
103
91
|
}
|
104
92
|
}
|
93
|
+
|
94
|
+
class SourceFileNotFound extends Error { }
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
class Hotwire::Spark::DefaultOptions
|
2
|
+
def initialize
|
3
|
+
@config = base_options
|
4
|
+
|
5
|
+
build
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_h
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def base_options
|
14
|
+
{
|
15
|
+
enabled: Rails.env.development?,
|
16
|
+
css_paths: File.directory?("app/assets/builds") ? %w[ app/assets/builds ] : %w[ app/assets/stylesheets ],
|
17
|
+
css_extensions: %w[ css ],
|
18
|
+
html_paths: %w[ app/controllers app/helpers app/models app/views ],
|
19
|
+
html_extensions: %w[ rb erb ],
|
20
|
+
stimulus_paths: %w[ app/javascript/controllers ],
|
21
|
+
stimulus_extensions: %w[ js ],
|
22
|
+
html_reload_method: :morph
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def build
|
27
|
+
configure_jsbundling if defined?(Jsbundling)
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure_jsbundling
|
31
|
+
@config[:stimulus_paths] = []
|
32
|
+
@config[:html_paths] << "app/assets/builds"
|
33
|
+
@config[:html_extensions] << "js"
|
34
|
+
@config[:html_reload_method] = :replace
|
35
|
+
end
|
36
|
+
end
|
data/lib/hotwire/spark/engine.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "action_cable/server/base"
|
2
|
+
require "hotwire/spark/default_options"
|
2
3
|
|
3
4
|
module Hotwire::Spark
|
4
5
|
class Engine < ::Rails::Engine
|
@@ -6,11 +7,7 @@ module Hotwire::Spark
|
|
6
7
|
|
7
8
|
config.hotwire = ActiveSupport::OrderedOptions.new unless config.respond_to?(:hotwire)
|
8
9
|
config.hotwire.spark = ActiveSupport::OrderedOptions.new
|
9
|
-
config.hotwire.spark.merge!
|
10
|
-
enabled: Rails.env.development?,
|
11
|
-
css_paths: File.directory?("app/assets/builds") ? %w[ app/assets/builds ] : %w[ app/assets/stylesheets ],
|
12
|
-
html_paths: %w[ app/controllers app/helpers app/models app/views ],
|
13
|
-
stimulus_paths: %w[ app/javascript/controllers ]
|
10
|
+
config.hotwire.spark.merge! Hotwire::Spark::DefaultOptions.new.to_h
|
14
11
|
|
15
12
|
initializer "hotwire_spark.config" do |application|
|
16
13
|
config.hotwire.spark.each do |key, value|
|
@@ -36,9 +36,13 @@ class Hotwire::Spark::FileWatcher
|
|
36
36
|
changed_files.each do |file|
|
37
37
|
@callbacks_by_path.each do |path, callbacks|
|
38
38
|
if file.to_s.start_with?(path.to_s)
|
39
|
-
callbacks.each { |callback| callback.call(file) }
|
39
|
+
callbacks.each { |callback| callback.call(as_relative_path(file)) }
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
|
+
|
45
|
+
def as_relative_path(path)
|
46
|
+
Pathname.new(path).relative_path_from(Rails.application.root)
|
47
|
+
end
|
44
48
|
end
|
@@ -5,6 +5,7 @@ class Hotwire::Spark::Installer
|
|
5
5
|
|
6
6
|
def install
|
7
7
|
configure_cable_server
|
8
|
+
configure_routes
|
8
9
|
configure_middleware
|
9
10
|
monitor_paths
|
10
11
|
end
|
@@ -19,6 +20,12 @@ class Hotwire::Spark::Installer
|
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
23
|
+
def configure_routes
|
24
|
+
application.routes.prepend do
|
25
|
+
mount Hotwire::Spark::Engine => "/spark", as: "hotwire_spark"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
22
29
|
def configure_middleware
|
23
30
|
middleware.use Hotwire::Spark::Middleware
|
24
31
|
end
|
@@ -29,14 +36,18 @@ class Hotwire::Spark::Installer
|
|
29
36
|
end
|
30
37
|
|
31
38
|
def register_monitored_paths
|
32
|
-
monitor :css_paths, action: :reload_css
|
33
|
-
monitor :html_paths, action: :reload_html
|
34
|
-
monitor :stimulus_paths, action: :reload_stimulus
|
39
|
+
monitor :css_paths, action: :reload_css, extensions: Hotwire::Spark.css_extensions
|
40
|
+
monitor :html_paths, action: :reload_html, extensions: Hotwire::Spark.html_extensions
|
41
|
+
monitor :stimulus_paths, action: :reload_stimulus, extensions: Hotwire::Spark.stimulus_extensions
|
35
42
|
end
|
36
43
|
|
37
|
-
def monitor(paths_name, action:)
|
38
|
-
|
39
|
-
|
44
|
+
def monitor(paths_name, action:, extensions:)
|
45
|
+
paths = Hotwire::Spark.public_send(paths_name)
|
46
|
+
if paths.present?
|
47
|
+
file_watcher.monitor paths do |file_path|
|
48
|
+
pattern = /#{extensions.map { |ext| "\\." + ext }.join("|")}$/
|
49
|
+
broadcast_reload_action(action, file_path) if file_path.to_s =~ pattern
|
50
|
+
end
|
40
51
|
end
|
41
52
|
end
|
42
53
|
|
@@ -7,6 +7,7 @@ class Hotwire::Spark::Middleware
|
|
7
7
|
status, headers, response = @app.call(env)
|
8
8
|
|
9
9
|
if html_response?(headers)
|
10
|
+
@request = ActionDispatch::Request.new(env)
|
10
11
|
html = html_from(response)
|
11
12
|
html = inject_javascript(html)
|
12
13
|
html = inject_options(html)
|
@@ -38,18 +39,22 @@ class Hotwire::Spark::Middleware
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def view_helpers
|
41
|
-
|
42
|
+
@request.controller_instance.helpers
|
42
43
|
end
|
43
44
|
|
44
45
|
def inject_options(html)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
html.sub("</head>", "#{options}</head>")
|
47
|
+
end
|
48
|
+
|
49
|
+
def options
|
50
|
+
[ logging_option, html_reload_method_option ].compact.join("\n")
|
50
51
|
end
|
51
52
|
|
52
53
|
def logging_option
|
53
|
-
view_helpers.tag.meta(name: "hotwire-spark:logging", content: "true")
|
54
|
+
view_helpers.tag.meta(name: "hotwire-spark:logging", content: "true") if Hotwire::Spark.logging
|
55
|
+
end
|
56
|
+
|
57
|
+
def html_reload_method_option
|
58
|
+
view_helpers.tag.meta(name: "hotwire-spark:html-reload-method", content: Hotwire::Spark.html_reload_method)
|
54
59
|
end
|
55
60
|
end
|
data/lib/hotwire-spark.rb
CHANGED
@@ -8,10 +8,13 @@ loader.ignore("#{__dir__}/hotwire/spark/version.rb")
|
|
8
8
|
loader.setup
|
9
9
|
|
10
10
|
module Hotwire::Spark
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
%i[ css html stimulus ].each do |type|
|
12
|
+
mattr_accessor "#{type}_paths".to_sym, default: []
|
13
|
+
mattr_accessor "#{type}_extensions".to_sym, default: []
|
14
|
+
end
|
15
|
+
|
14
16
|
mattr_accessor :logging, default: false
|
17
|
+
mattr_accessor :html_reload_method, default: :morph
|
15
18
|
|
16
19
|
mattr_accessor :enabled, default: Rails.env.development?
|
17
20
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hotwire-spark
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jorge Manrubia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 7.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: zeitwerk
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,17 +125,20 @@ files:
|
|
125
125
|
- app/assets/javascripts/hotwire_spark.min.js.map
|
126
126
|
- app/assets/stylesheets/hotwire_spark/application.css
|
127
127
|
- app/channels/hotwire/spark/channel.rb
|
128
|
+
- app/controllers/hotwire/spark/source_files_controller.rb
|
128
129
|
- app/javascript/hotwire/spark/channels/consumer.js
|
129
130
|
- app/javascript/hotwire/spark/channels/monitoring_channel.js
|
130
131
|
- app/javascript/hotwire/spark/helpers.js
|
131
132
|
- app/javascript/hotwire/spark/index.js
|
132
133
|
- app/javascript/hotwire/spark/logger.js
|
133
134
|
- app/javascript/hotwire/spark/reloaders/css_reloader.js
|
134
|
-
- app/javascript/hotwire/spark/reloaders/
|
135
|
+
- app/javascript/hotwire/spark/reloaders/morph_html_reloader.js
|
136
|
+
- app/javascript/hotwire/spark/reloaders/replace_html_reloader.js
|
135
137
|
- app/javascript/hotwire/spark/reloaders/stimulus_reloader.js
|
136
138
|
- config/routes.rb
|
137
139
|
- lib/hotwire-spark.rb
|
138
140
|
- lib/hotwire/spark/action_cable/server.rb
|
141
|
+
- lib/hotwire/spark/default_options.rb
|
139
142
|
- lib/hotwire/spark/engine.rb
|
140
143
|
- lib/hotwire/spark/file_watcher.rb
|
141
144
|
- lib/hotwire/spark/installer.rb
|