livereload-morph 0.1.0 → 0.1.2
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.
- package/README.md +77 -96
- package/dist/{live-morph.js → livereload-morph.cjs} +39 -7
- package/dist/livereload-morph.min.js +1 -0
- package/package.json +3 -3
- package/dist/index.js +0 -1387
package/README.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
# livereload-morph
|
|
2
2
|
|
|
3
|
-
A livereload-js
|
|
3
|
+
A drop-in replacement for [livereload-js](https://github.com/livereload/livereload-js) that uses **idiomorph** for intelligent DOM morphing. Instead of full page reloads, livereload-morph preserves page state by morphing HTML changes directly into the DOM.
|
|
4
|
+
|
|
5
|
+
## What is LiveReload?
|
|
6
|
+
|
|
7
|
+
LiveReload watches your files and automatically refreshes your browser when you save changes. To use it, you need:
|
|
8
|
+
|
|
9
|
+
1. A **LiveReload server** running on your development machine (see list below)
|
|
10
|
+
2. A **client script** (this library) loaded in your browser
|
|
11
|
+
|
|
12
|
+
This library implements an enhanced client that morphs HTML changes instead of doing full page reloads.
|
|
13
|
+
|
|
14
|
+
## Compatible Servers
|
|
15
|
+
|
|
16
|
+
livereload-morph works with any LiveReload Protocol 7 compatible server:
|
|
17
|
+
- [html-compose](https://github.com/jealouscloud/html-comose) (Python)
|
|
18
|
+
- [guard-livereload](https://github.com/guard/guard-livereload) (Ruby)
|
|
19
|
+
- [python-livereload](https://github.com/lepture/python-livereload)
|
|
20
|
+
- [livereload](https://www.npmjs.com/package/livereload) (Node.js)
|
|
21
|
+
- [browser-sync](https://browsersync.io/)
|
|
22
|
+
- [grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch)
|
|
23
|
+
- [LiveReload app for Mac](http://livereload.com/)
|
|
24
|
+
- Any server implementing the [LiveReload protocol](http://livereload.com/api/protocol/)
|
|
4
25
|
|
|
5
26
|
## Features
|
|
6
27
|
|
|
@@ -12,46 +33,31 @@ A livereload-js replacement that uses **idiomorph** for intelligent DOM morphing
|
|
|
12
33
|
|
|
13
34
|
- **CSS Live Reload**: CSS changes update without flash using clone-and-replace strategy
|
|
14
35
|
|
|
15
|
-
- **LiveReload Protocol 7 Compatible**: Works with existing LiveReload servers (guard-livereload, browser-sync, etc.)
|
|
16
|
-
|
|
17
36
|
- **Minimal & Fast**: Small bundle (includes idiomorph), vanilla JavaScript, no dependencies
|
|
18
37
|
|
|
19
|
-
##
|
|
38
|
+
## Usage
|
|
20
39
|
|
|
21
|
-
|
|
40
|
+
Add to your HTML page:
|
|
22
41
|
|
|
23
|
-
```
|
|
24
|
-
|
|
42
|
+
```html
|
|
43
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/livereload-morph@latest/dist/livereload-morph.min.js?host=localhost"></script>
|
|
25
44
|
```
|
|
26
45
|
|
|
27
|
-
|
|
46
|
+
You can also download and serve the script locally, or install via npm:
|
|
28
47
|
|
|
29
48
|
```bash
|
|
30
|
-
|
|
49
|
+
npm install livereload-morph
|
|
31
50
|
```
|
|
32
51
|
|
|
33
|
-
###
|
|
52
|
+
### Configuration
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
Configure via query string parameters:
|
|
36
55
|
|
|
37
|
-
```
|
|
38
|
-
|
|
56
|
+
```html
|
|
57
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/livereload-morph@latest/dist/livereload-morph.min.js?host=localhost&verbose=true"></script>
|
|
39
58
|
```
|
|
40
59
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
**Test HTML Morphing:**
|
|
44
|
-
1. Type in the input field
|
|
45
|
-
2. Edit `test/index.html` (change text, add elements, etc.)
|
|
46
|
-
3. Watch the page update without losing your input!
|
|
47
|
-
|
|
48
|
-
**Test CSS Reload:**
|
|
49
|
-
1. Edit `test/styles.css` (change `.color-box` background color)
|
|
50
|
-
2. Watch styles update with no flash
|
|
51
|
-
|
|
52
|
-
## Usage
|
|
53
|
-
|
|
54
|
-
Add to your HTML page:
|
|
60
|
+
Or via global options:
|
|
55
61
|
|
|
56
62
|
```html
|
|
57
63
|
<script type="module">
|
|
@@ -62,16 +68,39 @@ Add to your HTML page:
|
|
|
62
68
|
morphHTML: true // Enable HTML morphing (default: true)
|
|
63
69
|
};
|
|
64
70
|
</script>
|
|
65
|
-
<script type="module" src="
|
|
71
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/livereload-morph@latest/dist/livereload-morph.min.js"></script>
|
|
66
72
|
```
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
## vs livereload-js
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
| Feature | livereload-morph | livereload-js |
|
|
77
|
+
|---------|------------------|---------------|
|
|
78
|
+
| HTML updates | Morph (preserves state) | Full reload |
|
|
79
|
+
| CSS updates | Clone-replace | Clone-replace |
|
|
80
|
+
| Input state | Preserved | Lost on reload |
|
|
81
|
+
| Scroll position | Preserved | Lost on reload |
|
|
82
|
+
| Focus state | Preserved | Lost on reload |
|
|
73
83
|
|
|
74
|
-
##
|
|
84
|
+
## How It Works
|
|
85
|
+
|
|
86
|
+
When a file changes:
|
|
87
|
+
|
|
88
|
+
- **CSS files** → Clone-and-replace for `<link>` tags, CSSOM rule replacement for `@import`
|
|
89
|
+
- **Images** → Cache-bust `<img>` src and CSS backgrounds
|
|
90
|
+
- **JavaScript** → Full page reload (no safe hot-reload)
|
|
91
|
+
- **Everything else** → Re-fetch page HTML and morph with idiomorph
|
|
92
|
+
|
|
93
|
+
### State Preservation
|
|
94
|
+
|
|
95
|
+
The following is preserved automatically during HTML morphs:
|
|
96
|
+
- Input values (text, textarea, select)
|
|
97
|
+
- Checkbox/radio checked state
|
|
98
|
+
- `<details>` open/closed state
|
|
99
|
+
- Scroll position
|
|
100
|
+
|
|
101
|
+
For best results, add IDs to form elements. Without IDs, idiomorph may recreate elements instead of morphing them.
|
|
102
|
+
|
|
103
|
+
## All Options
|
|
75
104
|
|
|
76
105
|
| Option | Type | Default | Description |
|
|
77
106
|
|--------|------|---------|-------------|
|
|
@@ -81,80 +110,32 @@ Or use query string parameters:
|
|
|
81
110
|
| `https` | boolean | false | Use secure WebSocket (wss://) |
|
|
82
111
|
| `morphHTML` | boolean | true | Enable HTML morphing |
|
|
83
112
|
| `verbose` | boolean | false | Enable console logging |
|
|
84
|
-
| `importCacheWaitPeriod` | number | 200 |
|
|
113
|
+
| `importCacheWaitPeriod` | number | 200 | Legacy WebKit @import workaround delay. Set to 0 to disable |
|
|
85
114
|
| `mindelay` | number | 1000 | Min reconnection delay (ms) |
|
|
86
115
|
| `maxdelay` | number | 60000 | Max reconnection delay (ms) |
|
|
87
116
|
| `handshake_timeout` | number | 5000 | Handshake timeout (ms) |
|
|
88
117
|
|
|
89
|
-
##
|
|
90
|
-
|
|
91
|
-
1. **WebSocket Connection**: Connects to LiveReload server on port 35729
|
|
92
|
-
2. **File Change Detection**: Server sends `reload` command with changed file path
|
|
93
|
-
3. **Smart Routing**:
|
|
94
|
-
- `.css` / `.css.map` files → Clone-and-replace for `<link>` tags, CSSOM rule replacement for `@import`
|
|
95
|
-
- Images (`.jpg`, `.png`, `.gif`, `.svg`, `.webp`, `.ico`) → Cache-bust `<img>` src and CSS backgrounds
|
|
96
|
-
- `.js` / `.mjs` files → Full page reload (no safe hot-reload)
|
|
97
|
-
- Everything else → Morph with idiomorph (re-fetches page HTML)
|
|
98
|
-
4. **State Preservation**: idiomorph intelligently merges changes while preserving DOM state
|
|
99
|
-
|
|
100
|
-
### CSS Reload Details
|
|
101
|
-
|
|
102
|
-
Live-morph supports both `<link>` tags and `@import` rules:
|
|
118
|
+
## Browser Support
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
- **`@import` rules**: Replace rule in CSSOM with cache-busted URL
|
|
106
|
-
- By default uses legacy WebKit workaround (pre-cache with temp `<link>` tag to trigger browser fetch)
|
|
107
|
-
- Prevents flicker when updating `@import` rules (still needed in modern browsers!)
|
|
108
|
-
- Set `importCacheWaitPeriod: 0` to disable workaround (will cause brief flicker)
|
|
109
|
-
- **Cross-origin CSS**: CORS-protected stylesheets are handled gracefully (can't inspect `@import` rules)
|
|
120
|
+
Modern browsers with ES6+ support (Chrome/Edge 60+, Firefox 60+, Safari 12+).
|
|
110
121
|
|
|
111
|
-
##
|
|
122
|
+
## License
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
- Input values (text, textarea, select)
|
|
115
|
-
- Checkbox/radio checked state
|
|
116
|
-
- `<details>` open/closed state
|
|
124
|
+
MIT
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
## Credits
|
|
119
127
|
|
|
120
|
-
|
|
128
|
+
- [idiomorph](https://github.com/bigskysoftware/idiomorph) for DOM morphing
|
|
129
|
+
- [LiveReload Protocol 7](https://github.com/livereload/livereload-js)
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|---------|------------------|---------------|
|
|
124
|
-
| HTML updates | ✅ Morph (preserves state) | ❌ Full reload |
|
|
125
|
-
| CSS updates | ✅ Clone-replace | ✅ Clone-replace |
|
|
126
|
-
| Input state | ✅ Preserved | ❌ Lost on reload |
|
|
127
|
-
| Scroll position | ✅ Preserved | ❌ Lost on reload |
|
|
128
|
-
| Focus state | ✅ Preserved | ❌ Lost on reload |
|
|
131
|
+
---
|
|
129
132
|
|
|
130
|
-
##
|
|
133
|
+
## Contributing
|
|
131
134
|
|
|
132
135
|
```bash
|
|
133
|
-
#
|
|
134
|
-
bun run build
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
bun run
|
|
138
|
-
|
|
139
|
-
# Run test server (builds + starts server)
|
|
140
|
-
bun run test
|
|
141
|
-
|
|
142
|
-
# Development mode (watch build + server)
|
|
143
|
-
bun run dev
|
|
136
|
+
bun install # Install dependencies
|
|
137
|
+
bun run build # Build once
|
|
138
|
+
bun run dev # Watch mode + test server
|
|
139
|
+
bun run test # Run test server
|
|
140
|
+
bun run test:e2e # Run playwright tests
|
|
144
141
|
```
|
|
145
|
-
|
|
146
|
-
## Browser Support
|
|
147
|
-
|
|
148
|
-
Modern browsers with ES6+ support:
|
|
149
|
-
- Chrome/Edge 60+
|
|
150
|
-
- Firefox 60+
|
|
151
|
-
- Safari 12+
|
|
152
|
-
|
|
153
|
-
## License
|
|
154
|
-
|
|
155
|
-
MIT
|
|
156
|
-
|
|
157
|
-
## Credits
|
|
158
|
-
|
|
159
|
-
- Built with [idiomorph](https://github.com/bigskysoftware/idiomorph) for DOM morphing
|
|
160
|
-
- Compatible with [LiveReload Protocol 7](github.com/livereload/livereload-js)
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
6
|
+
var __toCommonJS = (from) => {
|
|
7
|
+
var entry = __moduleCache.get(from), desc;
|
|
8
|
+
if (entry)
|
|
9
|
+
return entry;
|
|
10
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
13
|
+
get: () => from[key],
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
}));
|
|
16
|
+
__moduleCache.set(from, entry);
|
|
17
|
+
return entry;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/index.js
|
|
30
|
+
var exports_src = {};
|
|
31
|
+
__export(exports_src, {
|
|
32
|
+
default: () => src_default
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(exports_src);
|
|
35
|
+
|
|
1
36
|
// src/protocol.js
|
|
2
37
|
var PROTOCOL_6 = "http://livereload.com/protocols/official-6";
|
|
3
38
|
var PROTOCOL_7 = "http://livereload.com/protocols/official-7";
|
|
@@ -270,14 +305,14 @@ Options.extract = function(document2) {
|
|
|
270
305
|
}
|
|
271
306
|
const scripts = Array.from(document2.getElementsByTagName("script"));
|
|
272
307
|
for (const script of scripts) {
|
|
273
|
-
const host = script.getAttribute("data-
|
|
308
|
+
const host = script.getAttribute("data-livereload-morph-host");
|
|
274
309
|
if (host) {
|
|
275
310
|
const options = new Options;
|
|
276
311
|
options.host = host;
|
|
277
|
-
const port = script.getAttribute("data-
|
|
312
|
+
const port = script.getAttribute("data-livereload-morph-port");
|
|
278
313
|
if (port)
|
|
279
314
|
options.port = parseInt(port, 10);
|
|
280
|
-
const verbose = script.getAttribute("data-
|
|
315
|
+
const verbose = script.getAttribute("data-livereload-morph-verbose");
|
|
281
316
|
if (verbose !== null)
|
|
282
317
|
options.verbose = verbose === "true";
|
|
283
318
|
return options;
|
|
@@ -1064,7 +1099,7 @@ class Morpher {
|
|
|
1064
1099
|
style: "merge",
|
|
1065
1100
|
shouldPreserve: (elt) => {
|
|
1066
1101
|
if (elt.tagName === "SCRIPT" && elt.src) {
|
|
1067
|
-
return elt.src.toLowerCase().includes("
|
|
1102
|
+
return elt.src.toLowerCase().includes("livereload-morph");
|
|
1068
1103
|
}
|
|
1069
1104
|
return false;
|
|
1070
1105
|
}
|
|
@@ -1459,6 +1494,3 @@ if (typeof document !== "undefined") {
|
|
|
1459
1494
|
});
|
|
1460
1495
|
}
|
|
1461
1496
|
var src_default = liveMorph;
|
|
1462
|
-
export {
|
|
1463
|
-
src_default as default
|
|
1464
|
-
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var w="http://livereload.com/protocols/official-6",u="http://livereload.com/protocols/official-7";class M{constructor(_,$){this.message=`LiveReload protocol error (${_}) after receiving data: "${$}".`}}class g{constructor(_){this.handlers=_,this.reset()}reset(){this.protocol=null}process(_){try{let $;if(!this.protocol){if(_.match(new RegExp("^!!ver:([\\d.]+)$")))this.protocol=6;else if($=this._parseMessage(_,["hello"]))if(!$.protocols.length)throw new M("no protocols specified in handshake message");else if(Array.from($.protocols).includes("http://livereload.com/protocols/official-7"))this.protocol=7;else if(Array.from($.protocols).includes("http://livereload.com/protocols/official-6"))this.protocol=6;else throw new M("no supported protocols found");return this.handlers.connected(this.protocol)}if(this.protocol===6){if($=JSON.parse(_),!$.length)throw new M("protocol 6 messages must be arrays");let[Y,K]=Array.from($);if(Y!=="refresh")throw new M("unknown protocol 6 command");return this.handlers.message({command:"reload",path:K.path,liveCSS:K.apply_css_live!=null?K.apply_css_live:!0})}return $=this._parseMessage(_,["reload","alert"]),this.handlers.message($)}catch($){if($ instanceof M)return this.handlers.error($);throw $}}_parseMessage(_,$){let Y;try{Y=JSON.parse(_)}catch(K){throw new M("unparsable JSON",_)}if(!Y.command)throw new M('missing "command" key',_);if(!$.includes(Y.command))throw new M(`invalid command '${Y.command}', only valid commands are: ${$.join(", ")})`,_);return Y}}var r="1.0.0";class C{constructor(_,$,Y,K){this.options=_,this.WebSocket=$,this.Timer=Y,this.handlers=K;let X=this.options.path?`${this.options.path}`:"livereload",Z=this.options.port?`:${this.options.port}`:"";this._uri=`ws${this.options.https?"s":""}://${this.options.host}${Z}/${X}`,this._nextDelay=this.options.mindelay,this._connectionDesired=!1,this.protocol=0,this.protocolParser=new g({connected:(V)=>{return this.protocol=V,this._handshakeTimeout.stop(),this._nextDelay=this.options.mindelay,this._disconnectionReason="broken",this.handlers.connected(this.protocol)},error:(V)=>{return this.handlers.error(V),this._closeOnError()},message:(V)=>{return this.handlers.message(V)}}),this._handshakeTimeout=new this.Timer(()=>{if(!this._isSocketConnected())return;return this._disconnectionReason="handshake-timeout",this.socket.close()}),this._reconnectTimer=new this.Timer(()=>{if(!this._connectionDesired)return;return this.connect()}),this.connect()}_isSocketConnected(){return this.socket&&this.socket.readyState===this.WebSocket.OPEN}connect(){if(this._connectionDesired=!0,this._isSocketConnected())return;this._reconnectTimer.stop(),this._disconnectionReason="cannot-connect",this.protocolParser.reset(),this.handlers.connecting(),this.socket=new this.WebSocket(this._uri),this.socket.onopen=(_)=>this._onopen(_),this.socket.onclose=(_)=>this._onclose(_),this.socket.onmessage=(_)=>this._onmessage(_),this.socket.onerror=(_)=>this._onerror(_)}disconnect(){if(this._connectionDesired=!1,this._reconnectTimer.stop(),!this._isSocketConnected())return;return this._disconnectionReason="manual",this.socket.close()}_scheduleReconnection(){if(!this._connectionDesired)return;if(!this._reconnectTimer.running)this._reconnectTimer.start(this._nextDelay),this._nextDelay=Math.min(this.options.maxdelay,this._nextDelay*2)}sendCommand(_){if(!this.protocol)return;return this._sendCommand(_)}_sendCommand(_){return this.socket.send(JSON.stringify(_))}_closeOnError(){return this._handshakeTimeout.stop(),this._disconnectionReason="error",this.socket.close()}_onopen(_){this.handlers.socketConnected(),this._disconnectionReason="handshake-failed";let $={command:"hello",protocols:[w,u]};return $.ver=r,this._sendCommand($),this._handshakeTimeout.start(this.options.handshake_timeout)}_onclose(_){return this.protocol=0,this.handlers.disconnected(this._disconnectionReason,this._nextDelay),this._scheduleReconnection()}_onerror(_){}_onmessage(_){return this.protocolParser.process(_.data)}}class k{constructor(_){this.func=_,this.running=!1,this.id=null,this._handler=()=>{return this.running=!1,this.id=null,this.func()}}start(_){if(this.running)clearTimeout(this.id);this.id=setTimeout(this._handler,_),this.running=!0}stop(){if(this.running)clearTimeout(this.id),this.running=!1,this.id=null}}k.start=(_,$)=>setTimeout($,_);class S{constructor(){this.https=!1,this.host=null;let _=35729;Object.defineProperty(this,"port",{get(){return _},set($){_=$?isNaN($)?$:+$:""}}),this.mindelay=1000,this.maxdelay=60000,this.handshake_timeout=5000,this.morphHTML=!0,this.verbose=!1,this.importCacheWaitPeriod=200}set(_,$){if(typeof $>"u")return;if(!isNaN(+$))$=+$;if($==="true")$=!0;else if($==="false")$=!1;this[_]=$}}S.extract=function(_){let $=_.defaultView||window;if($&&$.LiveMorphOptions){let K=new S;for(let[X,Z]of Object.entries($.LiveMorphOptions))K.set(X,Z);return K}let Y=Array.from(_.getElementsByTagName("script"));for(let K of Y){let X=K.getAttribute("data-livereload-morph-host");if(X){let Z=new S;Z.host=X;let V=K.getAttribute("data-livereload-morph-port");if(V)Z.port=parseInt(V,10);let H=K.getAttribute("data-livereload-morph-verbose");if(H!==null)Z.verbose=H==="true";return Z}}return null};var p=function(){let _=()=>{},$={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:_,afterNodeAdded:_,beforeNodeMorphed:_,afterNodeMorphed:_,beforeNodeRemoved:_,afterNodeRemoved:_,beforeAttributeUpdated:_},head:{style:"merge",shouldPreserve:(j)=>j.getAttribute("im-preserve")==="true",shouldReAppend:(j)=>j.getAttribute("im-re-append")==="true",shouldRemove:_,afterHeadMorphed:_},restoreFocus:!0};function Y(j,A,W={}){j=i(j);let B=E(A),T=a(j,B,W),q=X(T,()=>{return H(T,j,B,(Q)=>{if(Q.morphStyle==="innerHTML")return Z(Q,j,B),Array.from(j.childNodes);else return K(Q,j,B)})});return T.pantry.remove(),q}function K(j,A,W){let B=E(A);return Z(j,B,W,A,A.nextSibling),Array.from(B.childNodes)}function X(j,A){if(!j.config.restoreFocus)return A();let W=document.activeElement;if(!(W instanceof HTMLInputElement||W instanceof HTMLTextAreaElement))return A();let{id:B,selectionStart:T,selectionEnd:q}=W,Q=A();if(B&&B!==document.activeElement?.getAttribute("id"))W=j.target.querySelector(`[id="${B}"]`),W?.focus();if(W&&!W.selectionEnd&&q)W.setSelectionRange(T,q);return Q}let Z=function(){function j(G,J,D,z=null,R=null){if(J instanceof HTMLTemplateElement&&D instanceof HTMLTemplateElement)J=J.content,D=D.content;z||=J.firstChild;for(let F of D.childNodes){if(z&&z!=R){let U=W(G,F,z,R);if(U){if(U!==z)T(G,z,U);V(U,F,G),z=U.nextSibling;continue}}if(F instanceof Element){let U=F.getAttribute("id");if(G.persistentIds.has(U)){let I=q(J,U,z,G);V(I,F,G),z=I.nextSibling;continue}}let L=A(J,F,z,G);if(L)z=L.nextSibling}while(z&&z!=R){let F=z;z=z.nextSibling,B(G,F)}}function A(G,J,D,z){if(z.callbacks.beforeNodeAdded(J)===!1)return null;if(z.idMap.has(J)){let R=document.createElement(J.tagName);return G.insertBefore(R,D),V(R,J,z),z.callbacks.afterNodeAdded(R),R}else{let R=document.importNode(J,!0);return G.insertBefore(R,D),z.callbacks.afterNodeAdded(R),R}}let W=function(){function G(z,R,F,L){let U=null,I=R.nextSibling,x=0,f=F;while(f&&f!=L){if(D(f,R)){if(J(z,f,R))return f;if(U===null){if(!z.idMap.has(f))U=f}}if(U===null&&I&&D(f,I)){if(x++,I=I.nextSibling,x>=2)U=void 0}if(z.activeElementAndParents.includes(f))break;f=f.nextSibling}return U||null}function J(z,R,F){let L=z.idMap.get(R),U=z.idMap.get(F);if(!U||!L)return!1;for(let I of L)if(U.has(I))return!0;return!1}function D(z,R){let F=z,L=R;return F.nodeType===L.nodeType&&F.tagName===L.tagName&&(!F.getAttribute?.("id")||F.getAttribute?.("id")===L.getAttribute?.("id"))}return G}();function B(G,J){if(G.idMap.has(J))O(G.pantry,J,null);else{if(G.callbacks.beforeNodeRemoved(J)===!1)return;J.parentNode?.removeChild(J),G.callbacks.afterNodeRemoved(J)}}function T(G,J,D){let z=J;while(z&&z!==D){let R=z;z=z.nextSibling,B(G,R)}return z}function q(G,J,D,z){let R=z.target.getAttribute?.("id")===J&&z.target||z.target.querySelector(`[id="${J}"]`)||z.pantry.querySelector(`[id="${J}"]`);return Q(R,z),O(G,R,D),R}function Q(G,J){let D=G.getAttribute("id");while(G=G.parentNode){let z=J.idMap.get(G);if(z){if(z.delete(D),!z.size)J.idMap.delete(G)}}}function O(G,J,D){if(G.moveBefore)try{G.moveBefore(J,D)}catch(z){G.insertBefore(J,D)}else G.insertBefore(J,D)}return j}(),V=function(){function j(Q,O,G){if(G.ignoreActive&&Q===document.activeElement)return null;if(G.callbacks.beforeNodeMorphed(Q,O)===!1)return Q;if(Q instanceof HTMLHeadElement&&G.head.ignore);else if(Q instanceof HTMLHeadElement&&G.head.style!=="morph")y(Q,O,G);else if(A(Q,O,G),!q(Q,G))Z(G,Q,O);return G.callbacks.afterNodeMorphed(Q,O),Q}function A(Q,O,G){let J=O.nodeType;if(J===1){let D=Q,z=O,R=D.attributes,F=z.attributes;for(let L of F){if(T(L.name,D,"update",G))continue;if(D.getAttribute(L.name)!==L.value)D.setAttribute(L.name,L.value)}for(let L=R.length-1;0<=L;L--){let U=R[L];if(!U)continue;if(!z.hasAttribute(U.name)){if(T(U.name,D,"remove",G))continue;D.removeAttribute(U.name)}}if(!q(D,G))W(D,z,G)}if(J===8||J===3){if(Q.nodeValue!==O.nodeValue)Q.nodeValue=O.nodeValue}}function W(Q,O,G){if(Q instanceof HTMLInputElement&&O instanceof HTMLInputElement&&O.type!=="file"){let J=O.value,D=Q.value;if(B(Q,O,"checked",G),B(Q,O,"disabled",G),!O.hasAttribute("value")){if(!T("value",Q,"remove",G))Q.value="",Q.removeAttribute("value")}else if(D!==J){if(!T("value",Q,"update",G))Q.setAttribute("value",J),Q.value=J}}else if(Q instanceof HTMLOptionElement&&O instanceof HTMLOptionElement)B(Q,O,"selected",G);else if(Q instanceof HTMLTextAreaElement&&O instanceof HTMLTextAreaElement){let J=O.value,D=Q.value;if(T("value",Q,"update",G))return;if(J!==D)Q.value=J;if(Q.firstChild&&Q.firstChild.nodeValue!==J)Q.firstChild.nodeValue=J}}function B(Q,O,G,J){let D=O[G],z=Q[G];if(D!==z){let R=T(G,Q,"update",J);if(!R)Q[G]=O[G];if(D){if(!R)Q.setAttribute(G,"")}else if(!T(G,Q,"remove",J))Q.removeAttribute(G)}}function T(Q,O,G,J){if(Q==="value"&&J.ignoreActiveValue&&O===document.activeElement)return!0;return J.callbacks.beforeAttributeUpdated(Q,O,G)===!1}function q(Q,O){return!!O.ignoreActiveValue&&Q===document.activeElement&&Q!==document.body}return j}();function H(j,A,W,B){if(j.head.block){let T=A.querySelector("head"),q=W.querySelector("head");if(T&&q){let Q=y(T,q,j);return Promise.all(Q).then(()=>{let O=Object.assign(j,{head:{block:!1,ignore:!0}});return B(O)})}}return B(j)}function y(j,A,W){let B=[],T=[],q=[],Q=[],O=new Map;for(let J of A.children)O.set(J.outerHTML,J);for(let J of j.children){let D=O.has(J.outerHTML),z=W.head.shouldReAppend(J),R=W.head.shouldPreserve(J);if(D||R)if(z)T.push(J);else O.delete(J.outerHTML),q.push(J);else if(W.head.style==="append"){if(z)T.push(J),Q.push(J)}else if(W.head.shouldRemove(J)!==!1)T.push(J)}Q.push(...O.values());let G=[];for(let J of Q){let D=document.createRange().createContextualFragment(J.outerHTML).firstChild;if(W.callbacks.beforeNodeAdded(D)!==!1){if("href"in D&&D.href||"src"in D&&D.src){let z,R=new Promise(function(F){z=F});D.addEventListener("load",function(){z()}),G.push(R)}j.appendChild(D),W.callbacks.afterNodeAdded(D),B.push(D)}}for(let J of T)if(W.callbacks.beforeNodeRemoved(J)!==!1)j.removeChild(J),W.callbacks.afterNodeRemoved(J);return W.head.afterHeadMorphed(j,{added:B,kept:q,removed:T}),G}let a=function(){function j(G,J,D){let{persistentIds:z,idMap:R}=Q(G,J),F=A(D),L=F.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(L))throw`Do not understand how to morph style ${L}`;return{target:G,newContent:J,config:F,morphStyle:L,ignoreActive:F.ignoreActive,ignoreActiveValue:F.ignoreActiveValue,restoreFocus:F.restoreFocus,idMap:R,persistentIds:z,pantry:W(),activeElementAndParents:B(G),callbacks:F.callbacks,head:F.head}}function A(G){let J=Object.assign({},$);return Object.assign(J,G),J.callbacks=Object.assign({},$.callbacks,G.callbacks),J.head=Object.assign({},$.head,G.head),J}function W(){let G=document.createElement("div");return G.hidden=!0,document.body.insertAdjacentElement("afterend",G),G}function B(G){let J=[],D=document.activeElement;if(D?.tagName!=="BODY"&&G.contains(D))while(D){if(J.push(D),D===G)break;D=D.parentElement}return J}function T(G){let J=Array.from(G.querySelectorAll("[id]"));if(G.getAttribute?.("id"))J.push(G);return J}function q(G,J,D,z){for(let R of z){let F=R.getAttribute("id");if(J.has(F)){let L=R;while(L){let U=G.get(L);if(U==null)U=new Set,G.set(L,U);if(U.add(F),L===D)break;L=L.parentElement}}}}function Q(G,J){let D=T(G),z=T(J),R=O(D,z),F=new Map;q(F,R,G,D);let L=J.__idiomorphRoot||J;return q(F,R,L,z),{persistentIds:R,idMap:F}}function O(G,J){let D=new Set,z=new Map;for(let{id:F,tagName:L}of G)if(z.has(F))D.add(F);else z.set(F,L);let R=new Set;for(let{id:F,tagName:L}of J)if(R.has(F))D.add(F);else if(z.get(F)===L)R.add(F);for(let F of D)R.delete(F);return R}return j}(),{normalizeElement:i,normalizeParent:E}=function(){let j=new WeakSet;function A(q){if(q instanceof Document)return q.documentElement;else return q}function W(q){if(q==null)return document.createElement("div");else if(typeof q==="string")return W(T(q));else if(j.has(q))return q;else if(q instanceof Node)if(q.parentNode)return new B(q);else{let Q=document.createElement("div");return Q.append(q),Q}else{let Q=document.createElement("div");for(let O of[...q])Q.append(O);return Q}}class B{constructor(q){this.originalNode=q,this.realParentNode=q.parentNode,this.previousSibling=q.previousSibling,this.nextSibling=q.nextSibling}get childNodes(){let q=[],Q=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;while(Q&&Q!=this.nextSibling)q.push(Q),Q=Q.nextSibling;return q}querySelectorAll(q){return this.childNodes.reduce((Q,O)=>{if(O instanceof Element){if(O.matches(q))Q.push(O);let G=O.querySelectorAll(q);for(let J=0;J<G.length;J++)Q.push(G[J])}return Q},[])}insertBefore(q,Q){return this.realParentNode.insertBefore(q,Q)}moveBefore(q,Q){return this.realParentNode.moveBefore(q,Q)}get __idiomorphRoot(){return this.originalNode}}function T(q){let Q=new DOMParser,O=q.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(O.match(/<\/html>/)||O.match(/<\/head>/)||O.match(/<\/body>/)){let G=Q.parseFromString(q,"text/html");if(O.match(/<\/html>/))return j.add(G),G;else{let J=G.firstChild;if(J)j.add(J);return J}}else{let J=Q.parseFromString("<body><template>"+q+"</template></body>","text/html").body.querySelector("template").content;return j.add(J),J}}return{normalizeElement:A,normalizeParent:W}}();return{morph:Y,defaults:$}}();function m(_){let $="",Y="",K=_.indexOf("#");if(K>=0)$=_.slice(K),_=_.slice(0,K);let X=_.indexOf("??");if(X>=0){if(X+1!==_.lastIndexOf("?"))K=_.lastIndexOf("?")}else K=_.indexOf("?");if(K>=0)Y=_.slice(K),_=_.slice(0,K);return{url:_,params:Y,hash:$}}function P(_){if(!_)return"";let $;if({url:_}=m(_),_.indexOf("file://")===0)$=_.replace(new RegExp("^file://(localhost)?"),"");else $=_.replace(new RegExp("^([^:]+:)?//([^:/]+)(:\\d*)?/"),"/");return decodeURIComponent($)}function d(_,$){if(_=_.replace(/^\/+/,"").toLowerCase(),$=$.replace(/^\/+/,"").toLowerCase(),_===$)return 1e4;let Y=_.split(/\/|\\/).reverse(),K=$.split(/\/|\\/).reverse(),X=Math.min(Y.length,K.length),Z=0;while(Z<X&&Y[Z]===K[Z])++Z;return Z}function c(_,$,Y=(K)=>K){let K={score:0};for(let X of $){let Z=d(_,Y(X));if(Z>K.score)K={object:X,score:Z}}if(K.score===0)return null;return K}function b(_){let{url:$,params:Y,hash:K}=m(_),X=`livereload=${Date.now()}`;if(!Y)return`${$}?${X}${K}`;if(Y.includes("livereload=")){let Z=Y.replace(/([?&])livereload=\d+/,`$1${X}`);return`${$}${Z}${K}`}return`${$}${Y}&${X}${K}`}function s(_,$=15000){return new Promise((Y)=>{let K=!1,X=()=>{if(K)return;K=!0,Y()};_.onload=()=>{X()};let Z=50,V=()=>{if(K)return;if(_.sheet){X();return}setTimeout(V,Z)};setTimeout(V,Z),setTimeout(X,$)})}class N{constructor(_,$,Y,K=200){this.window=_,this.console=$,this.Timer=Y,this.document=_.document,this.importCacheWaitPeriod=K}reload(_,$={}){let Y=_.match(/\.css(?:\.map)?$/i),K=_.match(/\.(jpe?g|png|gif|svg|webp|ico)$/i),X=_.match(/\.m?js$/i);if(Y&&$.liveCSS)return this.reloadStylesheet(_,$);if(K&&$.liveImg)return this.reloadImages(_);if(X)return this.reloadPage();if($.morphHTML)return this.morphHTML(_,$);this.reloadPage()}async morphHTML(_,$={}){try{let Y=await fetch(this.window.location.href,{cache:"no-cache",headers:{"X-Live-Morph":"true"}});if(!Y.ok)throw Error(`Fetch failed: ${Y.status} ${Y.statusText}`);let K=await Y.text();K=K.replace(/<!DOCTYPE[^>]*>/i,"").trim(),p.morph(this.document.documentElement,K,{head:{style:"merge",shouldPreserve:(X)=>{if(X.tagName==="SCRIPT"&&X.src)return X.src.toLowerCase().includes("livereload-morph");return!1}},callbacks:{beforeAttributeUpdated:(X,Z,V)=>{if(Z.tagName==="INPUT"||Z.tagName==="TEXTAREA"||Z.tagName==="SELECT"){if(X==="value"||X==="checked")return!1}if(Z.tagName==="DETAILS"&&X==="open")return!1;return!0}}}),this.console.log("HTML morphed successfully")}catch(Y){if(this.console.error(`Morph failed: ${Y.message}`),$.fallbackToReload!==!1)this.console.log("Falling back to full page reload"),this.reloadPage()}}async reloadStylesheet(_,$={}){try{let Y=Array.from(this.document.getElementsByTagName("link")).filter((Z)=>Z.rel&&Z.rel.match(/^stylesheet$/i)&&!Z.__LiveReload_pendingRemoval),K=[];for(let Z of Array.from(this.document.getElementsByTagName("style")))if(Z.sheet)this.collectImportedStylesheets(Z,Z.sheet,K);for(let Z of Y)if(Z.sheet)this.collectImportedStylesheets(Z,Z.sheet,K);if(this.window.StyleFix&&this.document.querySelectorAll)for(let Z of Array.from(this.document.querySelectorAll("style[data-href]")))Y.push(Z);this.console.log(`CSS reload: found ${Y.length} LINKed stylesheets, ${K.length} @imported stylesheets`);let X=c(_,Y.concat(K),(Z)=>P(Z.href||this.linkHref(Z)));if(!X){if($.reloadMissingCSS!==!1){this.console.log(`CSS reload: no match found for '${_}', reloading all stylesheets`);for(let Z of Y)await this.reattachStylesheetLink(Z)}else this.console.log(`CSS reload: no match found for '${_}', skipping (reloadMissingCSS=false)`);return}if(X.object.rule)this.console.log(`CSS reload: reloading @imported stylesheet: ${X.object.href}`),await this.reattachImportedRule(X.object);else this.console.log(`CSS reload: reloading stylesheet: ${this.linkHref(X.object)}`),await this.reattachStylesheetLink(X.object)}catch(Y){this.console.error(`Stylesheet reload failed: ${Y.message}`),this.console.error("Stack:",Y.stack)}}async reattachStylesheetLink(_){if(_.__LiveReload_pendingRemoval)return;_.__LiveReload_pendingRemoval=!0;let $;if(_.tagName==="STYLE")$=this.document.createElement("link"),$.rel="stylesheet",$.media=_.media,$.disabled=_.disabled;else $=_.cloneNode(!1);$.href=b(this.linkHref(_));let Y=_.parentNode;if(Y.lastChild===_)Y.appendChild($);else Y.insertBefore($,_.nextSibling);await s($);let K=/AppleWebKit/.test(this.window.navigator.userAgent)?5:200;if(await new Promise((X)=>this.Timer.start(K,X)),_.parentNode)_.parentNode.removeChild(_);if(this.window.StyleFix)this.window.StyleFix.link($)}reloadPage(){this.window.location.reload()}reloadImages(_){for(let K of Array.from(this.document.images))if(this.pathsMatch(_,P(K.src)))K.src=b(K.src);let $=["background","border"],Y=["backgroundImage","borderImage","webkitBorderImage","MozBorderImage"];for(let K of $)for(let X of Array.from(this.document.querySelectorAll(`[style*=${K}]`)))this.reloadStyleImages(X.style,Y,_);for(let K of Array.from(this.document.styleSheets))this.reloadStylesheetImages(K,_);this.console.log(`Image reload: ${_}`)}reloadStylesheetImages(_,$){let Y;try{Y=(_||{}).cssRules}catch(X){return}if(!Y)return;let K=["backgroundImage","borderImage","webkitBorderImage","MozBorderImage"];for(let X of Array.from(Y))switch(X.type){case CSSRule.IMPORT_RULE:this.reloadStylesheetImages(X.styleSheet,$);break;case CSSRule.STYLE_RULE:this.reloadStyleImages(X.style,K,$);break;case CSSRule.MEDIA_RULE:this.reloadStylesheetImages(X,$);break}}reloadStyleImages(_,$,Y){for(let K of $){let X=_[K];if(typeof X==="string"){let Z=X.replace(/\burl\s*\(([^)]*)\)/g,(V,H)=>{let y=H.replace(/^['"]|['"]$/g,"");if(this.pathsMatch(Y,P(y)))return`url(${b(y)})`;return V});if(Z!==X)_[K]=Z}}}pathsMatch(_,$){let Y=_.replace(/^\//,"").split("/").reverse(),K=$.replace(/^\//,"").split("/").reverse(),X=Math.min(Y.length,K.length);for(let Z=0;Z<X;Z++)if(Y[Z]!==K[Z])return!1;return X>0}linkHref(_){return _.href||_.getAttribute&&_.getAttribute("data-href")}collectImportedStylesheets(_,$,Y){let K;try{K=($||{}).cssRules}catch(X){return}if(K&&K.length)for(let X=0;X<K.length;X++){let Z=K[X];switch(Z.type){case CSSRule.CHARSET_RULE:continue;case CSSRule.IMPORT_RULE:Y.push({link:_,rule:Z,index:X,href:Z.href}),this.collectImportedStylesheets(_,Z.styleSheet,Y);break;default:break}}}async reattachImportedRule({rule:_,index:$,link:Y}){let K=_.parentStyleSheet,X=b(_.href),Z="";try{Z=_.media.length?[].join.call(_.media,", "):""}catch(H){if(H.name!=="SecurityError")this.console.error(`Unexpected error accessing @import media: ${H.name}: ${H.message}`)}let V=`@import url("${X}") ${Z};`;if(_.__LiveReload_newHref=X,this.importCacheWaitPeriod>0){let H=this.document.createElement("link");if(H.rel="stylesheet",H.href=X,H.__LiveReload_pendingRemoval=!0,Y.parentNode)Y.parentNode.insertBefore(H,Y);if(await new Promise((y)=>this.Timer.start(this.importCacheWaitPeriod,y)),H.parentNode)H.parentNode.removeChild(H);if(_.__LiveReload_newHref!==X)return}if(K.insertRule(V,$),K.deleteRule($+1),this.importCacheWaitPeriod>0){let H=K.cssRules[$];if(H.__LiveReload_newHref=X,await new Promise((y)=>this.Timer.start(this.importCacheWaitPeriod,y)),H.__LiveReload_newHref!==X)return;K.insertRule(V,$),K.deleteRule($+1)}}}class h{constructor(_){if(this.window=_,this.listeners={},!(this.WebSocket=this.window.WebSocket||this.window.MozWebSocket)){console.error("[LiveMorph] Disabled because the browser does not support WebSockets");return}if(this.options=S.extract(this.window.document),!this.options){console.error("[LiveMorph] Disabled - no configuration found"),console.error('[LiveMorph] Set window.LiveMorphOptions = { host: "localhost", port: 35729 }');return}console.log("[LiveMorph] Options loaded:",JSON.stringify({host:this.options.host,port:this.options.port,morphHTML:this.options.morphHTML,verbose:this.options.verbose})),this.console=this._setupConsole(),this.morpher=new N(this.window,this.console,k,this.options.importCacheWaitPeriod),this.connector=new C(this.options,this.WebSocket,k,{connecting:()=>{},socketConnected:()=>{},connected:($)=>{if(typeof this.listeners.connect==="function")this.listeners.connect();let{host:Y}=this.options,K=this.options.port?`:${this.options.port}`:"";return this.log(`Connected to ${Y}${K} (protocol v${$})`),this.sendInfo()},error:($)=>{if($ instanceof M)return console.log(`[LiveMorph] ${$.message}`);else return console.log(`[LiveMorph] Internal error: ${$.message}`)},disconnected:($,Y)=>{if(typeof this.listeners.disconnect==="function")this.listeners.disconnect();let{host:K}=this.options,X=this.options.port?`:${this.options.port}`:"",Z=(Y/1000).toFixed(0);switch($){case"cannot-connect":return this.log(`Cannot connect to ${K}${X}, will retry in ${Z} sec`);case"broken":return this.log(`Disconnected from ${K}${X}, reconnecting in ${Z} sec`);case"handshake-timeout":return this.log(`Cannot connect to ${K}${X} (handshake timeout), will retry in ${Z} sec`);case"handshake-failed":return this.log(`Cannot connect to ${K}${X} (handshake failed), will retry in ${Z} sec`);case"manual":case"error":default:return this.log(`Disconnected from ${K}${X} (${$}), reconnecting in ${Z} sec`)}},message:($)=>{switch($.command){case"reload":return this.performReload($);case"alert":return this.performAlert($)}}}),this.initialized=!0}_setupConsole(){if(!(this.window.console&&this.window.console.log&&this.window.console.error))return{log(){},error(){}};if(this.options.verbose)return this.window.console;return{log(){},error:this.window.console.error.bind(this.window.console)}}on(_,$){this.listeners[_]=$}log(_){return this.console.log(`[LiveMorph] ${_}`)}performReload(_){this.log(`Received reload request for: ${_.path}`);let $={liveCSS:_.liveCSS!=null?_.liveCSS:!0,liveImg:_.liveImg!=null?_.liveImg:!0,reloadMissingCSS:_.reloadMissingCSS!=null?_.reloadMissingCSS:!0,morphHTML:this.options.morphHTML};return this.log(`Reload options: ${JSON.stringify($)}`),this.morpher.reload(_.path,$)}performAlert(_){return alert(_.message)}sendInfo(){if(!this.initialized)return;if(!(this.connector.protocol>=7))return;this.connector.sendCommand({command:"info",plugins:{},url:this.window.location.href})}shutDown(){if(!this.initialized)return;if(this.connector.disconnect(),this.log("Disconnected"),typeof this.listeners.shutdown==="function")this.listeners.shutdown()}}var v=new h(window);window.LiveMorph=v;if(typeof document<"u")document.addEventListener("LiveMorphShutDown",()=>{v.shutDown()}),v.on("connect",()=>{let _=new CustomEvent("LiveMorphConnect");document.dispatchEvent(_)}),v.on("disconnect",()=>{let _=new CustomEvent("LiveMorphDisconnect");document.dispatchEvent(_)});var R_=v;export{R_ as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livereload-morph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A livereload-js replacement using idiomorph for intelligent DOM morphing",
|
|
5
5
|
"author": "Noah A.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"development"
|
|
23
23
|
],
|
|
24
24
|
"module": "dist/livereload-morph.js",
|
|
25
|
-
"main": "dist/livereload-morph.
|
|
25
|
+
"main": "dist/livereload-morph.cjs",
|
|
26
26
|
"type": "module",
|
|
27
27
|
"files": ["dist", "LICENSE"],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "bun build src/index.js --outfile dist/livereload-morph.js --target browser --format esm",
|
|
29
|
+
"build": "bun build src/index.js --outfile dist/livereload-morph.js --target browser --format esm && bun build src/index.js --outfile dist/livereload-morph.min.js --target browser --format esm --minify && bun build src/index.js --outfile dist/livereload-morph.cjs --target browser --format cjs",
|
|
30
30
|
"build:watch": "bun build src/index.js --outfile dist/livereload-morph.js --target browser --format esm --watch",
|
|
31
31
|
"test": "bun run build && bun test-server.js",
|
|
32
32
|
"test:e2e": "bun tests/e2e.js",
|