hotwire-spark 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|