vite-plugin-rebundle 1.3.4 → 1.3.6

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.
@@ -25,13 +25,15 @@ declare class Rebundle extends Unit {
25
25
  private onConfig;
26
26
  private onConfigResolved;
27
27
  private onBuildEnd;
28
+ private onGenerateBundle;
28
29
  private onWriteBundle;
29
30
  rebundleChunk(chunk: OutputChunk, bundle: OutputBundle): Promise<boolean | undefined>;
30
- private removeChunk;
31
31
  private readChunkFiles;
32
+ private getChunks;
32
33
  private get dist();
34
+ private prefixed;
35
+ private unprefixed;
33
36
  private readFromDist;
34
- private writeToDist;
35
37
  private removeFromDist;
36
38
  private ensureWs;
37
39
  private removeDirectoryIfEmpty;
package/dist/rebundle.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { safe, Unit } from "@eposlabs/utils";
3
3
  import chalk from "chalk";
4
4
  import { filesize } from "filesize";
5
- import { readdir, readFile, rmdir, stat, unlink, writeFile } from "fs/promises";
5
+ import { readdir, readFile, rmdir, stat, unlink } from "fs/promises";
6
6
  import { dirname, extname, join } from "path";
7
7
  import { getPort } from "portfinder";
8
8
  import { rolldown } from "rolldown";
@@ -29,6 +29,7 @@ var Rebundle = class extends Unit {
29
29
  config: this.onConfig,
30
30
  configResolved: this.onConfigResolved,
31
31
  buildEnd: this.onBuildEnd,
32
+ generateBundle: this.onGenerateBundle,
32
33
  writeBundle: this.onWriteBundle
33
34
  };
34
35
  }
@@ -38,9 +39,8 @@ var Rebundle = class extends Unit {
38
39
  onConfig = async () => {
39
40
  this.port = await getPort({ port: 3100 });
40
41
  return {
41
- define: {
42
- "import.meta.env.REBUNDLE_PORT": JSON.stringify(this.port)
43
- }
42
+ define: { "import.meta.env.REBUNDLE_PORT": JSON.stringify(this.port) },
43
+ build: { sourcemap: false }
44
44
  };
45
45
  };
46
46
  onConfigResolved = async (config) => {
@@ -55,21 +55,31 @@ var Rebundle = class extends Unit {
55
55
  onBuildEnd = (error) => {
56
56
  this.hasError = !!error;
57
57
  };
58
+ onGenerateBundle = async (_options, bundle) => {
59
+ const chunks = this.getChunks(bundle);
60
+ for (const chunk of chunks) {
61
+ if (!chunk.isEntry) continue;
62
+ chunk.fileName = this.prefixed(chunk.fileName);
63
+ }
64
+ };
58
65
  onWriteBundle = async (_output, bundle) => {
59
66
  if (this.hasError) return;
60
67
  if (!this.config) throw this.never;
61
- const chunks = Object.values(bundle).filter((chunkOrAsset) => chunkOrAsset.type === "chunk");
62
- const entryChunks = chunks.filter((chunk) => chunk.isEntry);
63
- const nonEntryChunks = chunks.filter((chunk) => !chunk.isEntry);
68
+ const chunks = this.getChunks(bundle);
64
69
  const modifiedChunkNames = [];
65
70
  await Promise.all(
66
- entryChunks.map(async (chunk) => {
71
+ chunks.map(async (chunk) => {
72
+ if (!chunk.isEntry) return;
67
73
  const modified = await this.rebundleChunk(chunk, bundle);
68
74
  if (modified) modifiedChunkNames.push(chunk.name);
69
75
  })
70
76
  );
71
- for (const chunk of nonEntryChunks) {
72
- await this.removeChunk(chunk, bundle);
77
+ for (const chunk of chunks) {
78
+ if (chunk.isEntry) continue;
79
+ await this.removeFromDist(chunk.fileName);
80
+ delete bundle[chunk.fileName];
81
+ const dir = dirname(join(this.dist, chunk.fileName));
82
+ await this.removeDirectoryIfEmpty(dir);
73
83
  }
74
84
  if (this.config.build.watch && modifiedChunkNames.length > 0) {
75
85
  const ws = await this.ensureWs();
@@ -82,49 +92,34 @@ var Rebundle = class extends Unit {
82
92
  async rebundleChunk(chunk, bundle) {
83
93
  if (!this.config) throw this.never;
84
94
  delete bundle[chunk.fileName];
85
- const chunkPath = join(this.dist, chunk.fileName);
86
- const chunkOptions = this.options[chunk.name] ?? {};
87
95
  const chunkFiles = await this.readChunkFiles(chunk);
88
96
  const chunkFilePaths = Object.keys(chunkFiles);
89
97
  const chunkModified = chunkFilePaths.some((path) => chunkFiles[path] !== this.originalFiles[path]);
90
98
  Object.assign(this.originalFiles, chunkFiles);
91
99
  if (!chunkModified) {
92
- const code2 = this.rebundledFiles[chunk.fileName];
93
- await this.writeToDist(chunk.fileName, code2);
94
- if (chunk.sourcemapFileName) {
95
- const sourcemap = this.rebundledFiles[chunk.sourcemapFileName];
96
- if (sourcemap) await this.writeToDist(chunk.sourcemapFileName, sourcemap);
97
- }
100
+ await this.removeFromDist(chunk.fileName);
98
101
  return false;
99
102
  }
100
- const [build] = await safe(rolldown({ ...chunkOptions.input, input: chunkPath }));
101
- if (!build) return;
102
- const [_, error] = await safe(build.write({ ...chunkOptions.output, file: chunkPath }));
103
- if (error) return;
104
- const { size } = await stat(chunkPath);
103
+ const options = this.options[chunk.name] ?? {};
104
+ const [result] = await safe(async () => {
105
+ const inputPath = join(this.dist, chunk.fileName);
106
+ const outputPath = join(this.dist, this.unprefixed(chunk.fileName));
107
+ const build = await rolldown({ ...options.input, input: inputPath });
108
+ const result2 = await build.write({ ...options.output, sourcemap: false, file: outputPath });
109
+ return result2;
110
+ });
111
+ if (!result) return;
112
+ const { size } = await stat(join(this.dist, chunk.fileName));
105
113
  const _dist_ = chalk.dim(`${this.dist}/`);
106
- const _fileName_ = chalk.cyan(chunk.fileName);
114
+ const _fileName_ = chalk.cyan(this.unprefixed(chunk.fileName));
107
115
  const _rebundle_ = chalk.dim.cyan("[rebundle]");
108
116
  const _size_ = chalk.bold.dim(`${filesize(size)}`);
109
117
  console.log(`${_dist_}${_fileName_} ${_rebundle_} ${_size_}`);
110
- const code = await this.readFromDist(chunk.fileName);
118
+ const code = result.output[0].code;
111
119
  if (!code) throw this.never;
112
120
  this.rebundledFiles[chunk.fileName] = code;
113
- if (chunk.sourcemapFileName) {
114
- const sourcemap = await this.readFromDist(chunk.sourcemapFileName);
115
- if (sourcemap) this.rebundledFiles[chunk.sourcemapFileName] = sourcemap;
116
- }
117
- return true;
118
- }
119
- async removeChunk(chunk, bundle) {
120
121
  await this.removeFromDist(chunk.fileName);
121
- delete bundle[chunk.fileName];
122
- if (chunk.sourcemapFileName) {
123
- await this.removeFromDist(chunk.sourcemapFileName);
124
- delete bundle[chunk.sourcemapFileName];
125
- }
126
- const dir = dirname(join(this.dist, chunk.fileName));
127
- await this.removeDirectoryIfEmpty(dir);
122
+ return true;
128
123
  }
129
124
  async readChunkFiles(chunk) {
130
125
  const files = {};
@@ -137,6 +132,9 @@ var Rebundle = class extends Unit {
137
132
  );
138
133
  return files;
139
134
  }
135
+ getChunks(bundle) {
136
+ return Object.values(bundle).filter((chunkOrAsset) => chunkOrAsset.type === "chunk");
137
+ }
140
138
  // ---------------------------------------------------------------------------
141
139
  // HELPERS
142
140
  // ---------------------------------------------------------------------------
@@ -144,13 +142,16 @@ var Rebundle = class extends Unit {
144
142
  if (!this.config) throw this.never;
145
143
  return this.config.build.outDir;
146
144
  }
145
+ prefixed(fileName) {
146
+ return `rebundle-original-${fileName}`;
147
+ }
148
+ unprefixed(fileName) {
149
+ return fileName.replace("rebundle-original-", "");
150
+ }
147
151
  async readFromDist(path) {
148
152
  const [content] = await safe(readFile(join(this.dist, path), "utf-8"));
149
153
  return content;
150
154
  }
151
- async writeToDist(path, content) {
152
- await writeFile(join(this.dist, path), content, "utf-8");
153
- }
154
155
  async removeFromDist(path) {
155
156
  await safe(unlink(join(this.dist, path)));
156
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-rebundle",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "imkost",
@@ -27,7 +27,7 @@
27
27
  "dist"
28
28
  ],
29
29
  "dependencies": {
30
- "@eposlabs/utils": "^1.2.0",
30
+ "@eposlabs/utils": "^1.2.2",
31
31
  "@types/ws": "^8.18.1",
32
32
  "chalk": "^5.6.2",
33
33
  "filesize": "^11.0.13",
package/readme.md CHANGED
@@ -4,9 +4,7 @@ A Vite plugin that guarantees **one standalone file per entry point**. Each entr
4
4
 
5
5
  ## Why?
6
6
 
7
- Sometimes you need bundles without dynamic imports. Vite/Rollup don’t provide this option when building with multiple entries. This plugin solves it by rebundling Vite’s output with rolldown to enforce single-file output.
8
-
9
- > ⚠️ This plugin runs **only during** `vite build`. It does not affect the Vite dev server.
7
+ Sometimes you need bundles without dynamic imports. Vite/Rollup don’t provide this option when building with multiple entries. `vite-plugin-rebundle` solves this issue by rebundling Vite’s output with rolldown to enforce single-file output. This plugin runs only during `vite build`, and it does not affect the Vite dev server.
10
8
 
11
9
  ## Installation
12
10
 
@@ -18,7 +16,7 @@ npm install -D vite-plugin-rebundle
18
16
 
19
17
  ```javascript
20
18
  import { defineConfig } from 'vite'
21
- import rebundle from 'vite-plugin-rebundle'
19
+ import { rebundle } from 'vite-plugin-rebundle'
22
20
 
23
21
  export default defineConfig({
24
22
  plugins: [rebundle()],
@@ -38,18 +36,24 @@ export default defineConfig({
38
36
 
39
37
  ## Options
40
38
 
41
- You can provide **rolldown options per entry point**. This is useful, for example, to inject custom define variables into specific bundles:
39
+ You can provide rolldown options per entry point. This is useful, for example, to inject custom define variables into specific bundles:
42
40
 
43
41
  ```javascript
44
42
  export default defineConfig({
45
43
  plugins: [
46
44
  rebundle({
47
45
  app: {
48
- output: { define: { BUNDLE_NAME: JSON.stringify('app') } },
46
+ output: {
47
+ define: { BUNDLE_NAME: JSON.stringify('app') },
48
+ },
49
49
  },
50
50
  libs: {
51
- input: { keepNames: true },
52
- output: { define: { BUNDLE_NAME: JSON.stringify('libs') } },
51
+ input: {
52
+ keepNames: true,
53
+ },
54
+ output: {
55
+ define: { BUNDLE_NAME: JSON.stringify('libs') },
56
+ },
53
57
  },
54
58
  }),
55
59
  ],
@@ -75,11 +79,4 @@ When you run `vite build`, Rollup normally outputs multiple chunks per entry if
75
79
  - Vite still handles the initial build (tree-shaking, asset pipeline, etc.).
76
80
  - Afterward, each entry is passed through rolldown.
77
81
  - The final result is one .js file per entry with no dynamic imports or shared chunks.
78
- - If sourcemaps are enabled, they are preserved the rebundled files include correct mappings.
79
-
80
- ## Limitations
81
-
82
- - **Build only** — this plugin affects only vite build, not the dev server.
83
- - **Bundle size** — since all shared chunks are inlined, resulting files can be larger than Rollup’s default output.
84
- - **Dynamic imports** — intentionally disabled; any import() calls are bundled inline.
85
- - **Code-splitting features** — manual chunks and vendor splitting are ignored.
82
+ - Sourcemaps are ignored, as they would be inaccurate after rebundling.
package/src/rebundle.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { safe, Unit } from '@eposlabs/utils'
2
2
  import chalk from 'chalk'
3
3
  import { filesize } from 'filesize'
4
- import { readdir, readFile, rmdir, stat, unlink, writeFile } from 'node:fs/promises'
4
+ import { readdir, readFile, rmdir, stat, unlink } from 'node:fs/promises'
5
5
  import { dirname, extname, join } from 'node:path'
6
6
  import { getPort } from 'portfinder'
7
7
  import { rolldown, type InputOptions, type OutputOptions } from 'rolldown'
@@ -43,6 +43,7 @@ export class Rebundle extends Unit {
43
43
  config: this.onConfig,
44
44
  configResolved: this.onConfigResolved,
45
45
  buildEnd: this.onBuildEnd,
46
+ generateBundle: this.onGenerateBundle,
46
47
  writeBundle: this.onWriteBundle,
47
48
  }
48
49
  }
@@ -53,10 +54,10 @@ export class Rebundle extends Unit {
53
54
 
54
55
  private onConfig = async () => {
55
56
  this.port = await getPort({ port: 3100 })
57
+
56
58
  return {
57
- define: {
58
- 'import.meta.env.REBUNDLE_PORT': JSON.stringify(this.port),
59
- },
59
+ define: { 'import.meta.env.REBUNDLE_PORT': JSON.stringify(this.port) },
60
+ build: { sourcemap: false },
60
61
  }
61
62
  }
62
63
 
@@ -77,26 +78,42 @@ export class Rebundle extends Unit {
77
78
  this.hasError = !!error
78
79
  }
79
80
 
81
+ private onGenerateBundle = async (_options: NormalizedOutputOptions, bundle: OutputBundle) => {
82
+ // Prefix all entry chunks, so Vite writes to temporary files instead of final files
83
+ const chunks = this.getChunks(bundle)
84
+ for (const chunk of chunks) {
85
+ if (!chunk.isEntry) continue
86
+ chunk.fileName = this.prefixed(chunk.fileName)
87
+ }
88
+ }
89
+
80
90
  private onWriteBundle = async (_output: NormalizedOutputOptions, bundle: OutputBundle) => {
81
91
  if (this.hasError) return
82
92
  if (!this.config) throw this.never
83
93
 
84
- const chunks = Object.values(bundle).filter(chunkOrAsset => chunkOrAsset.type === 'chunk')
85
- const entryChunks = chunks.filter(chunk => chunk.isEntry)
86
- const nonEntryChunks = chunks.filter(chunk => !chunk.isEntry)
94
+ const chunks = this.getChunks(bundle)
95
+ const modifiedChunkNames: string[] = []
87
96
 
88
97
  // Rebundle entry chunks
89
- const modifiedChunkNames: string[] = []
90
98
  await Promise.all(
91
- entryChunks.map(async chunk => {
99
+ chunks.map(async chunk => {
100
+ if (!chunk.isEntry) return
92
101
  const modified = await this.rebundleChunk(chunk, bundle)
93
102
  if (modified) modifiedChunkNames.push(chunk.name)
94
103
  }),
95
104
  )
96
105
 
97
106
  // Remove non-entry chunks
98
- for (const chunk of nonEntryChunks) {
99
- await this.removeChunk(chunk, bundle)
107
+ for (const chunk of chunks) {
108
+ if (chunk.isEntry) continue
109
+
110
+ // Remove from dist and bundle
111
+ await this.removeFromDist(chunk.fileName)
112
+ delete bundle[chunk.fileName]
113
+
114
+ // Recursively remove containing directory if empty
115
+ const dir = dirname(join(this.dist, chunk.fileName))
116
+ await this.removeDirectoryIfEmpty(dir)
100
117
  }
101
118
 
102
119
  // Notify about modified chunks
@@ -116,78 +133,53 @@ export class Rebundle extends Unit {
116
133
  // Delete chunk from bundle to hide log for rolldown-vite. Call for rollup for consistency.
117
134
  delete bundle[chunk.fileName]
118
135
 
119
- const chunkPath = join(this.dist, chunk.fileName)
120
- const chunkOptions = this.options[chunk.name] ?? {}
121
-
122
136
  // Read chunk files
123
137
  const chunkFiles = await this.readChunkFiles(chunk)
124
138
 
125
- // Check if chunk was modified
139
+ // Check if some of chunk files were modified
126
140
  const chunkFilePaths = Object.keys(chunkFiles)
127
141
  const chunkModified = chunkFilePaths.some(path => chunkFiles[path] !== this.originalFiles[path])
128
142
 
129
- // Update original files content
143
+ // Save chunk files content for next comparison
130
144
  Object.assign(this.originalFiles, chunkFiles)
131
145
 
132
- // Chunk was not modified? -> Use previous content
146
+ // Chunk was not modified? -> Don't rebundle, just remove Vite's output
133
147
  if (!chunkModified) {
134
- // Overwrite vite's output with previously rebundled code
135
- const code = this.rebundledFiles[chunk.fileName]
136
- await this.writeToDist(chunk.fileName, code)
137
-
138
- // Overwrite vite's sourcemap
139
- if (chunk.sourcemapFileName) {
140
- const sourcemap = this.rebundledFiles[chunk.sourcemapFileName]
141
- if (sourcemap) await this.writeToDist(chunk.sourcemapFileName, sourcemap)
142
- }
143
-
144
- // Return not modified status
148
+ await this.removeFromDist(chunk.fileName)
145
149
  return false
146
150
  }
147
151
 
148
152
  // Build with rolldown
149
- const [build] = await safe(rolldown({ ...chunkOptions.input, input: chunkPath }))
150
- if (!build) return
151
- const [_, error] = await safe(build.write({ ...chunkOptions.output, file: chunkPath }))
152
- if (error) return
153
+ const options = this.options[chunk.name] ?? {}
154
+ const [result] = await safe(async () => {
155
+ const inputPath = join(this.dist, chunk.fileName)
156
+ const outputPath = join(this.dist, this.unprefixed(chunk.fileName))
157
+ const build = await rolldown({ ...options.input, input: inputPath })
158
+ const result = await build.write({ ...options.output, sourcemap: false, file: outputPath })
159
+ return result
160
+ })
161
+ if (!result) return
153
162
 
154
163
  // Log successful build
155
- const { size } = await stat(chunkPath)
164
+ const { size } = await stat(join(this.dist, chunk.fileName))
156
165
  const _dist_ = chalk.dim(`${this.dist}/`)
157
- const _fileName_ = chalk.cyan(chunk.fileName)
166
+ const _fileName_ = chalk.cyan(this.unprefixed(chunk.fileName))
158
167
  const _rebundle_ = chalk.dim.cyan('[rebundle]')
159
168
  const _size_ = chalk.bold.dim(`${filesize(size)}`)
160
169
  console.log(`${_dist_}${_fileName_} ${_rebundle_} ${_size_}`)
161
170
 
162
171
  // Save code
163
- const code = await this.readFromDist(chunk.fileName)
172
+ const code = result.output[0].code
164
173
  if (!code) throw this.never
165
174
  this.rebundledFiles[chunk.fileName] = code
166
175
 
167
- // Save sourcemap
168
- if (chunk.sourcemapFileName) {
169
- const sourcemap = await this.readFromDist(chunk.sourcemapFileName)
170
- if (sourcemap) this.rebundledFiles[chunk.sourcemapFileName] = sourcemap
171
- }
176
+ // Remove Vite's output
177
+ await this.removeFromDist(chunk.fileName)
172
178
 
173
179
  // Return modified status
174
180
  return true
175
181
  }
176
182
 
177
- private async removeChunk(chunk: OutputChunk, bundle: OutputBundle) {
178
- await this.removeFromDist(chunk.fileName)
179
- delete bundle[chunk.fileName]
180
-
181
- if (chunk.sourcemapFileName) {
182
- await this.removeFromDist(chunk.sourcemapFileName)
183
- delete bundle[chunk.sourcemapFileName]
184
- }
185
-
186
- // Recursively remove containing directory if empty
187
- const dir = dirname(join(this.dist, chunk.fileName))
188
- await this.removeDirectoryIfEmpty(dir)
189
- }
190
-
191
183
  private async readChunkFiles(chunk: OutputChunk) {
192
184
  const files: Record<string, string> = {}
193
185
  const usedPaths = [chunk.fileName, ...chunk.imports]
@@ -202,6 +194,10 @@ export class Rebundle extends Unit {
202
194
  return files
203
195
  }
204
196
 
197
+ private getChunks(bundle: OutputBundle) {
198
+ return Object.values(bundle).filter(chunkOrAsset => chunkOrAsset.type === 'chunk')
199
+ }
200
+
205
201
  // ---------------------------------------------------------------------------
206
202
  // HELPERS
207
203
  // ---------------------------------------------------------------------------
@@ -211,15 +207,19 @@ export class Rebundle extends Unit {
211
207
  return this.config.build.outDir
212
208
  }
213
209
 
210
+ private prefixed(fileName: string) {
211
+ return `rebundle-original-${fileName}`
212
+ }
213
+
214
+ private unprefixed(fileName: string) {
215
+ return fileName.replace('rebundle-original-', '')
216
+ }
217
+
214
218
  private async readFromDist(path: string) {
215
219
  const [content] = await safe(readFile(join(this.dist, path), 'utf-8'))
216
220
  return content
217
221
  }
218
222
 
219
- private async writeToDist(path: string, content: string) {
220
- await writeFile(join(this.dist, path), content, 'utf-8')
221
- }
222
-
223
223
  private async removeFromDist(path: string) {
224
224
  await safe(unlink(join(this.dist, path)))
225
225
  }