@rollup-extras/plugin-serve 2.2.0 → 2.3.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.
- package/README.md +13 -0
- package/exports.d.ts +14 -0
- package/index.d.ts +14 -0
- package/index.js +58 -38
- package/livereload.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,6 +122,18 @@ Optional, `(server: Server) => void | true`
|
|
|
122
122
|
|
|
123
123
|
Extension point after the server is live. Please return true to suppress the default banner.
|
|
124
124
|
|
|
125
|
+
### liveReload
|
|
126
|
+
|
|
127
|
+
Optional, `boolean`, default: `false`.
|
|
128
|
+
|
|
129
|
+
> **Note:** Live reload is currently disabled by default. Set to `true` to opt in.
|
|
130
|
+
|
|
131
|
+
When enabled and Rollup is running in watch mode, the plugin exposes a Server-Sent Events endpoint at `/__livereload` and injects a tiny client script into every served `text/html` response. After each rebuild the server broadcasts a `reload` event that triggers `location.reload()` in the browser.
|
|
132
|
+
|
|
133
|
+
Injection happens uniformly for in-memory HTML, disk-served HTML (via `dirs`), and any HTML returned by routes registered in `customize`. Non-HTML responses are left unchanged.
|
|
134
|
+
|
|
135
|
+
Set to `false` (or leave unset) to disable both the endpoint and the script injection.
|
|
136
|
+
|
|
125
137
|
## In-Memory Serving
|
|
126
138
|
|
|
127
139
|
```javascript
|
|
@@ -153,6 +165,7 @@ type ServePluginOptions = {
|
|
|
153
165
|
pluginName?: string;
|
|
154
166
|
useWriteBundle?: boolean;
|
|
155
167
|
inMemory?: boolean;
|
|
168
|
+
liveReload?: boolean;
|
|
156
169
|
dirs?: string | string[];
|
|
157
170
|
port?: number;
|
|
158
171
|
useLogger?: boolean;
|
package/exports.d.ts
CHANGED
|
@@ -8,6 +8,20 @@ export type ServePluginOptionsObject = {
|
|
|
8
8
|
pluginName?: string;
|
|
9
9
|
useWriteBundle?: boolean;
|
|
10
10
|
inMemory?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Enable live reload over Server-Sent Events. Defaults to `false`.
|
|
13
|
+
*
|
|
14
|
+
* Live reload is currently disabled by default. Set to `true` to opt in.
|
|
15
|
+
*
|
|
16
|
+
* When enabled and Rollup is running in watch mode, a small client script
|
|
17
|
+
* is injected into every served `text/html` response and subscribes to an
|
|
18
|
+
* SSE endpoint at `/__livereload`. After every rebuild the server
|
|
19
|
+
* broadcasts a `reload` event that triggers `location.reload()` in the
|
|
20
|
+
* browser.
|
|
21
|
+
*
|
|
22
|
+
* Set to `false` (or leave unset) to disable injection and the SSE endpoint.
|
|
23
|
+
*/
|
|
24
|
+
liveReload?: boolean;
|
|
11
25
|
dirs?: string | string[];
|
|
12
26
|
port?: number;
|
|
13
27
|
useLogger?: boolean;
|
package/index.d.ts
CHANGED
|
@@ -7,6 +7,20 @@ declare module '@rollup-extras/plugin-serve' {
|
|
|
7
7
|
pluginName?: string;
|
|
8
8
|
useWriteBundle?: boolean;
|
|
9
9
|
inMemory?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Enable live reload over Server-Sent Events. Defaults to `false`.
|
|
12
|
+
*
|
|
13
|
+
* Live reload is currently disabled by default. Set to `true` to opt in.
|
|
14
|
+
*
|
|
15
|
+
* When enabled and Rollup is running in watch mode, a small client script
|
|
16
|
+
* is injected into every served `text/html` response and subscribes to an
|
|
17
|
+
* SSE endpoint at `/__livereload`. After every rebuild the server
|
|
18
|
+
* broadcasts a `reload` event that triggers `location.reload()` in the
|
|
19
|
+
* browser.
|
|
20
|
+
*
|
|
21
|
+
* Set to `false` (or leave unset) to disable injection and the SSE endpoint.
|
|
22
|
+
*/
|
|
23
|
+
liveReload?: boolean;
|
|
10
24
|
dirs?: string | string[];
|
|
11
25
|
port?: number;
|
|
12
26
|
useLogger?: boolean;
|
package/index.js
CHANGED
|
@@ -11,8 +11,12 @@ import logger from '@rollup-extras/utils/logger';
|
|
|
11
11
|
import { multiConfigPluginBase } from '@rollup-extras/utils/multi-config-plugin-base';
|
|
12
12
|
import { getOptions } from '@rollup-extras/utils/options';
|
|
13
13
|
|
|
14
|
+
import { createLiveReload } from './livereload.js';
|
|
15
|
+
|
|
14
16
|
let globalServer;
|
|
15
17
|
|
|
18
|
+
let globalLiveReload;
|
|
19
|
+
|
|
16
20
|
const factories = { logger };
|
|
17
21
|
|
|
18
22
|
export default function (options = {}) {
|
|
@@ -23,11 +27,13 @@ export default function (options = {}) {
|
|
|
23
27
|
useWriteBundle: true,
|
|
24
28
|
port: 8080,
|
|
25
29
|
useLogger: true,
|
|
30
|
+
liveReload: false,
|
|
26
31
|
},
|
|
27
32
|
'dirs',
|
|
28
33
|
factories
|
|
29
34
|
);
|
|
30
|
-
const { pluginName, port, host, https, useLogger, customize, staticOptions, onListen, logger, inMemory } =
|
|
35
|
+
const { pluginName, port, host, https, useLogger, customize, staticOptions, onListen, logger, inMemory, liveReload } =
|
|
36
|
+
normalizedOptions;
|
|
31
37
|
const useWriteBundle = inMemory ? false : normalizedOptions.useWriteBundle;
|
|
32
38
|
const instance = inMemory
|
|
33
39
|
? multiConfigPluginBase(useWriteBundle, pluginName, serve, captureOutput)
|
|
@@ -88,54 +94,68 @@ export default function (options = {}) {
|
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
async function serve() {
|
|
91
|
-
if (!
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
if (!watchMode) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (started) {
|
|
101
|
+
if (liveReload) {
|
|
102
|
+
globalLiveReload?.broadcast('reload');
|
|
97
103
|
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
started = true;
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
if (globalServer) {
|
|
109
|
+
globalLiveReload?.close();
|
|
110
|
+
globalLiveReload = undefined;
|
|
111
|
+
await new Promise(resolve => (globalServer).close(resolve));
|
|
112
|
+
globalServer = undefined;
|
|
113
|
+
}
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
customize(app);
|
|
103
|
-
}
|
|
115
|
+
const app = new Hono();
|
|
104
116
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
if (customize) {
|
|
118
|
+
customize(app);
|
|
119
|
+
}
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
if (liveReload) {
|
|
122
|
+
globalLiveReload = createLiveReload();
|
|
123
|
+
globalLiveReload.register(app);
|
|
124
|
+
}
|
|
112
125
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
126
|
+
if (useLogger) {
|
|
127
|
+
app.use('*', honoLogger());
|
|
128
|
+
}
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
globalServer = server;
|
|
130
|
+
if (inMemory) {
|
|
131
|
+
app.use('*', inMemoryMiddleware(memoryFiles));
|
|
132
|
+
}
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
for (const dir of (dirs)) {
|
|
135
|
+
app.use('*', serveStatic({ root: dir, ...staticOptions }));
|
|
136
|
+
}
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
const server = createAdaptorServer({
|
|
139
|
+
fetch: app.fetch,
|
|
140
|
+
...(https ? { createServer: createHttpsServer, serverOptions: https } : {}),
|
|
141
|
+
});
|
|
142
|
+
globalServer = server;
|
|
143
|
+
|
|
144
|
+
const listenCb = () => internalOnListen(server);
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw e;
|
|
136
|
-
}
|
|
137
|
-
});
|
|
146
|
+
if (host) {
|
|
147
|
+
server.listen(port, host, listenCb);
|
|
148
|
+
} else {
|
|
149
|
+
server.listen(port, listenCb);
|
|
138
150
|
}
|
|
151
|
+
|
|
152
|
+
server.on('error', ( e) => {
|
|
153
|
+
if (e.code === 'EADDRINUSE') {
|
|
154
|
+
logger.finish('address in use, please try another port', LogLevel.error);
|
|
155
|
+
} else {
|
|
156
|
+
throw e;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
function internalOnListen(server) {
|
package/livereload.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { streamSSE } from 'hono/streaming';
|
|
2
|
+
|
|
3
|
+
const ENDPOINT = '/__livereload';
|
|
4
|
+
const KEEPALIVE_MS = 25000;
|
|
5
|
+
|
|
6
|
+
const CLIENT_SCRIPT = `<script>(function(){try{var es=new EventSource(${JSON.stringify(ENDPOINT)});es.addEventListener('reload',function(){location.reload();});}catch(e){}})();</script>`;
|
|
7
|
+
|
|
8
|
+
export function createLiveReload() {
|
|
9
|
+
|
|
10
|
+
const clients = new Set();
|
|
11
|
+
let closed = false;
|
|
12
|
+
|
|
13
|
+
return { register, broadcast, close };
|
|
14
|
+
|
|
15
|
+
function register(app) {
|
|
16
|
+
app.get(ENDPOINT, c => {
|
|
17
|
+
return streamSSE(c, async stream => {
|
|
18
|
+
clients.add(stream);
|
|
19
|
+
stream.onAbort(() => {
|
|
20
|
+
clients.delete(stream);
|
|
21
|
+
});
|
|
22
|
+
while (!closed && !stream.aborted && !stream.closed) {
|
|
23
|
+
await new Promise(res => setTimeout(res, KEEPALIVE_MS));
|
|
24
|
+
if (closed || stream.aborted || stream.closed) break;
|
|
25
|
+
try {
|
|
26
|
+
await stream.writeSSE({ event: 'ping', data: '' });
|
|
27
|
+
} catch {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
clients.delete(stream);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
app.use('*', async (c, next) => {
|
|
36
|
+
await next();
|
|
37
|
+
const res = c.res;
|
|
38
|
+
if (!res) return;
|
|
39
|
+
const contentType = res.headers.get('content-type') || '';
|
|
40
|
+
if (!contentType.toLowerCase().includes('text/html')) return;
|
|
41
|
+
let body;
|
|
42
|
+
try {
|
|
43
|
+
body = await res.text();
|
|
44
|
+
} catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const injected = inject(body);
|
|
48
|
+
res.headers.delete('content-length');
|
|
49
|
+
c.res = new Response(injected, {
|
|
50
|
+
status: res.status,
|
|
51
|
+
statusText: res.statusText,
|
|
52
|
+
headers: { 'content-length': String(Buffer.byteLength(injected)) },
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function broadcast(event) {
|
|
58
|
+
for (const stream of clients) {
|
|
59
|
+
try {
|
|
60
|
+
stream.writeSSE({ event, data: '' });
|
|
61
|
+
} catch {
|
|
62
|
+
clients.delete(stream);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function close() {
|
|
68
|
+
closed = true;
|
|
69
|
+
for (const stream of clients) {
|
|
70
|
+
try {
|
|
71
|
+
stream.close();
|
|
72
|
+
} catch {
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
clients.clear();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function inject(html) {
|
|
81
|
+
if (html.includes(ENDPOINT)) {
|
|
82
|
+
return html;
|
|
83
|
+
}
|
|
84
|
+
const match = html.match(/<\/body\s*>/i);
|
|
85
|
+
if (match) {
|
|
86
|
+
const idx = (match.index);
|
|
87
|
+
return html.slice(0, idx) + CLIENT_SCRIPT + html.slice(idx);
|
|
88
|
+
}
|
|
89
|
+
return html + CLIENT_SCRIPT;
|
|
90
|
+
}
|
package/package.json
CHANGED