mata 0.10.0 → 0.11.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/README.md +26 -14
- data/lib/mata/agent.rb +9 -1
- data/lib/mata/broadcaster.rb +29 -9
- data/lib/mata/client.js +52 -32
- data/lib/mata/version.rb +1 -1
- data/lib/mata/watch_tower.rb +4 -2
- data/lib/mata.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 886a7db4fb956cd1191b502d73b582569e3dc680ca3c375fdf42296fb5d6197b
|
|
4
|
+
data.tar.gz: d87cd56304b9eb144d3b9a997598f09df16d94292c9724cf3312f0e9b6298411
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e82ce5b92c7ae0ee99758373be421ac03abbc0546af2ccab1f51f74584b4d13fbeaa4efbf0c8af1c3efc6e6da9f9703ccf74cae7dc46e3fb3605364c3a0d7ff
|
|
7
|
+
data.tar.gz: 59b37d520084e2afed7d2237cf6d99d42f5a15bd690958e65f66397237131bdda23cf62e0909b3456ae5a3247a2b37a52fb2ceb2b5470206f2d76a245ebcaefc
|
data/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Live Reload with DOM Morphing for Rack applications using Server-Sent Events (SS
|
|
|
10
10
|
<picture>
|
|
11
11
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Rails-Designer/mata/HEAD/.github/logo-dark.svg">
|
|
12
12
|
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Rails-Designer/mata/HEAD/.github/logo-light.svg">
|
|
13
|
+
|
|
13
14
|
<img alt="Rails Designer" src="https://raw.githubusercontent.com/Rails-Designer/mata/HEAD/.github/logo-light.svg" width="240" style="max-width: 100%;">
|
|
14
15
|
</picture>
|
|
15
16
|
</a>
|
|
@@ -36,20 +37,6 @@ config.middleware.insert_before(
|
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
### Other rack-based apps
|
|
40
|
-
```ruby
|
|
41
|
-
# config.ru
|
|
42
|
-
require "mata"
|
|
43
|
-
|
|
44
|
-
use Mata, watch: %w[views assets], skip: %w[views/tmp]
|
|
45
|
-
|
|
46
|
-
run YourApp
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
> [!NOTE]
|
|
50
|
-
> Do share your snippets on how to add Mata into your specific Rack-based apps 💙
|
|
51
|
-
|
|
52
|
-
|
|
53
40
|
### Hanami
|
|
54
41
|
```ruby
|
|
55
42
|
# config.ru
|
|
@@ -87,6 +74,31 @@ route do |r|
|
|
|
87
74
|
end
|
|
88
75
|
```
|
|
89
76
|
|
|
77
|
+
### Other rack-based apps
|
|
78
|
+
```ruby
|
|
79
|
+
# config.ru
|
|
80
|
+
require "mata"
|
|
81
|
+
|
|
82
|
+
use Mata, watch: %w[views assets], skip: %w[views/tmp]
|
|
83
|
+
|
|
84
|
+
run YourApp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Options
|
|
89
|
+
|
|
90
|
+
All options are optional.
|
|
91
|
+
|
|
92
|
+
| Option | Default | Type | Description |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `watch` | `[]` | Array of String | Directories to watch for changes |
|
|
95
|
+
| `skip` | `[]` | Array of String/Regexp | Paths to ignore |
|
|
96
|
+
| `debounce` | `300` | Integer | Debounce interval in milliseconds before broadcasting changes |
|
|
97
|
+
| `retry` | not set | Integer | SSE retry interval in milliseconds (not set = browser default ~3s, maximum clamped at 30s) |
|
|
98
|
+
| `log_level` | `:verbose` | Symbol | `:verbose` shows all console messages; `:silent` suppresses them in the browser console (`[Mata] Connected with DOM morphing`, etc.) |
|
|
99
|
+
| `idiomorph_options` | `nil` | String | A raw JS object literal passed **verbatim** as the `options` argument to [`Idiomorph.morph`](https://github.com/bigskysoftware/idiomorph#options), fully replacing Mata's built-in defaults. Example: `idiomorph_options: '{ morphStyle: "innerHTML" }'` |
|
|
100
|
+
|
|
101
|
+
|
|
90
102
|
## Used in
|
|
91
103
|
|
|
92
104
|
This gem powers live reloading in [Perron](https://github.com/rails-designer/perron), a Rails-based static site generator. It can be enabled with `config.live_reload = true` in your Perron initializer.
|
data/lib/mata/agent.rb
CHANGED
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
class Mata
|
|
4
4
|
class Agent
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
@retry = options[:retry]
|
|
7
|
+
@log_level = options[:log_level]
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
def insert(status, headers, body)
|
|
6
11
|
return [status, headers, body] unless html_response?(headers)
|
|
7
12
|
|
|
8
13
|
content = extract(body)
|
|
9
14
|
return [status, headers, [content]] unless content.include?("</head>")
|
|
10
15
|
|
|
11
|
-
script_tag = '<script src="/__mata/client.js"
|
|
16
|
+
script_tag = '<script src="/__mata/client.js"'
|
|
17
|
+
script_tag += %( data-mata-retry="#{@retry}") if @retry
|
|
18
|
+
script_tag += %( data-mata-log-level="#{@log_level}") if @log_level
|
|
19
|
+
script_tag += "></script>"
|
|
12
20
|
modified_content = content.sub("</head>", "#{script_tag}\n</head>")
|
|
13
21
|
|
|
14
22
|
headers["Content-Length"] = modified_content.bytesize.to_s if headers["Content-Length"]
|
data/lib/mata/broadcaster.rb
CHANGED
|
@@ -2,9 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
class Mata
|
|
4
4
|
class Broadcaster
|
|
5
|
-
|
|
5
|
+
DEFAULT_IDIOMORPH_OPTIONS = <<~JS
|
|
6
|
+
{
|
|
7
|
+
ignoreActiveValue: true,
|
|
8
|
+
callbacks: {
|
|
9
|
+
beforeNodeMorphed: function(oldNode, _) {
|
|
10
|
+
if (oldNode.tagName && oldNode.tagName.includes("-")) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
JS
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
6
21
|
@clients = []
|
|
7
22
|
@clients_mutex = Mutex.new
|
|
23
|
+
@retry = options[:retry]
|
|
24
|
+
@idiomorph_options = options[:idiomorph_options]
|
|
8
25
|
@cleanup_thread = cleanup_periodically
|
|
9
26
|
end
|
|
10
27
|
|
|
@@ -26,6 +43,7 @@ class Mata
|
|
|
26
43
|
end
|
|
27
44
|
|
|
28
45
|
begin
|
|
46
|
+
stream << "retry: #{@retry}\n\n" if @retry
|
|
29
47
|
stream << "data: {\"type\":\"connected\"}\n\n"
|
|
30
48
|
rescue
|
|
31
49
|
@clients_mutex.synchronize { @clients.delete(stream) }
|
|
@@ -34,9 +52,13 @@ class Mata
|
|
|
34
52
|
end
|
|
35
53
|
|
|
36
54
|
def deliver_payload
|
|
55
|
+
raise ArgumentError, "idiomorph_options must be a String containing a raw JS object literal" if @idiomorph_options && !@idiomorph_options.is_a?(String)
|
|
56
|
+
|
|
37
57
|
idiomorph_js = File.read(File.join(__dir__, "idiomorph.min.js"))
|
|
38
58
|
client_js = File.read(File.join(__dir__, "client.js"))
|
|
39
59
|
|
|
60
|
+
client_js = client_js.gsub("__MATA_IDIOMORPH_OPTIONS__", @idiomorph_options || DEFAULT_IDIOMORPH_OPTIONS)
|
|
61
|
+
|
|
40
62
|
script = "#{idiomorph_js}\n\n#{client_js}"
|
|
41
63
|
|
|
42
64
|
[200, {"content-type" => "application/javascript"}, [script]]
|
|
@@ -46,15 +68,13 @@ class Mata
|
|
|
46
68
|
clients_copy = @clients_mutex.synchronize { @clients.dup }
|
|
47
69
|
return if clients_copy.empty?
|
|
48
70
|
|
|
49
|
-
files
|
|
50
|
-
|
|
51
|
-
message = "data: #{event_data.to_json}\n\n"
|
|
71
|
+
event_data = {type: "reload", files: files}
|
|
72
|
+
message = "data: #{event_data.to_json}\n\n"
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
74
|
+
clients_copy.each do |stream|
|
|
75
|
+
stream << message
|
|
76
|
+
rescue
|
|
77
|
+
@clients_mutex.synchronize { @clients.delete(stream) }
|
|
58
78
|
end
|
|
59
79
|
end
|
|
60
80
|
|
data/lib/mata/client.js
CHANGED
|
@@ -2,26 +2,61 @@
|
|
|
2
2
|
initMata();
|
|
3
3
|
|
|
4
4
|
function initMata() {
|
|
5
|
-
const
|
|
5
|
+
const retryDelayBase = parseInt(document.currentScript.dataset.mataRetry) || 1000;
|
|
6
|
+
const maximumRetryDelay = 30000;
|
|
7
|
+
let retryDelay = retryDelayBase;
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
const data = JSON.parse(event.data);
|
|
9
|
+
const silent = (document.currentScript.dataset.mataLogLevel || "verbose").toLowerCase() === "silent";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function log(...arguments) {
|
|
12
|
+
if (!silent) console.log("[Mata]", ...arguments);
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
function error(...arguments) {
|
|
16
|
+
if (!silent) console.error("[Mata]", ...arguments);
|
|
17
|
+
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
connect();
|
|
20
|
+
|
|
21
|
+
function connect() {
|
|
22
|
+
const eventSource = new EventSource("/__mata/events");
|
|
23
|
+
|
|
24
|
+
eventSource.onmessage = function(event) {
|
|
25
|
+
const data = JSON.parse(event.data);
|
|
26
|
+
|
|
27
|
+
switch(data.type) {
|
|
28
|
+
case "reload":
|
|
29
|
+
if (data.files?.some((file) => file.endsWith(".js"))) {
|
|
30
|
+
log("JS file changed, reloading…");
|
|
31
|
+
|
|
32
|
+
window.location.reload();
|
|
33
|
+
} else {
|
|
34
|
+
morphPage();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
break;
|
|
38
|
+
case "connected":
|
|
39
|
+
retryDelay = retryDelayBase;
|
|
40
|
+
|
|
41
|
+
log("Connected with DOM morphing");
|
|
42
|
+
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
eventSource.onerror = function() {
|
|
48
|
+
eventSource.close();
|
|
49
|
+
|
|
50
|
+
log(`Connection lost, retrying in ${retryDelay}ms…`);
|
|
51
|
+
|
|
52
|
+
setTimeout(connect, retryDelay);
|
|
53
|
+
retryDelay = Math.min(retryDelay * 2, maximumRetryDelay);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
21
56
|
|
|
22
57
|
async function morphPage() {
|
|
23
58
|
try {
|
|
24
|
-
|
|
59
|
+
log("Fetching updated page…");
|
|
25
60
|
const response = await fetch(window.location.href);
|
|
26
61
|
|
|
27
62
|
if (!response.ok) {
|
|
@@ -36,30 +71,15 @@
|
|
|
36
71
|
throw new Error("Invalid HTML response");
|
|
37
72
|
}
|
|
38
73
|
|
|
39
|
-
Idiomorph.morph(document.documentElement, updatedDocument.documentElement,
|
|
40
|
-
ignoreActiveValue: true,
|
|
41
|
-
callbacks: {
|
|
42
|
-
beforeNodeMorphed: function(oldNode, _) {
|
|
43
|
-
if (oldNode.tagName && oldNode.tagName.includes("-")) { // skip custom elements
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
74
|
+
Idiomorph.morph(document.documentElement, updatedDocument.documentElement, __MATA_IDIOMORPH_OPTIONS__);
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
log("Page morphed successfully");
|
|
53
77
|
} catch (error) {
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
error("Morph failed:", error.message);
|
|
79
|
+
log("Falling back to full reload");
|
|
56
80
|
|
|
57
81
|
window.location.reload();
|
|
58
82
|
}
|
|
59
83
|
}
|
|
60
|
-
|
|
61
|
-
eventSource.onerror = function() {
|
|
62
|
-
console.log("[Mata] Connection lost, retrying…");
|
|
63
|
-
};
|
|
64
84
|
}
|
|
65
85
|
})();
|
data/lib/mata/version.rb
CHANGED
data/lib/mata/watch_tower.rb
CHANGED
|
@@ -5,6 +5,7 @@ class Mata
|
|
|
5
5
|
def initialize(options)
|
|
6
6
|
@watch_paths = Array(options[:watch] || [])
|
|
7
7
|
@skip_paths = skipped_patterns(options[:skip] || options[:ignore] || [])
|
|
8
|
+
@debounce = options[:debounce] || 300
|
|
8
9
|
@on_change = nil
|
|
9
10
|
@listener = nil
|
|
10
11
|
|
|
@@ -42,8 +43,9 @@ class Mata
|
|
|
42
43
|
@last_change_time = Time.now
|
|
43
44
|
|
|
44
45
|
Thread.new do
|
|
45
|
-
sleep 0
|
|
46
|
-
|
|
46
|
+
sleep(@debounce / 1000.0)
|
|
47
|
+
|
|
48
|
+
if Time.now - @last_change_time >= (@debounce / 1000.0)
|
|
47
49
|
@on_change&.call(modified + added + removed)
|
|
48
50
|
end
|
|
49
51
|
end
|
data/lib/mata.rb
CHANGED
|
@@ -14,8 +14,8 @@ class Mata
|
|
|
14
14
|
return if ENV["MATA_DISABLED"] == "true"
|
|
15
15
|
|
|
16
16
|
@watch_tower = WatchTower.new(options)
|
|
17
|
-
@broadcaster = Broadcaster.new
|
|
18
|
-
@agent = Agent.new
|
|
17
|
+
@broadcaster = Broadcaster.new(options)
|
|
18
|
+
@agent = Agent.new(options)
|
|
19
19
|
|
|
20
20
|
@watch_tower.on_change { |files| @broadcaster.broadcast_to_all(files) }
|
|
21
21
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mata
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rails Designer
|
|
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
73
|
version: '0'
|
|
74
74
|
requirements: []
|
|
75
|
-
rubygems_version: 4.0.
|
|
75
|
+
rubygems_version: 4.0.22
|
|
76
76
|
specification_version: 4
|
|
77
77
|
summary: Hot module reloading for Rack applications
|
|
78
78
|
test_files: []
|