@rollup-extras/plugin-serve 2.3.1 → 2.4.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 CHANGED
@@ -66,15 +66,15 @@ Option to use `writeBundle` hook instead of `generateBundle`.
66
66
 
67
67
  Optional, `boolean`, default: `false`.
68
68
 
69
- When enabled, serves all emitted assets directly from memory instead of from disk. This is similar to how webpack-dev-server works — Rollup output never needs to hit the filesystem.
69
+ When enabled, serves all emitted assets directly from memory instead of from disk. This controls how files are served; Rollup still writes output when run with `rollup -cw`.
70
70
 
71
71
  When `inMemory` is `true`, the plugin automatically uses the `generateBundle` hook (regardless of `useWriteBundle`) to capture all chunks and assets in memory. A custom Hono middleware serves these files before falling through to any disk-based static serving configured via `dirs`.
72
72
 
73
- For a fully disk-free dev server, set `watch: { skipWrite: true }` in your Rollup config to prevent Rollup from writing files to disk. The plugin does not force this — you control it.
73
+ For disk-free Rollup output, use the package's [`/watch` helper](#in-memory-serving). Setting `watch: { skipWrite: true }` with Rollup's ordinary watcher skips output generation as well as writing, so the plugin receives no files to serve.
74
74
 
75
75
  **Limitations:**
76
76
  - Only covers Rollup's own output. Other plugins (e.g., plugin-copy, plugin-html) may still perform disk I/O independently.
77
- - Plugins that only use the `writeBundle` hook won't participate when `skipWrite` is enabled.
77
+ - Plugins that only use the `writeBundle` hook won't participate with the `/watch` helper. Configure plugins such as plugin-html to use `generateBundle` when their output should be served from memory.
78
78
 
79
79
  ### dirs
80
80
 
@@ -138,6 +138,8 @@ Set to `false` (or leave unset) to disable both the endpoint and the script inje
138
138
 
139
139
  ## In-Memory Serving
140
140
 
141
+ Keep the Rollup configuration in `rollup.config.js`:
142
+
141
143
  ```javascript
142
144
  import serve from "@rollup-extras/plugin-serve";
143
145
 
@@ -147,13 +149,34 @@ export default {
147
149
  format: "es",
148
150
  dir: "dest",
149
151
  },
150
- watch: {
151
- skipWrite: true, // recommended: avoid writing files to disk
152
- },
153
152
  plugins: [serve({ inMemory: true })],
154
153
  };
155
154
  ```
156
155
 
156
+ Create `dev.js` to start a watcher that generates output without writing it to disk:
157
+
158
+ ```javascript
159
+ import watch from "@rollup-extras/plugin-serve/watch";
160
+ import config from "./rollup.config.js";
161
+
162
+ const watcher = watch(config);
163
+
164
+ watcher.on("event", event => {
165
+ if (event.code === "ERROR") console.error(event.error);
166
+ });
167
+
168
+ process.once("SIGINT", () => watcher.close());
169
+ process.once("SIGTERM", () => watcher.close());
170
+ ```
171
+
172
+ Run `node dev.js`. The helper accepts one Rollup configuration or an array, generates all configured outputs on each rebuild, and forces `watch.skipWrite` without mutating the supplied configuration. Configurations with `watch: false` are ignored.
173
+
174
+ It returns a Rollup watcher. Use its `END` event to observe completion of generation and build cleanup. The helper owns the build results, so consumers should not call `event.result.generate()` or `event.result.close()`. Output errors are reported through the usual `ERROR` event, and later file changes can trigger another build. `await watcher.close()` waits for pending generation and shuts down the server.
175
+
176
+ Register watched dependencies in build hooks, such as `buildStart` or `transform`. With `skipWrite`, Rollup collects watch files before `BUNDLE_END`; files first registered in output hooks during the helper's generation are not picked up by the watcher.
177
+
178
+ Running the same configuration with `rollup -cw` also serves from memory, while continuing to write the usual output files.
179
+
157
180
  Hybrid mode — bundle from memory, static assets from disk:
158
181
 
159
182
  ```javascript
package/index.d.ts CHANGED
@@ -41,3 +41,14 @@ declare module '@rollup-extras/plugin-serve' {
41
41
 
42
42
  export {};
43
43
  }
44
+
45
+ declare module '@rollup-extras/plugin-serve/watch' {
46
+ import type { RollupWatcher, RollupWatchOptions } from 'rollup';
47
+ /**
48
+ * Watch and generate Rollup output in memory, closing each build result automatically.
49
+ * Configure `serve({ inMemory: true })` in the supplied Rollup configuration.
50
+ */
51
+ export default function watch(config: RollupWatchOptions | RollupWatchOptions[]): RollupWatcher;
52
+
53
+ export {};
54
+ }
package/index.js CHANGED
@@ -43,12 +43,14 @@ export default function (options = {}) {
43
43
 
44
44
  let currentInputOptions;
45
45
 
46
+ let server;
47
+
46
48
  let { dirs } = normalizedOptions,
47
49
  collectDirs = false,
48
50
  started = false,
49
51
  watchMode = true;
50
52
 
51
- const pluginInstance = { ...instance, outputOptions };
53
+ const pluginInstance = { ...instance, outputOptions, closeWatcher };
52
54
 
53
55
  if (!dirs) {
54
56
  dirs = [];
@@ -138,13 +140,13 @@ export default function (options = {}) {
138
140
  app.use('*', serveStatic({ root: dir, ...staticOptions }));
139
141
  }
140
142
 
141
- const server = createAdaptorServer({
143
+ server = createAdaptorServer({
142
144
  fetch: app.fetch,
143
145
  ...(https ? { createServer: createHttpsServer, serverOptions: https } : {}),
144
146
  });
145
147
  globalServer = server;
146
148
 
147
- const listenCb = () => internalOnListen(server);
149
+ const listenCb = () => internalOnListen( (server));
148
150
 
149
151
  if (host) {
150
152
  server.listen(port, host, listenCb);
@@ -161,6 +163,14 @@ export default function (options = {}) {
161
163
  });
162
164
  }
163
165
 
166
+ async function closeWatcher() {
167
+ if (!server || globalServer !== server) return;
168
+ globalLiveReload?.close();
169
+ globalLiveReload = undefined;
170
+ globalServer = undefined;
171
+ await new Promise(resolve => (server).close(resolve));
172
+ }
173
+
164
174
  function internalOnListen(server) {
165
175
  if (!onListen?.( (server))) {
166
176
  logger.finish(`listening on ${linkFromAddress(server.address(), !!https)}`, LogLevel.info);
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.3.1",
2
+ "version": "2.4.0",
3
3
  "license": "MIT",
4
4
  "name": "@rollup-extras/plugin-serve",
5
5
  "author": "Konstantin Shutkin",
@@ -10,6 +10,10 @@
10
10
  "types": "./index.d.ts",
11
11
  "default": "./index.js"
12
12
  },
13
+ "./watch": {
14
+ "types": "./index.d.ts",
15
+ "default": "./watch.js"
16
+ },
13
17
  "./package.json": "./package.json"
14
18
  },
15
19
  "main": "./index.js",
@@ -0,0 +1,7 @@
1
+ import type { RollupWatcher, RollupWatchOptions } from 'rollup';
2
+
3
+ /**
4
+ * Watch and generate Rollup output in memory, closing each build result automatically.
5
+ * Configure `serve({ inMemory: true })` in the supplied Rollup configuration.
6
+ */
7
+ export default function watch(config: RollupWatchOptions | RollupWatchOptions[]): RollupWatcher;
package/watch.js ADDED
@@ -0,0 +1,93 @@
1
+ import { watch as rollupWatch } from 'rollup';
2
+
3
+ export default function watch(config) {
4
+
5
+ let outputs = [];
6
+ let closing = false;
7
+
8
+ let finishBuild;
9
+ let pending = Promise.resolve();
10
+
11
+ let closed;
12
+
13
+ let ready;
14
+ const initialized = new Promise(resolve => {
15
+ ready = () => resolve(undefined);
16
+ });
17
+ const configs = Array.isArray(config) ? config : [config];
18
+ const watcher = rollupWatch(
19
+ configs.map(options => ({
20
+ ...options,
21
+ watch: options.watch === false ? false : { ...options.watch, skipWrite: true },
22
+ plugins: [
23
+ options.plugins,
24
+ {
25
+ name: '@rollup-extras/plugin-serve/watch',
26
+ buildStart() {
27
+ outputs = Array.isArray(options.output) ? options.output : [options.output || {}];
28
+ },
29
+ },
30
+ ],
31
+ }))
32
+ );
33
+ watcher.on('event', async event => {
34
+ if (event.code === 'BUNDLE_START') {
35
+ pending = new Promise(resolve => {
36
+ finishBuild = resolve;
37
+ });
38
+ ready?.();
39
+ } else if (event.code === 'BUNDLE_END') {
40
+ try {
41
+ await generate(event.result, outputs);
42
+ } finally {
43
+ finishBuild?.();
44
+ }
45
+ } else if (event.code === 'ERROR') {
46
+ try {
47
+ await event.result?.close();
48
+ } finally {
49
+ finishBuild?.();
50
+ }
51
+ }
52
+ });
53
+
54
+ function close() {
55
+ closing = true;
56
+ closed ||= (async () => {
57
+
58
+ await initialized;
59
+ await pending;
60
+ await watcher.close();
61
+ })();
62
+ return closed;
63
+ }
64
+
65
+ const managed = new Proxy(watcher, {
66
+ get(target, key) {
67
+ if (key === 'close') return close;
68
+ const value = Reflect.get(target, key);
69
+ if (typeof value !== 'function') return value;
70
+
71
+ function call(...args) {
72
+ const result = value.apply(target, args);
73
+ return result === target ? managed : result;
74
+ }
75
+ return call;
76
+ },
77
+ });
78
+ return managed;
79
+
80
+ async function generate(result, outputOptions) {
81
+ try {
82
+ if (!closing) {
83
+
84
+ const generated = await Promise.allSettled(outputOptions.map(output => result.generate(output)));
85
+ for (const output of generated) {
86
+ if (output.status === 'rejected') throw output.reason;
87
+ }
88
+ }
89
+ } finally {
90
+ await result.close();
91
+ }
92
+ }
93
+ }