mata 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e57a04054693fd5c3b092f6c028d8cac79e9c374c043a6c29c34bda658836060
4
- data.tar.gz: fb9c2cdb01e636273c4e13a5e55321dd5da252bda4a0cd3ed1942565336c2814
3
+ metadata.gz: 886a7db4fb956cd1191b502d73b582569e3dc680ca3c375fdf42296fb5d6197b
4
+ data.tar.gz: d87cd56304b9eb144d3b9a997598f09df16d94292c9724cf3312f0e9b6298411
5
5
  SHA512:
6
- metadata.gz: d5736a691cd06abf3ec90a561f3ebe5f88c5e88d5445b67ae21a32ff3f39757f49e0a88f3171dbed10afb587018ffc43980a110cf861541a660662d3222a05fe
7
- data.tar.gz: b5ab2d3ebb6496ffee7f113d2f143b91170ec9f7baa9f4b1257090fe3bc20f1ed4ef2d9858df6164a35dd3eca3f19d0f69791b563acbe2109d841cd67d923243
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"></script>'
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"]
@@ -26,7 +34,7 @@ class Mata
26
34
 
27
35
  def extract(body)
28
36
  if body.respond_to?(:each)
29
- body.each.to_a.join
37
+ [].tap { |parts| body.each { |part| parts << part } }.join
30
38
  else
31
39
  body.to_s
32
40
  end
@@ -2,9 +2,26 @@
2
2
 
3
3
  class Mata
4
4
  class Broadcaster
5
- def initialize
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,13 +43,8 @@ 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
-
31
- Thread.new do
32
- loop { sleep 30 }
33
- rescue
34
- @clients_mutex.synchronize { @clients.delete(stream) }
35
- end
36
48
  rescue
37
49
  @clients_mutex.synchronize { @clients.delete(stream) }
38
50
  end
@@ -40,9 +52,13 @@ class Mata
40
52
  end
41
53
 
42
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
+
43
57
  idiomorph_js = File.read(File.join(__dir__, "idiomorph.min.js"))
44
58
  client_js = File.read(File.join(__dir__, "client.js"))
45
59
 
60
+ client_js = client_js.gsub("__MATA_IDIOMORPH_OPTIONS__", @idiomorph_options || DEFAULT_IDIOMORPH_OPTIONS)
61
+
46
62
  script = "#{idiomorph_js}\n\n#{client_js}"
47
63
 
48
64
  [200, {"content-type" => "application/javascript"}, [script]]
@@ -52,15 +68,13 @@ class Mata
52
68
  clients_copy = @clients_mutex.synchronize { @clients.dup }
53
69
  return if clients_copy.empty?
54
70
 
55
- files.each do |file|
56
- event_data = {type: "reload"}
57
- message = "data: #{event_data.to_json}\n\n"
71
+ event_data = {type: "reload", files: files}
72
+ message = "data: #{event_data.to_json}\n\n"
58
73
 
59
- clients_copy.each do |stream|
60
- stream << message
61
- rescue
62
- @clients_mutex.synchronize { @clients.delete(stream) }
63
- end
74
+ clients_copy.each do |stream|
75
+ stream << message
76
+ rescue
77
+ @clients_mutex.synchronize { @clients.delete(stream) }
64
78
  end
65
79
  end
66
80
 
@@ -87,6 +101,7 @@ class Mata
87
101
  @clients_mutex.synchronize do
88
102
  @clients.reject! do |client|
89
103
  client << ""
104
+
90
105
  false
91
106
  rescue
92
107
  true
data/lib/mata/client.js CHANGED
@@ -2,26 +2,61 @@
2
2
  initMata();
3
3
 
4
4
  function initMata() {
5
- const eventSource = new EventSource("/__mata/events");
5
+ const retryDelayBase = parseInt(document.currentScript.dataset.mataRetry) || 1000;
6
+ const maximumRetryDelay = 30000;
7
+ let retryDelay = retryDelayBase;
6
8
 
7
- eventSource.onmessage = function(event) {
8
- const data = JSON.parse(event.data);
9
+ const silent = (document.currentScript.dataset.mataLogLevel || "verbose").toLowerCase() === "silent";
9
10
 
10
- switch(data.type) {
11
- case "reload":
12
- morphPage();
11
+ function log(...arguments) {
12
+ if (!silent) console.log("[Mata]", ...arguments);
13
+ }
13
14
 
14
- break;
15
- case "connected":
16
- console.log("[Mata] Connected with DOM morphing");
15
+ function error(...arguments) {
16
+ if (!silent) console.error("[Mata]", ...arguments);
17
+ }
17
18
 
18
- break;
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
- console.log("[Mata] Fetching updated page…");
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
- console.log("[Mata] Page morphed successfully");
76
+ log("Page morphed successfully");
53
77
  } catch (error) {
54
- console.error("[Mata] Morph failed:", error.message);
55
- console.log("[Mata] Falling back to full reload");
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
@@ -1,3 +1,3 @@
1
1
  class Mata
2
- VERSION = "0.9.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -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.15
46
- if Time.now - @last_change_time >= 0.15
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.9.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer
@@ -52,11 +52,11 @@ files:
52
52
  - lib/mata/idiomorph.min.js
53
53
  - lib/mata/version.rb
54
54
  - lib/mata/watch_tower.rb
55
- homepage: https://railsdesigner.com/mata/
55
+ homepage: https://railsdesigner.com/open-source/mata/
56
56
  licenses:
57
57
  - MIT
58
58
  metadata:
59
- homepage_uri: https://railsdesigner.com/mata/
59
+ homepage_uri: https://railsdesigner.com/open-source/mata/
60
60
  source_code_uri: https://github.com/Rails-Designer/mata
61
61
  rdoc_options: []
62
62
  require_paths:
@@ -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.10
75
+ rubygems_version: 4.0.22
76
76
  specification_version: 4
77
77
  summary: Hot module reloading for Rack applications
78
78
  test_files: []