@rollup-extras/plugin-serve 2.2.0 → 2.3.1
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 +15 -0
- package/exports.d.ts +14 -0
- package/index.d.ts +14 -0
- package/index.js +62 -39
- package/livereload.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# Plugin Serve
|
|
2
4
|
|
|
3
5
|
Rollup plugin for a dev server.
|
|
@@ -122,6 +124,18 @@ Optional, `(server: Server) => void | true`
|
|
|
122
124
|
|
|
123
125
|
Extension point after the server is live. Please return true to suppress the default banner.
|
|
124
126
|
|
|
127
|
+
### liveReload
|
|
128
|
+
|
|
129
|
+
Optional, `boolean`, default: `false`.
|
|
130
|
+
|
|
131
|
+
> **Note:** Live reload is currently disabled by default. Set to `true` to opt in.
|
|
132
|
+
|
|
133
|
+
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.
|
|
134
|
+
|
|
135
|
+
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.
|
|
136
|
+
|
|
137
|
+
Set to `false` (or leave unset) to disable both the endpoint and the script injection.
|
|
138
|
+
|
|
125
139
|
## In-Memory Serving
|
|
126
140
|
|
|
127
141
|
```javascript
|
|
@@ -153,6 +167,7 @@ type ServePluginOptions = {
|
|
|
153
167
|
pluginName?: string;
|
|
154
168
|
useWriteBundle?: boolean;
|
|
155
169
|
inMemory?: boolean;
|
|
170
|
+
liveReload?: boolean;
|
|
156
171
|
dirs?: string | string[];
|
|
157
172
|
port?: number;
|
|
158
173
|
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)
|
|
@@ -35,6 +41,8 @@ export default function (options = {}) {
|
|
|
35
41
|
|
|
36
42
|
const memoryFiles = new Map();
|
|
37
43
|
|
|
44
|
+
let currentInputOptions;
|
|
45
|
+
|
|
38
46
|
let { dirs } = normalizedOptions,
|
|
39
47
|
collectDirs = false,
|
|
40
48
|
started = false,
|
|
@@ -55,8 +63,9 @@ export default function (options = {}) {
|
|
|
55
63
|
function renderStart( outputOptions, inputOptions) {
|
|
56
64
|
|
|
57
65
|
( (instance).renderStart).call(this, outputOptions, inputOptions);
|
|
58
|
-
if (inMemory) {
|
|
66
|
+
if (inMemory && currentInputOptions !== inputOptions) {
|
|
59
67
|
memoryFiles.clear();
|
|
68
|
+
currentInputOptions = inputOptions;
|
|
60
69
|
}
|
|
61
70
|
if (collectDirs) {
|
|
62
71
|
if (outputOptions.dir) {
|
|
@@ -88,54 +97,68 @@ export default function (options = {}) {
|
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
async function serve() {
|
|
91
|
-
if (!
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
if (!watchMode) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (started) {
|
|
104
|
+
if (liveReload) {
|
|
105
|
+
globalLiveReload?.broadcast('reload');
|
|
97
106
|
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
started = true;
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
if (globalServer) {
|
|
112
|
+
globalLiveReload?.close();
|
|
113
|
+
globalLiveReload = undefined;
|
|
114
|
+
await new Promise(resolve => (globalServer).close(resolve));
|
|
115
|
+
globalServer = undefined;
|
|
116
|
+
}
|
|
100
117
|
|
|
101
|
-
|
|
102
|
-
customize(app);
|
|
103
|
-
}
|
|
118
|
+
const app = new Hono();
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
if (customize) {
|
|
121
|
+
customize(app);
|
|
122
|
+
}
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
if (liveReload) {
|
|
125
|
+
globalLiveReload = createLiveReload();
|
|
126
|
+
globalLiveReload.register(app);
|
|
127
|
+
}
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
if (useLogger) {
|
|
130
|
+
app.use('*', honoLogger());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (inMemory) {
|
|
134
|
+
app.use('*', inMemoryMiddleware(memoryFiles));
|
|
135
|
+
}
|
|
116
136
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
globalServer = server;
|
|
137
|
+
for (const dir of (dirs)) {
|
|
138
|
+
app.use('*', serveStatic({ root: dir, ...staticOptions }));
|
|
139
|
+
}
|
|
122
140
|
|
|
123
|
-
|
|
141
|
+
const server = createAdaptorServer({
|
|
142
|
+
fetch: app.fetch,
|
|
143
|
+
...(https ? { createServer: createHttpsServer, serverOptions: https } : {}),
|
|
144
|
+
});
|
|
145
|
+
globalServer = server;
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
server.listen(port, host, listenCb);
|
|
127
|
-
} else {
|
|
128
|
-
server.listen(port, listenCb);
|
|
129
|
-
}
|
|
147
|
+
const listenCb = () => internalOnListen(server);
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw e;
|
|
136
|
-
}
|
|
137
|
-
});
|
|
149
|
+
if (host) {
|
|
150
|
+
server.listen(port, host, listenCb);
|
|
151
|
+
} else {
|
|
152
|
+
server.listen(port, listenCb);
|
|
138
153
|
}
|
|
154
|
+
|
|
155
|
+
server.on('error', ( e) => {
|
|
156
|
+
if (e.code === 'EADDRINUSE') {
|
|
157
|
+
logger.finish('address in use, please try another port', LogLevel.error);
|
|
158
|
+
} else {
|
|
159
|
+
throw e;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
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