hotwire-livereload 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e80daa28376d668cb49288182ab69de36a544c0367deab64359e99d1b1c5cb8e
4
+ data.tar.gz: ba7057fa9ce97ea7801b5c993ff3172ff0fb0ba7820474fb0be467c01900d333
5
+ SHA512:
6
+ metadata.gz: 4eb10a968fa542b584bdd0184be9ecf6740b2633d2481f61df65681b7e476ae8273770d58b9a3a56d05baf6c2773d9fb9e9508263e179f3954d9fe86604b7d18
7
+ data.tar.gz: 34a454aa9316357d37b89754394457b06ad5c3cf4b694bee5ce0e954d072b8f535341811c3d613a0f4f5114463e9e0239a6c2db98c3005eedb47db96af4ec803
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Kirill Platonov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Hotwire::Livereload
2
+
3
+ Automatically reload Hotwire Turbo when "view" files are modified.
4
+
5
+ https://user-images.githubusercontent.com/839922/131983979-afd0bcc7-86e8-4c53-9758-3bf762dbb16a.mp4
6
+
7
+ ## Installation
8
+
9
+ The JavaScript for Hotwire::Livereload is installed via asset pipeline, which is included with this gem.
10
+
11
+ 1. Add `hotwire-livereload` gem to your Gemfile: `gem 'hotwire-livereload'`
12
+ 2. Run `./bin/bundle install`
13
+ 3. Run `./bin/rails hotwire_livereload:install`
14
+
15
+ ## Development
16
+
17
+ To get started:
18
+
19
+ 1. Run `npm install`
20
+ 2. Run `npm run watch`
21
+
22
+ ## License
23
+
24
+ Hotwire::Livereload is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,619 @@
1
+ (() => {
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __reExport = (target, module, desc) => {
13
+ if (module && typeof module === "object" || typeof module === "function") {
14
+ for (let key of __getOwnPropNames(module))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, { get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable });
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module) => {
21
+ return __reExport(__markAsModule(__defProp(module != null ? __create(__getProtoOf(module)) : {}, "default", module && module.__esModule && "default" in module ? { get: () => module.default, enumerable: true } : { value: module, enumerable: true })), module);
22
+ };
23
+
24
+ // node_modules/@rails/actioncable/app/assets/javascripts/action_cable.js
25
+ var require_action_cable = __commonJS({
26
+ "node_modules/@rails/actioncable/app/assets/javascripts/action_cable.js"(exports, module) {
27
+ (function(global, factory) {
28
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : factory(global.ActionCable = {});
29
+ })(exports, function(exports2) {
30
+ "use strict";
31
+ var adapters = {
32
+ logger: self.console,
33
+ WebSocket: self.WebSocket
34
+ };
35
+ var logger = {
36
+ log: function log() {
37
+ if (this.enabled) {
38
+ var _adapters$logger;
39
+ for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) {
40
+ messages[_key] = arguments[_key];
41
+ }
42
+ messages.push(Date.now());
43
+ (_adapters$logger = adapters.logger).log.apply(_adapters$logger, ["[ActionCable]"].concat(messages));
44
+ }
45
+ }
46
+ };
47
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) {
48
+ return typeof obj;
49
+ } : function(obj) {
50
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
51
+ };
52
+ var classCallCheck = function(instance, Constructor) {
53
+ if (!(instance instanceof Constructor)) {
54
+ throw new TypeError("Cannot call a class as a function");
55
+ }
56
+ };
57
+ var createClass = function() {
58
+ function defineProperties(target, props) {
59
+ for (var i = 0; i < props.length; i++) {
60
+ var descriptor = props[i];
61
+ descriptor.enumerable = descriptor.enumerable || false;
62
+ descriptor.configurable = true;
63
+ if ("value" in descriptor)
64
+ descriptor.writable = true;
65
+ Object.defineProperty(target, descriptor.key, descriptor);
66
+ }
67
+ }
68
+ return function(Constructor, protoProps, staticProps) {
69
+ if (protoProps)
70
+ defineProperties(Constructor.prototype, protoProps);
71
+ if (staticProps)
72
+ defineProperties(Constructor, staticProps);
73
+ return Constructor;
74
+ };
75
+ }();
76
+ var now = function now2() {
77
+ return new Date().getTime();
78
+ };
79
+ var secondsSince = function secondsSince2(time) {
80
+ return (now() - time) / 1e3;
81
+ };
82
+ var clamp = function clamp2(number, min, max) {
83
+ return Math.max(min, Math.min(max, number));
84
+ };
85
+ var ConnectionMonitor = function() {
86
+ function ConnectionMonitor2(connection) {
87
+ classCallCheck(this, ConnectionMonitor2);
88
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
89
+ this.connection = connection;
90
+ this.reconnectAttempts = 0;
91
+ }
92
+ ConnectionMonitor2.prototype.start = function start() {
93
+ if (!this.isRunning()) {
94
+ this.startedAt = now();
95
+ delete this.stoppedAt;
96
+ this.startPolling();
97
+ addEventListener("visibilitychange", this.visibilityDidChange);
98
+ logger.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms");
99
+ }
100
+ };
101
+ ConnectionMonitor2.prototype.stop = function stop() {
102
+ if (this.isRunning()) {
103
+ this.stoppedAt = now();
104
+ this.stopPolling();
105
+ removeEventListener("visibilitychange", this.visibilityDidChange);
106
+ logger.log("ConnectionMonitor stopped");
107
+ }
108
+ };
109
+ ConnectionMonitor2.prototype.isRunning = function isRunning() {
110
+ return this.startedAt && !this.stoppedAt;
111
+ };
112
+ ConnectionMonitor2.prototype.recordPing = function recordPing() {
113
+ this.pingedAt = now();
114
+ };
115
+ ConnectionMonitor2.prototype.recordConnect = function recordConnect() {
116
+ this.reconnectAttempts = 0;
117
+ this.recordPing();
118
+ delete this.disconnectedAt;
119
+ logger.log("ConnectionMonitor recorded connect");
120
+ };
121
+ ConnectionMonitor2.prototype.recordDisconnect = function recordDisconnect() {
122
+ this.disconnectedAt = now();
123
+ logger.log("ConnectionMonitor recorded disconnect");
124
+ };
125
+ ConnectionMonitor2.prototype.startPolling = function startPolling() {
126
+ this.stopPolling();
127
+ this.poll();
128
+ };
129
+ ConnectionMonitor2.prototype.stopPolling = function stopPolling() {
130
+ clearTimeout(this.pollTimeout);
131
+ };
132
+ ConnectionMonitor2.prototype.poll = function poll() {
133
+ var _this = this;
134
+ this.pollTimeout = setTimeout(function() {
135
+ _this.reconnectIfStale();
136
+ _this.poll();
137
+ }, this.getPollInterval());
138
+ };
139
+ ConnectionMonitor2.prototype.getPollInterval = function getPollInterval() {
140
+ var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max, multiplier = _constructor$pollInte.multiplier;
141
+ var interval = multiplier * Math.log(this.reconnectAttempts + 1);
142
+ return Math.round(clamp(interval, min, max) * 1e3);
143
+ };
144
+ ConnectionMonitor2.prototype.reconnectIfStale = function reconnectIfStale() {
145
+ if (this.connectionIsStale()) {
146
+ logger.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + this.getPollInterval() + " ms, time disconnected = " + secondsSince(this.disconnectedAt) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
147
+ this.reconnectAttempts++;
148
+ if (this.disconnectedRecently()) {
149
+ logger.log("ConnectionMonitor skipping reopening recent disconnect");
150
+ } else {
151
+ logger.log("ConnectionMonitor reopening");
152
+ this.connection.reopen();
153
+ }
154
+ }
155
+ };
156
+ ConnectionMonitor2.prototype.connectionIsStale = function connectionIsStale() {
157
+ return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold;
158
+ };
159
+ ConnectionMonitor2.prototype.disconnectedRecently = function disconnectedRecently() {
160
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
161
+ };
162
+ ConnectionMonitor2.prototype.visibilityDidChange = function visibilityDidChange() {
163
+ var _this2 = this;
164
+ if (document.visibilityState === "visible") {
165
+ setTimeout(function() {
166
+ if (_this2.connectionIsStale() || !_this2.connection.isOpen()) {
167
+ logger.log("ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = " + document.visibilityState);
168
+ _this2.connection.reopen();
169
+ }
170
+ }, 200);
171
+ }
172
+ };
173
+ return ConnectionMonitor2;
174
+ }();
175
+ ConnectionMonitor.pollInterval = {
176
+ min: 3,
177
+ max: 30,
178
+ multiplier: 5
179
+ };
180
+ ConnectionMonitor.staleThreshold = 6;
181
+ var INTERNAL = {
182
+ message_types: {
183
+ welcome: "welcome",
184
+ disconnect: "disconnect",
185
+ ping: "ping",
186
+ confirmation: "confirm_subscription",
187
+ rejection: "reject_subscription"
188
+ },
189
+ disconnect_reasons: {
190
+ unauthorized: "unauthorized",
191
+ invalid_request: "invalid_request",
192
+ server_restart: "server_restart"
193
+ },
194
+ default_mount_path: "/cable",
195
+ protocols: ["actioncable-v1-json", "actioncable-unsupported"]
196
+ };
197
+ var message_types = INTERNAL.message_types, protocols = INTERNAL.protocols;
198
+ var supportedProtocols = protocols.slice(0, protocols.length - 1);
199
+ var indexOf = [].indexOf;
200
+ var Connection = function() {
201
+ function Connection2(consumer2) {
202
+ classCallCheck(this, Connection2);
203
+ this.open = this.open.bind(this);
204
+ this.consumer = consumer2;
205
+ this.subscriptions = this.consumer.subscriptions;
206
+ this.monitor = new ConnectionMonitor(this);
207
+ this.disconnected = true;
208
+ }
209
+ Connection2.prototype.send = function send(data) {
210
+ if (this.isOpen()) {
211
+ this.webSocket.send(JSON.stringify(data));
212
+ return true;
213
+ } else {
214
+ return false;
215
+ }
216
+ };
217
+ Connection2.prototype.open = function open() {
218
+ if (this.isActive()) {
219
+ logger.log("Attempted to open WebSocket, but existing socket is " + this.getState());
220
+ return false;
221
+ } else {
222
+ logger.log("Opening WebSocket, current state is " + this.getState() + ", subprotocols: " + protocols);
223
+ if (this.webSocket) {
224
+ this.uninstallEventHandlers();
225
+ }
226
+ this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
227
+ this.installEventHandlers();
228
+ this.monitor.start();
229
+ return true;
230
+ }
231
+ };
232
+ Connection2.prototype.close = function close() {
233
+ var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {
234
+ allowReconnect: true
235
+ }, allowReconnect = _ref.allowReconnect;
236
+ if (!allowReconnect) {
237
+ this.monitor.stop();
238
+ }
239
+ if (this.isActive()) {
240
+ return this.webSocket.close();
241
+ }
242
+ };
243
+ Connection2.prototype.reopen = function reopen() {
244
+ logger.log("Reopening WebSocket, current state is " + this.getState());
245
+ if (this.isActive()) {
246
+ try {
247
+ return this.close();
248
+ } catch (error) {
249
+ logger.log("Failed to reopen WebSocket", error);
250
+ } finally {
251
+ logger.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
252
+ setTimeout(this.open, this.constructor.reopenDelay);
253
+ }
254
+ } else {
255
+ return this.open();
256
+ }
257
+ };
258
+ Connection2.prototype.getProtocol = function getProtocol() {
259
+ if (this.webSocket) {
260
+ return this.webSocket.protocol;
261
+ }
262
+ };
263
+ Connection2.prototype.isOpen = function isOpen() {
264
+ return this.isState("open");
265
+ };
266
+ Connection2.prototype.isActive = function isActive() {
267
+ return this.isState("open", "connecting");
268
+ };
269
+ Connection2.prototype.isProtocolSupported = function isProtocolSupported() {
270
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
271
+ };
272
+ Connection2.prototype.isState = function isState() {
273
+ for (var _len = arguments.length, states = Array(_len), _key = 0; _key < _len; _key++) {
274
+ states[_key] = arguments[_key];
275
+ }
276
+ return indexOf.call(states, this.getState()) >= 0;
277
+ };
278
+ Connection2.prototype.getState = function getState() {
279
+ if (this.webSocket) {
280
+ for (var state in adapters.WebSocket) {
281
+ if (adapters.WebSocket[state] === this.webSocket.readyState) {
282
+ return state.toLowerCase();
283
+ }
284
+ }
285
+ }
286
+ return null;
287
+ };
288
+ Connection2.prototype.installEventHandlers = function installEventHandlers() {
289
+ for (var eventName in this.events) {
290
+ var handler = this.events[eventName].bind(this);
291
+ this.webSocket["on" + eventName] = handler;
292
+ }
293
+ };
294
+ Connection2.prototype.uninstallEventHandlers = function uninstallEventHandlers() {
295
+ for (var eventName in this.events) {
296
+ this.webSocket["on" + eventName] = function() {
297
+ };
298
+ }
299
+ };
300
+ return Connection2;
301
+ }();
302
+ Connection.reopenDelay = 500;
303
+ Connection.prototype.events = {
304
+ message: function message(event) {
305
+ if (!this.isProtocolSupported()) {
306
+ return;
307
+ }
308
+ var _JSON$parse = JSON.parse(event.data), identifier = _JSON$parse.identifier, message2 = _JSON$parse.message, reason = _JSON$parse.reason, reconnect = _JSON$parse.reconnect, type = _JSON$parse.type;
309
+ switch (type) {
310
+ case message_types.welcome:
311
+ this.monitor.recordConnect();
312
+ return this.subscriptions.reload();
313
+ case message_types.disconnect:
314
+ logger.log("Disconnecting. Reason: " + reason);
315
+ return this.close({
316
+ allowReconnect: reconnect
317
+ });
318
+ case message_types.ping:
319
+ return this.monitor.recordPing();
320
+ case message_types.confirmation:
321
+ return this.subscriptions.notify(identifier, "connected");
322
+ case message_types.rejection:
323
+ return this.subscriptions.reject(identifier);
324
+ default:
325
+ return this.subscriptions.notify(identifier, "received", message2);
326
+ }
327
+ },
328
+ open: function open() {
329
+ logger.log("WebSocket onopen event, using '" + this.getProtocol() + "' subprotocol");
330
+ this.disconnected = false;
331
+ if (!this.isProtocolSupported()) {
332
+ logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
333
+ return this.close({
334
+ allowReconnect: false
335
+ });
336
+ }
337
+ },
338
+ close: function close(event) {
339
+ logger.log("WebSocket onclose event");
340
+ if (this.disconnected) {
341
+ return;
342
+ }
343
+ this.disconnected = true;
344
+ this.monitor.recordDisconnect();
345
+ return this.subscriptions.notifyAll("disconnected", {
346
+ willAttemptReconnect: this.monitor.isRunning()
347
+ });
348
+ },
349
+ error: function error() {
350
+ logger.log("WebSocket onerror event");
351
+ }
352
+ };
353
+ var extend = function extend2(object, properties) {
354
+ if (properties != null) {
355
+ for (var key in properties) {
356
+ var value = properties[key];
357
+ object[key] = value;
358
+ }
359
+ }
360
+ return object;
361
+ };
362
+ var Subscription = function() {
363
+ function Subscription2(consumer2) {
364
+ var params = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
365
+ var mixin = arguments[2];
366
+ classCallCheck(this, Subscription2);
367
+ this.consumer = consumer2;
368
+ this.identifier = JSON.stringify(params);
369
+ extend(this, mixin);
370
+ }
371
+ Subscription2.prototype.perform = function perform(action) {
372
+ var data = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
373
+ data.action = action;
374
+ return this.send(data);
375
+ };
376
+ Subscription2.prototype.send = function send(data) {
377
+ return this.consumer.send({
378
+ command: "message",
379
+ identifier: this.identifier,
380
+ data: JSON.stringify(data)
381
+ });
382
+ };
383
+ Subscription2.prototype.unsubscribe = function unsubscribe() {
384
+ return this.consumer.subscriptions.remove(this);
385
+ };
386
+ return Subscription2;
387
+ }();
388
+ var Subscriptions = function() {
389
+ function Subscriptions2(consumer2) {
390
+ classCallCheck(this, Subscriptions2);
391
+ this.consumer = consumer2;
392
+ this.subscriptions = [];
393
+ }
394
+ Subscriptions2.prototype.create = function create(channelName, mixin) {
395
+ var channel = channelName;
396
+ var params = (typeof channel === "undefined" ? "undefined" : _typeof(channel)) === "object" ? channel : {
397
+ channel
398
+ };
399
+ var subscription = new Subscription(this.consumer, params, mixin);
400
+ return this.add(subscription);
401
+ };
402
+ Subscriptions2.prototype.add = function add(subscription) {
403
+ this.subscriptions.push(subscription);
404
+ this.consumer.ensureActiveConnection();
405
+ this.notify(subscription, "initialized");
406
+ this.sendCommand(subscription, "subscribe");
407
+ return subscription;
408
+ };
409
+ Subscriptions2.prototype.remove = function remove(subscription) {
410
+ this.forget(subscription);
411
+ if (!this.findAll(subscription.identifier).length) {
412
+ this.sendCommand(subscription, "unsubscribe");
413
+ }
414
+ return subscription;
415
+ };
416
+ Subscriptions2.prototype.reject = function reject(identifier) {
417
+ var _this = this;
418
+ return this.findAll(identifier).map(function(subscription) {
419
+ _this.forget(subscription);
420
+ _this.notify(subscription, "rejected");
421
+ return subscription;
422
+ });
423
+ };
424
+ Subscriptions2.prototype.forget = function forget(subscription) {
425
+ this.subscriptions = this.subscriptions.filter(function(s) {
426
+ return s !== subscription;
427
+ });
428
+ return subscription;
429
+ };
430
+ Subscriptions2.prototype.findAll = function findAll(identifier) {
431
+ return this.subscriptions.filter(function(s) {
432
+ return s.identifier === identifier;
433
+ });
434
+ };
435
+ Subscriptions2.prototype.reload = function reload() {
436
+ var _this2 = this;
437
+ return this.subscriptions.map(function(subscription) {
438
+ return _this2.sendCommand(subscription, "subscribe");
439
+ });
440
+ };
441
+ Subscriptions2.prototype.notifyAll = function notifyAll(callbackName) {
442
+ var _this3 = this;
443
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
444
+ args[_key - 1] = arguments[_key];
445
+ }
446
+ return this.subscriptions.map(function(subscription) {
447
+ return _this3.notify.apply(_this3, [subscription, callbackName].concat(args));
448
+ });
449
+ };
450
+ Subscriptions2.prototype.notify = function notify(subscription, callbackName) {
451
+ for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
452
+ args[_key2 - 2] = arguments[_key2];
453
+ }
454
+ var subscriptions = void 0;
455
+ if (typeof subscription === "string") {
456
+ subscriptions = this.findAll(subscription);
457
+ } else {
458
+ subscriptions = [subscription];
459
+ }
460
+ return subscriptions.map(function(subscription2) {
461
+ return typeof subscription2[callbackName] === "function" ? subscription2[callbackName].apply(subscription2, args) : void 0;
462
+ });
463
+ };
464
+ Subscriptions2.prototype.sendCommand = function sendCommand(subscription, command) {
465
+ var identifier = subscription.identifier;
466
+ return this.consumer.send({
467
+ command,
468
+ identifier
469
+ });
470
+ };
471
+ return Subscriptions2;
472
+ }();
473
+ var Consumer = function() {
474
+ function Consumer2(url) {
475
+ classCallCheck(this, Consumer2);
476
+ this._url = url;
477
+ this.subscriptions = new Subscriptions(this);
478
+ this.connection = new Connection(this);
479
+ }
480
+ Consumer2.prototype.send = function send(data) {
481
+ return this.connection.send(data);
482
+ };
483
+ Consumer2.prototype.connect = function connect() {
484
+ return this.connection.open();
485
+ };
486
+ Consumer2.prototype.disconnect = function disconnect() {
487
+ return this.connection.close({
488
+ allowReconnect: false
489
+ });
490
+ };
491
+ Consumer2.prototype.ensureActiveConnection = function ensureActiveConnection() {
492
+ if (!this.connection.isActive()) {
493
+ return this.connection.open();
494
+ }
495
+ };
496
+ createClass(Consumer2, [{
497
+ key: "url",
498
+ get: function get$$1() {
499
+ return createWebSocketURL(this._url);
500
+ }
501
+ }]);
502
+ return Consumer2;
503
+ }();
504
+ function createWebSocketURL(url) {
505
+ if (typeof url === "function") {
506
+ url = url();
507
+ }
508
+ if (url && !/^wss?:/i.test(url)) {
509
+ var a = document.createElement("a");
510
+ a.href = url;
511
+ a.href = a.href;
512
+ a.protocol = a.protocol.replace("http", "ws");
513
+ return a.href;
514
+ } else {
515
+ return url;
516
+ }
517
+ }
518
+ function createConsumer2() {
519
+ var url = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path;
520
+ return new Consumer(url);
521
+ }
522
+ function getConfig(name) {
523
+ var element = document.head.querySelector("meta[name='action-cable-" + name + "']");
524
+ if (element) {
525
+ return element.getAttribute("content");
526
+ }
527
+ }
528
+ exports2.Connection = Connection;
529
+ exports2.ConnectionMonitor = ConnectionMonitor;
530
+ exports2.Consumer = Consumer;
531
+ exports2.INTERNAL = INTERNAL;
532
+ exports2.Subscription = Subscription;
533
+ exports2.Subscriptions = Subscriptions;
534
+ exports2.adapters = adapters;
535
+ exports2.createWebSocketURL = createWebSocketURL;
536
+ exports2.logger = logger;
537
+ exports2.createConsumer = createConsumer2;
538
+ exports2.getConfig = getConfig;
539
+ Object.defineProperty(exports2, "__esModule", {
540
+ value: true
541
+ });
542
+ });
543
+ }
544
+ });
545
+
546
+ // node_modules/debounce/index.js
547
+ var require_debounce = __commonJS({
548
+ "node_modules/debounce/index.js"(exports, module) {
549
+ function debounce2(func, wait, immediate) {
550
+ var timeout, args, context, timestamp, result;
551
+ if (wait == null)
552
+ wait = 100;
553
+ function later() {
554
+ var last = Date.now() - timestamp;
555
+ if (last < wait && last >= 0) {
556
+ timeout = setTimeout(later, wait - last);
557
+ } else {
558
+ timeout = null;
559
+ if (!immediate) {
560
+ result = func.apply(context, args);
561
+ context = args = null;
562
+ }
563
+ }
564
+ }
565
+ ;
566
+ var debounced = function() {
567
+ context = this;
568
+ args = arguments;
569
+ timestamp = Date.now();
570
+ var callNow = immediate && !timeout;
571
+ if (!timeout)
572
+ timeout = setTimeout(later, wait);
573
+ if (callNow) {
574
+ result = func.apply(context, args);
575
+ context = args = null;
576
+ }
577
+ return result;
578
+ };
579
+ debounced.clear = function() {
580
+ if (timeout) {
581
+ clearTimeout(timeout);
582
+ timeout = null;
583
+ }
584
+ };
585
+ debounced.flush = function() {
586
+ if (timeout) {
587
+ result = func.apply(context, args);
588
+ context = args = null;
589
+ clearTimeout(timeout);
590
+ timeout = null;
591
+ }
592
+ };
593
+ return debounced;
594
+ }
595
+ debounce2.debounce = debounce2;
596
+ module.exports = debounce2;
597
+ }
598
+ });
599
+
600
+ // app/javascript/hotwire-livereload.js
601
+ var import_actioncable = __toModule(require_action_cable());
602
+ var import_debounce = __toModule(require_debounce());
603
+ var endpoint = "/hotwire-livereload/cable";
604
+ var uid = (Date.now() + (Math.random() * 100 | 0)).toString();
605
+ var consumer = (0, import_actioncable.createConsumer)(`${endpoint}?uid=${uid}`);
606
+ var received = (0, import_debounce.default)(() => {
607
+ console.log("Hotwire::Livereload files changed");
608
+ Turbo.visit(window.location.href);
609
+ }, 300);
610
+ consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", {
611
+ received,
612
+ connected() {
613
+ console.log("Hotwire::Livereload websocket connected");
614
+ },
615
+ disconnected() {
616
+ console.log("Hotwire::Livereload websocket disconnected");
617
+ }
618
+ });
619
+ })();
@@ -0,0 +1,9 @@
1
+ class Hotwire::Livereload::Connection < ActionCable::Connection::Base
2
+ identified_by :uid
3
+
4
+ def connect
5
+ self.uid = request.params[:uid]
6
+ logger.add_tags(uid)
7
+ logger.info "connected to Hotwire::Livereload"
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Hotwire::Livereload::ReloadChannel < ActionCable::Channel::Base
2
+ def subscribed
3
+ stream_from "reload"
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module Hotwire::Livereload::LivereloadTagsHelper
2
+ def hotwire_livereload_tags
3
+ return unless Rails.env.development?
4
+
5
+ javascript_include_tag "hotwire-livereload", defer: true
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ import { createConsumer } from "@rails/actioncable"
2
+ import debounce from "debounce"
3
+
4
+ const endpoint = "/hotwire-livereload/cable"
5
+ const uid = (Date.now() + ((Math.random() * 100) | 0)).toString()
6
+ const consumer = createConsumer(`${endpoint}?uid=${uid}`)
7
+ const received = debounce(() => {
8
+ console.log("Hotwire::Livereload files changed")
9
+ Turbo.visit(window.location.href)
10
+ }, 300)
11
+
12
+ consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", {
13
+ received,
14
+ connected() {
15
+ console.log("Hotwire::Livereload websocket connected")
16
+ },
17
+ disconnected() {
18
+ console.log("Hotwire::Livereload websocket disconnected")
19
+ },
20
+ })
@@ -0,0 +1,2 @@
1
+ development:
2
+ adapter: async
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Hotwire::Livereload::Engine.routes.draw do
2
+ if Rails.env.development?
3
+ mount Hotwire::Livereload::Engine.websocket => Hotwire::Livereload::Engine.cable.mount_path
4
+ end
5
+ end
@@ -0,0 +1,75 @@
1
+ require "rails"
2
+ require "action_cable/engine"
3
+ require "listen"
4
+
5
+ module Hotwire
6
+ module Livereload
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace Hotwire::Livereload
9
+ config.hotwire_livereload = ActiveSupport::OrderedOptions.new
10
+ config.hotwire_livereload.listen_paths ||= []
11
+ config.autoload_once_paths = %W(
12
+ #{root}/app/channels
13
+ #{root}/app/helpers
14
+ )
15
+
16
+ initializer "hotwire_livereload.assets" do
17
+ if Rails.application.config.respond_to?(:assets)
18
+ Rails.application.config.assets.precompile += %w( hotwire-livereload.js )
19
+ end
20
+ end
21
+
22
+ initializer "hotwire_livereload.helpers" do
23
+ ActiveSupport.on_load(:action_controller_base) do
24
+ helper Hotwire::Livereload::LivereloadTagsHelper
25
+ end
26
+ end
27
+
28
+ initializer "hotwire_livereload.set_configs" do |app|
29
+ options = app.config.hotwire_livereload
30
+ options.listen_paths = options.listen_paths.map(&:to_s)
31
+ options.listen_paths << Rails.root.join("app", "views")
32
+ end
33
+
34
+ initializer "hotwire_livereload.cable.config" do |app|
35
+ config_path = Hotwire::Livereload::Engine.root.join("config", "livereload_cable.yml")
36
+ cable = Hotwire::Livereload::Engine.cable
37
+
38
+ cable.cable = app.config_for(config_path).with_indifferent_access
39
+ cable.mount_path = "/cable"
40
+ cable.connection_class = -> { Hotwire::Livereload::Connection }
41
+ cable.logger ||= Rails.logger
42
+ end
43
+
44
+ config.after_initialize do |app|
45
+ return unless Rails.env.development?
46
+
47
+ @listener = Listen.to(*app.config.hotwire_livereload.listen_paths) do |modified, added, removed|
48
+ if modified.any? || removed.any? || added.any?
49
+ Hotwire::Livereload::Engine.websocket.broadcast(
50
+ "reload",
51
+ { modified: modified, removed: removed, added: added }
52
+ )
53
+ end
54
+ end
55
+ @listener.start
56
+ end
57
+
58
+ at_exit do
59
+ return unless Rails.env.development?
60
+
61
+ @listener&.stop
62
+ end
63
+
64
+ class << self
65
+ def websocket
66
+ @websocket ||= ActionCable::Server::Base.new(config: Hotwire::Livereload::Engine.cable)
67
+ end
68
+
69
+ def cable
70
+ @cable ||= ActionCable::Server::Configuration.new
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ module Hotwire
2
+ module Livereload
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require "hotwire/livereload/engine"
2
+
3
+ module Hotwire
4
+ module Livereload
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ route("mount Hotwire::Livereload::Engine, at: '/hotwire-livereload'")
2
+
3
+ if (app_layout_path = Rails.root.join("app/views/layouts/application.html.erb")).exist?
4
+ say "Add Hotwire::Livereload tag in application layout"
5
+ insert_into_file app_layout_path.to_s, before: /\s*<\/head>/ do <<-HTML
6
+ \n <%= hotwire_livereload_tags %>
7
+ HTML
8
+ end
9
+ else
10
+ say "Default application.html.erb is missing!", :red
11
+ say %( Add <%= hotwire_livereload_tags %> within the <head> tag in your custom layout.)
12
+ end
@@ -0,0 +1,6 @@
1
+ namespace :hotwire_livereload do
2
+ desc "Setup Hotwire Livereload"
3
+ task :install do
4
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../../../install/install.rb", __dir__)}"
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotwire-livereload
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kirill Platonov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: listen
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ description:
42
+ email:
43
+ - mail@kirillplatonov.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - app/assets/javascripts/hotwire-livereload.js
51
+ - app/channels/hotwire/livereload/connection.rb
52
+ - app/channels/hotwire/livereload/reload_channel.rb
53
+ - app/helpers/hotwire/livereload/livereload_tags_helper.rb
54
+ - app/javascript/hotwire-livereload.js
55
+ - config/livereload_cable.yml
56
+ - config/routes.rb
57
+ - lib/hotwire/livereload.rb
58
+ - lib/hotwire/livereload/engine.rb
59
+ - lib/hotwire/livereload/version.rb
60
+ - lib/install/install.rb
61
+ - lib/tasks/hotwire/livereload/install.rake
62
+ homepage: https://github.com/kirillplatonov/hotwire-livereload
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.2.22
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Automatically reload Hotwire Turbo when 'view' files are modified.
85
+ test_files: []