hotwire-spark 0.1.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/app/assets/javascripts/hotwire_spark.js +3705 -0
- data/app/assets/javascripts/hotwire_spark.js.map +1 -0
- data/app/assets/javascripts/hotwire_spark.min.js +2 -0
- data/app/assets/javascripts/hotwire_spark.min.js.map +1 -0
- data/app/assets/stylesheets/hotwire_spark/application.css +15 -0
- data/app/channels/hotwire/spark/channel.rb +5 -0
- data/app/javascript/hotwire/spark/channels/consumer.js +3 -0
- data/app/javascript/hotwire/spark/channels/monitoring_channel.js +47 -0
- data/app/javascript/hotwire/spark/helpers.js +37 -0
- data/app/javascript/hotwire/spark/index.js +14 -0
- data/app/javascript/hotwire/spark/logger.js +8 -0
- data/app/javascript/hotwire/spark/reloaders/css_reloader.js +65 -0
- data/app/javascript/hotwire/spark/reloaders/html_reloader.js +31 -0
- data/app/javascript/hotwire/spark/reloaders/stimulus_reloader.js +76 -0
- data/config/routes.rb +2 -0
- data/lib/hotwire/spark/action_cable/persistent_cable_middleware.rb +43 -0
- data/lib/hotwire/spark/action_cable/persistent_cable_server.rb +25 -0
- data/lib/hotwire/spark/action_cable/solid_cable_listener_with_safe_reloads.rb +8 -0
- data/lib/hotwire/spark/engine.rb +25 -0
- data/lib/hotwire/spark/file_watcher.rb +40 -0
- data/lib/hotwire/spark/installer.rb +52 -0
- data/lib/hotwire/spark/middleware.rb +55 -0
- data/lib/hotwire/spark/version.rb +5 -0
- data/lib/hotwire-spark.rb +26 -0
- data/lib/tasks/hotwire_spark_tasks.rake +4 -0
- metadata +173 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
import consumer from "./consumer"
|
2
|
+
import { assetNameFromPath } from "../helpers.js";
|
3
|
+
import { HtmlReloader } from "../reloaders/html_reloader.js";
|
4
|
+
import { CssReloader } from "../reloaders/css_reloader.js";
|
5
|
+
import { StimulusReloader } from "../reloaders/stimulus_reloader.js";
|
6
|
+
|
7
|
+
consumer.subscriptions.create({ channel: "Hotwire::Spark::Channel" }, {
|
8
|
+
connected() {
|
9
|
+
document.body.setAttribute("data-hotwire-spark-ready", "")
|
10
|
+
},
|
11
|
+
|
12
|
+
async received(message) {
|
13
|
+
try {
|
14
|
+
await this.dispatch(message)
|
15
|
+
} catch(error) {
|
16
|
+
console.log(`Error on ${message.action}`, error)
|
17
|
+
}
|
18
|
+
},
|
19
|
+
|
20
|
+
dispatch({ action, path }) {
|
21
|
+
const fileName = assetNameFromPath(path)
|
22
|
+
|
23
|
+
switch(action) {
|
24
|
+
case "reload_html":
|
25
|
+
return this.reloadHtml()
|
26
|
+
case "reload_css":
|
27
|
+
return this.reloadCss(fileName)
|
28
|
+
case "reload_stimulus":
|
29
|
+
return this.reloadStimulus(fileName)
|
30
|
+
default:
|
31
|
+
throw new Error(`Unknown action: ${action}`)
|
32
|
+
}
|
33
|
+
},
|
34
|
+
|
35
|
+
reloadHtml() {
|
36
|
+
return HtmlReloader.reload()
|
37
|
+
},
|
38
|
+
|
39
|
+
reloadCss(fileName) {
|
40
|
+
return CssReloader.reload(new RegExp(fileName))
|
41
|
+
},
|
42
|
+
|
43
|
+
reloadStimulus(fileName) {
|
44
|
+
return StimulusReloader.reload(new RegExp(fileName))
|
45
|
+
}
|
46
|
+
})
|
47
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
export function assetNameFromPath(path) {
|
2
|
+
return path.split("/").pop().split(".")[0]
|
3
|
+
}
|
4
|
+
|
5
|
+
export function pathWithoutAssetDigest(path) {
|
6
|
+
return path.replace(/-[a-z0-9]+\.(\w+)(\?.*)?$/, ".$1")
|
7
|
+
}
|
8
|
+
|
9
|
+
export function urlWithParams(urlString, params) {
|
10
|
+
const url = new URL(urlString, window.location.origin)
|
11
|
+
Object.entries(params).forEach(([ key, value ]) => {
|
12
|
+
url.searchParams.set(key, value)
|
13
|
+
})
|
14
|
+
return url.toString()
|
15
|
+
}
|
16
|
+
|
17
|
+
export function cacheBustedUrl(urlString) {
|
18
|
+
return urlWithParams(urlString, { reload: Date.now() })
|
19
|
+
}
|
20
|
+
|
21
|
+
export async function reloadHtmlDocument() {
|
22
|
+
let currentUrl = cacheBustedUrl(urlWithParams(window.location.href, { hotwire_spark: "true" }))
|
23
|
+
const response = await fetch(currentUrl)
|
24
|
+
|
25
|
+
if (!response.ok) {
|
26
|
+
throw new Error(`${response.status} when fetching ${currentUrl}`)
|
27
|
+
}
|
28
|
+
|
29
|
+
const fetchedHTML = await response.text()
|
30
|
+
const parser = new DOMParser()
|
31
|
+
return parser.parseFromString(fetchedHTML, "text/html")
|
32
|
+
}
|
33
|
+
|
34
|
+
export function getConfigurationProperty(name) {
|
35
|
+
return document.querySelector(`meta[name="hotwire-spark:${name}"]`)?.content
|
36
|
+
}
|
37
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import "./channels/monitoring_channel.js"
|
2
|
+
import { getConfigurationProperty } from "./helpers.js";
|
3
|
+
|
4
|
+
const HotwireSpark = {
|
5
|
+
config: {
|
6
|
+
loggingEnabled: false
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
document.addEventListener("DOMContentLoaded", function() {
|
11
|
+
HotwireSpark.config.loggingEnabled = getConfigurationProperty("logging");
|
12
|
+
})
|
13
|
+
|
14
|
+
export default HotwireSpark
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { log } from "../logger.js"
|
2
|
+
import { cacheBustedUrl, reloadHtmlDocument, pathWithoutAssetDigest } from "../helpers.js"
|
3
|
+
|
4
|
+
export class CssReloader {
|
5
|
+
static async reload(...params) {
|
6
|
+
return new CssReloader(...params).reload()
|
7
|
+
}
|
8
|
+
|
9
|
+
constructor(filePattern = /./) {
|
10
|
+
this.filePattern = filePattern
|
11
|
+
}
|
12
|
+
|
13
|
+
async reload() {
|
14
|
+
log("Reload css...")
|
15
|
+
await Promise.all(await this.#reloadAllLinks())
|
16
|
+
}
|
17
|
+
|
18
|
+
async #reloadAllLinks() {
|
19
|
+
const cssLinks = await this.#loadNewCssLinks();
|
20
|
+
return cssLinks.map(link => this.#reloadLinkIfNeeded(link))
|
21
|
+
}
|
22
|
+
|
23
|
+
async #loadNewCssLinks() {
|
24
|
+
const reloadedDocument = await reloadHtmlDocument()
|
25
|
+
return Array.from(reloadedDocument.head.querySelectorAll("link[rel='stylesheet']"))
|
26
|
+
}
|
27
|
+
|
28
|
+
#reloadLinkIfNeeded(link) {
|
29
|
+
if (this.#shouldReloadLink(link)) {
|
30
|
+
return this.#reloadLink(link)
|
31
|
+
} else {
|
32
|
+
return Promise.resolve()
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
#shouldReloadLink(link) {
|
37
|
+
return this.filePattern.test(link.getAttribute("href"))
|
38
|
+
}
|
39
|
+
|
40
|
+
async #reloadLink(link) {
|
41
|
+
return new Promise(resolve => {
|
42
|
+
const href = link.getAttribute("href")
|
43
|
+
const newLink = this.#findExistingLinkFor(link) || this.#appendNewLink(link)
|
44
|
+
|
45
|
+
newLink.setAttribute("href", cacheBustedUrl(link.getAttribute("href")))
|
46
|
+
newLink.onload = () => {
|
47
|
+
log(`\t${href}`)
|
48
|
+
resolve()
|
49
|
+
}
|
50
|
+
})
|
51
|
+
}
|
52
|
+
|
53
|
+
#findExistingLinkFor(link) {
|
54
|
+
return this.#cssLinks.find(newLink => pathWithoutAssetDigest(link.href) === pathWithoutAssetDigest(newLink.href))
|
55
|
+
}
|
56
|
+
|
57
|
+
get #cssLinks() {
|
58
|
+
return Array.from(document.querySelectorAll("link[rel='stylesheet']"))
|
59
|
+
}
|
60
|
+
|
61
|
+
#appendNewLink(link) {
|
62
|
+
document.head.append(link)
|
63
|
+
return link
|
64
|
+
}
|
65
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { Idiomorph } from "idiomorph/dist/idiomorph.esm.js"
|
2
|
+
import { log } from "../logger.js"
|
3
|
+
import { reloadHtmlDocument } from "../helpers.js"
|
4
|
+
import { StimulusReloader } from "./stimulus_reloader.js"
|
5
|
+
|
6
|
+
export class HtmlReloader {
|
7
|
+
static async reload() {
|
8
|
+
return new HtmlReloader().reload()
|
9
|
+
}
|
10
|
+
|
11
|
+
async reload() {
|
12
|
+
const reloadedDocument = await this.#reloadHtml()
|
13
|
+
await this.#reloadStimulus(reloadedDocument)
|
14
|
+
}
|
15
|
+
|
16
|
+
async #reloadHtml() {
|
17
|
+
log("Reload html...")
|
18
|
+
|
19
|
+
const reloadedDocument = await reloadHtmlDocument()
|
20
|
+
this.#updateBody(reloadedDocument.body)
|
21
|
+
return reloadedDocument
|
22
|
+
}
|
23
|
+
|
24
|
+
#updateBody(newBody) {
|
25
|
+
Idiomorph.morph(document.body, newBody)
|
26
|
+
}
|
27
|
+
|
28
|
+
async #reloadStimulus(reloadedDocument) {
|
29
|
+
return new StimulusReloader(reloadedDocument).reload()
|
30
|
+
}
|
31
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
2
|
+
import { log } from "../logger.js"
|
3
|
+
import { cacheBustedUrl, reloadHtmlDocument } from "../helpers.js"
|
4
|
+
|
5
|
+
export class StimulusReloader {
|
6
|
+
static async reload(filePattern) {
|
7
|
+
const document = await reloadHtmlDocument()
|
8
|
+
return new StimulusReloader(document, filePattern).reload()
|
9
|
+
}
|
10
|
+
|
11
|
+
constructor(document, filePattern = /./) {
|
12
|
+
this.document = document
|
13
|
+
this.filePattern = filePattern
|
14
|
+
this.application = window.Stimulus || Application.start()
|
15
|
+
}
|
16
|
+
|
17
|
+
async reload() {
|
18
|
+
log("Reload Stimulus controllers...")
|
19
|
+
|
20
|
+
this.application.stop()
|
21
|
+
await this.#reloadStimulusControllers()
|
22
|
+
this.application.start()
|
23
|
+
}
|
24
|
+
|
25
|
+
async #reloadStimulusControllers() {
|
26
|
+
await Promise.all(
|
27
|
+
this.#stimulusControllerPaths.map(async moduleName => this.#reloadStimulusController(moduleName))
|
28
|
+
)
|
29
|
+
}
|
30
|
+
|
31
|
+
get #stimulusControllerPaths() {
|
32
|
+
return Object.keys(this.#stimulusPathsByModule).filter(path => path.endsWith("_controller") && this.#shouldReloadController(path))
|
33
|
+
}
|
34
|
+
|
35
|
+
#shouldReloadController(path) {
|
36
|
+
return this.filePattern.test(path)
|
37
|
+
}
|
38
|
+
|
39
|
+
get #stimulusPathsByModule() {
|
40
|
+
this.pathsByModule = this.pathsByModule || this.#parseImportmapJson()
|
41
|
+
return this.pathsByModule
|
42
|
+
}
|
43
|
+
|
44
|
+
#parseImportmapJson() {
|
45
|
+
const importmapScript = this.document.querySelector("script[type=importmap]")
|
46
|
+
return JSON.parse(importmapScript.text).imports
|
47
|
+
}
|
48
|
+
|
49
|
+
async #reloadStimulusController(moduleName) {
|
50
|
+
log(`\t${moduleName}`)
|
51
|
+
|
52
|
+
const controllerName = this.#extractControllerName(moduleName)
|
53
|
+
const path = cacheBustedUrl(this.#pathForModuleName(moduleName))
|
54
|
+
|
55
|
+
const module = await import(path)
|
56
|
+
|
57
|
+
this.#registerController(controllerName, module)
|
58
|
+
}
|
59
|
+
|
60
|
+
#pathForModuleName(moduleName) {
|
61
|
+
return this.#stimulusPathsByModule[moduleName]
|
62
|
+
}
|
63
|
+
|
64
|
+
#extractControllerName(path) {
|
65
|
+
return path
|
66
|
+
.replace(/^.*\//, "")
|
67
|
+
.replace("_controller", "")
|
68
|
+
.replace(/\//g, "--")
|
69
|
+
.replace(/_/g, "-")
|
70
|
+
}
|
71
|
+
|
72
|
+
#registerController(name, module) {
|
73
|
+
this.application.unload(name)
|
74
|
+
this.application.register(name, module.default)
|
75
|
+
}
|
76
|
+
}
|
data/config/routes.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class Hotwire::Spark::ActionCable::PersistentCableMiddleware
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
request = Rack::Request.new(env)
|
8
|
+
|
9
|
+
if supress_action_cable_restarts?(request)
|
10
|
+
respond_suppressing_action_cable_restarts(env)
|
11
|
+
else
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
COOKIE_NAME = "hotwire_spark_disable_cable_restarts"
|
18
|
+
RESTARTS_SUPPRESSED_GRACE_PERIOD = 10.seconds
|
19
|
+
|
20
|
+
def supress_action_cable_restarts?(request)
|
21
|
+
request.params["hotwire_spark"] || request.cookies[COOKIE_NAME]
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_suppressing_action_cable_restarts(env)
|
25
|
+
status, headers, body = suppressing_action_cable_restarts { @app.call(env) }
|
26
|
+
headers["Set-Cookie"] = append_cookie_to_disable_cable_restarts(headers["Set-Cookie"])
|
27
|
+
|
28
|
+
[ status, headers, body ]
|
29
|
+
end
|
30
|
+
|
31
|
+
def suppressing_action_cable_restarts(&block)
|
32
|
+
ActionCable.server.without_restarting(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def append_cookie_to_disable_cable_restarts(existing_cookies)
|
36
|
+
[ existing_cookies, cookie_to_disable_cable_restarts ].compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def cookie_to_disable_cable_restarts
|
40
|
+
expiration = RESTARTS_SUPPRESSED_GRACE_PERIOD.from_now.utc
|
41
|
+
"#{COOKIE_NAME}=true; Path=/; Expires=#{expiration.httpdate}; HttpOnly"
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hotwire::Spark::ActionCable::PersistentCableServer
|
2
|
+
def self.prepended(base)
|
3
|
+
base.class_eval do
|
4
|
+
thread_mattr_accessor :suppress_restarts
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def restart
|
9
|
+
return if restarts_suppressed?
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def without_restarting
|
15
|
+
old_suppress_restarts, self.suppress_restarts = self.suppress_restarts, true
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
self.suppress_restarts = old_suppress_restarts
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def restarts_suppressed?
|
23
|
+
suppress_restarts
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "action_cable/server/base"
|
2
|
+
|
3
|
+
module Hotwire::Spark
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace Hotwire::Spark
|
6
|
+
|
7
|
+
config.hotwire = ActiveSupport::OrderedOptions.new unless config.respond_to?(:hotwire)
|
8
|
+
config.hotwire.spark = ActiveSupport::OrderedOptions.new
|
9
|
+
config.hotwire.spark.merge! \
|
10
|
+
enabled: Rails.env.development?,
|
11
|
+
css_paths: %w[ app/assets/stylesheets ],
|
12
|
+
html_paths: %w[ app/controllers app/helpers app/models app/views ],
|
13
|
+
stimulus_paths: %w[ app/javascript/controllers ]
|
14
|
+
|
15
|
+
initializer "hotwire_spark.config" do |app|
|
16
|
+
config.hotwire.spark.each do |key, value|
|
17
|
+
Hotwire::Spark.send("#{key}=", value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initializer "hotwire_spark.install" do |application|
|
22
|
+
Hotwire::Spark.install_into application if Hotwire::Spark.enabled?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "listen"
|
2
|
+
|
3
|
+
class Hotwire::Spark::FileWatcher
|
4
|
+
def initialize
|
5
|
+
@callbacks_by_path = Hash.new { |hash, key| hash[key] = [] }
|
6
|
+
end
|
7
|
+
|
8
|
+
def monitor(paths, &callback)
|
9
|
+
Array(paths).each do |path|
|
10
|
+
@callbacks_by_path[expand_path(path)] << callback
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
listener = Listen.to(*paths) do |modified, added, removed|
|
16
|
+
process_changed_files modified + added + removed
|
17
|
+
end
|
18
|
+
|
19
|
+
listener.start
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def expand_path(path)
|
24
|
+
Rails.application.root.join(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def paths
|
28
|
+
@callbacks_by_path.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_changed_files(changed_files)
|
32
|
+
changed_files.each do |file|
|
33
|
+
@callbacks_by_path.each do |path, callbacks|
|
34
|
+
if file.to_s.start_with?(path.to_s)
|
35
|
+
callbacks.each { |callback| callback.call(file) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Hotwire::Spark::Installer
|
2
|
+
attr_reader :file_watcher
|
3
|
+
|
4
|
+
def initialize(application)
|
5
|
+
@application = application
|
6
|
+
end
|
7
|
+
|
8
|
+
def install
|
9
|
+
configure_middleware
|
10
|
+
monitor_paths
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure_middleware
|
14
|
+
::ActionCable::Server::Base.prepend(Hotwire::Spark::ActionCable::PersistentCableServer)
|
15
|
+
|
16
|
+
middleware.insert_before ActionDispatch::Executor, Hotwire::Spark::ActionCable::PersistentCableMiddleware
|
17
|
+
middleware.use Hotwire::Spark::Middleware
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
attr_reader :application
|
22
|
+
delegate :middleware, to: :application
|
23
|
+
|
24
|
+
def monitor_paths
|
25
|
+
register_monitored_paths
|
26
|
+
file_watcher.start
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_monitored_paths
|
30
|
+
monitor :css_paths, action: :reload_css
|
31
|
+
monitor :html_paths, action: :reload_html
|
32
|
+
monitor :stimulus_paths, action: :reload_stimulus
|
33
|
+
end
|
34
|
+
|
35
|
+
def monitor(paths_name, action:)
|
36
|
+
file_watcher.monitor Hotwire::Spark.public_send(paths_name) do |file_path|
|
37
|
+
broadcast_reload_action(action, file_path)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def broadcast_reload_action(action, file_path)
|
42
|
+
ActionCable.server.broadcast "hotwire_spark", reload_message_for(action, file_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def reload_message_for(action, file_path)
|
46
|
+
{ action: action, path: file_path }
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_watcher
|
50
|
+
@file_watches ||= Hotwire::Spark::FileWatcher.new
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Hotwire::Spark::Middleware
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
status, headers, response = @app.call(env)
|
8
|
+
|
9
|
+
if html_response?(headers)
|
10
|
+
html = html_from(response)
|
11
|
+
html = inject_javascript(html)
|
12
|
+
html = inject_options(html)
|
13
|
+
headers["Content-Length"] = html.bytesize.to_s if html
|
14
|
+
response = [ html ]
|
15
|
+
end
|
16
|
+
|
17
|
+
[ status, headers, response ]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def html_response?(headers)
|
22
|
+
headers["Content-Type"]&.include?("text/html")
|
23
|
+
end
|
24
|
+
|
25
|
+
def html_from(response)
|
26
|
+
response_body = []
|
27
|
+
response.each { |part| response_body << part }
|
28
|
+
response_body.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def inject_javascript(html)
|
32
|
+
html.sub("</head>", "#{script_tag}</head>")
|
33
|
+
end
|
34
|
+
|
35
|
+
def script_tag
|
36
|
+
script_path = view_helpers.asset_path("hotwire_spark.js")
|
37
|
+
view_helpers.javascript_include_tag(script_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def view_helpers
|
41
|
+
ActionController::Base.helpers
|
42
|
+
end
|
43
|
+
|
44
|
+
def inject_options(html)
|
45
|
+
if Hotwire::Spark.logging
|
46
|
+
html.sub("</head>", "#{logging_option}</head>")
|
47
|
+
else
|
48
|
+
html
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def logging_option
|
53
|
+
view_helpers.tag.meta(name: "hotwire-spark:logging", content: "true")
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "hotwire/spark/version"
|
2
|
+
require "hotwire/spark/engine"
|
3
|
+
|
4
|
+
require "zeitwerk"
|
5
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
6
|
+
loader.ignore("#{__dir__}/hotwire-spark.rb")
|
7
|
+
loader.setup
|
8
|
+
|
9
|
+
module Hotwire::Spark
|
10
|
+
mattr_accessor :css_paths, default: []
|
11
|
+
mattr_accessor :html_paths, default: []
|
12
|
+
mattr_accessor :stimulus_paths, default: []
|
13
|
+
mattr_accessor :logging, default: false
|
14
|
+
|
15
|
+
mattr_accessor :enabled, default: Rails.env.development?
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def install_into(application)
|
19
|
+
Installer.new(application).install
|
20
|
+
end
|
21
|
+
|
22
|
+
def enabled?
|
23
|
+
enabled
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|