hotwire-livereload 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []