vite-plugin-rebundle 1.0.0 → 1.1.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/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { type Options } from './rebundle.js';
2
- export default function rebundle(options?: Options): import("vite").Plugin<any>;
1
+ import { type OptionsInput } from './rebundle.js';
2
+ export default function rebundle(options?: OptionsInput): import("vite").Plugin<any>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Rebundle } from './rebundle.js';
2
2
  export default function rebundle(options = {}) {
3
- return new Rebundle(options).plugin;
3
+ return new Rebundle(options).vite;
4
4
  }
@@ -1,25 +1,24 @@
1
+ import * as $utils from '@eposlabs/utils';
1
2
  import type { BuildOptions } from 'esbuild';
2
3
  import type { Plugin } from 'vite';
3
- export type Options = BuildOptions & {
4
- bundles?: {
5
- [chunkName: string]: BuildOptions;
6
- };
4
+ export type Options = {
5
+ [chunkName: string]: BuildOptions;
7
6
  };
8
- export type ContentMap = {
9
- [path: string]: string;
10
- };
11
- export declare class Rebundle {
7
+ export type OptionsInput = Options | (() => Options | Promise<Options>);
8
+ export declare class Rebundle extends $utils.Unit {
12
9
  private options;
13
10
  private config;
14
- private emptyOutDir;
15
- private content;
16
- constructor(options: Options);
17
- get plugin(): Plugin;
18
- private onConfig;
11
+ private chunkFiles;
12
+ private rebundledContent;
13
+ constructor(options: OptionsInput);
14
+ get vite(): Plugin;
19
15
  private onConfigResolved;
20
16
  private onWriteBundle;
21
17
  private get outDir();
22
- private getChunkContentMap;
23
- private without;
24
- private get never();
18
+ private outPath;
19
+ private outRead;
20
+ private outWrite;
21
+ private getOptions;
22
+ private readChunkFiles;
23
+ private removeDirectoryIfEmpty;
25
24
  }
package/dist/rebundle.js CHANGED
@@ -1,22 +1,22 @@
1
- import $chalk from 'chalk';
2
1
  import * as $esbuild from 'esbuild';
3
- import * as $merge from 'merge-anything';
4
2
  import * as $fs from 'node:fs/promises';
5
3
  import * as $path from 'node:path';
6
- export class Rebundle {
4
+ import * as $utils from '@eposlabs/utils';
5
+ import $chalk from 'chalk';
6
+ export class Rebundle extends $utils.Unit {
7
7
  options;
8
8
  config = null;
9
- emptyOutDir = true;
10
- content = {};
9
+ chunkFiles = {};
10
+ rebundledContent = {};
11
11
  constructor(options) {
12
+ super();
12
13
  this.options = options;
13
14
  }
14
- get plugin() {
15
+ get vite() {
15
16
  return {
16
17
  name: 'vite-plugin-rebundle',
17
18
  apply: 'build',
18
19
  enforce: 'post',
19
- config: this.onConfig,
20
20
  configResolved: this.onConfigResolved,
21
21
  writeBundle: this.onWriteBundle,
22
22
  };
@@ -24,23 +24,9 @@ export class Rebundle {
24
24
  // ---------------------------------------------------------------------------
25
25
  // HOOKS
26
26
  // ---------------------------------------------------------------------------
27
- onConfig = (config) => {
28
- // Save user's `emptyOutDir` value
29
- this.emptyOutDir = config.build?.emptyOutDir ?? true;
30
- // Make `emptyOutDir = false` to prevent Vite from deleting rebundled output files
31
- return {
32
- build: {
33
- emptyOutDir: false,
34
- },
35
- };
36
- };
37
27
  onConfigResolved = async (config) => {
38
28
  // Save resolved config
39
29
  this.config = config;
40
- // Cleanup output directory if user's `emptyOutDir` is `true`
41
- if (this.emptyOutDir) {
42
- await $fs.rmdir(this.outDir, { recursive: true });
43
- }
44
30
  // Hide .js files from output logs
45
31
  const info = this.config.logger.info;
46
32
  this.config.logger.info = (message, options) => {
@@ -51,87 +37,86 @@ export class Rebundle {
51
37
  };
52
38
  };
53
39
  onWriteBundle = async (output, bundle) => {
40
+ const options = await this.getOptions();
54
41
  // Get entry js chunks
55
42
  const entryJsChunks = Object.values(bundle)
56
43
  .filter(chunkOrAsset => chunkOrAsset.type === 'chunk')
57
44
  .filter(chunk => chunk.isEntry && $path.extname(chunk.fileName) === '.js');
58
- // Get non-entry chunks (.js, .js.map)
59
- const nonEntryChunks = Object.values(bundle)
60
- .filter(chunkOrAsset => chunkOrAsset.type === 'chunk')
61
- .filter(chunk => !chunk.isEntry);
62
45
  // Rebundle entry js chunks with esbuild
63
46
  await Promise.all(entryJsChunks.map(async (chunk) => {
64
47
  if (!this.config)
65
48
  throw this.never;
66
- // Check if chunk has been modified
67
- const content = await this.getChunkContentMap(chunk);
68
- const usedPaths = Object.keys(content);
69
- const changed = usedPaths.some(path => content[path] !== this.content[path]);
70
- // Update content map
71
- Object.assign(this.content, content);
72
- // Not modified? -> Skip
73
- if (!changed)
74
- return;
75
- // Prepare chunk path and build options
76
- const chunkPath = $path.join(this.outDir, chunk.fileName);
77
- const commonBuildOptions = this.without(this.options, 'bundles');
78
- const chunkBuildOptions = this.options.bundles?.[chunk.name] ?? {};
79
- // Build with esbuild
80
- await $esbuild.build({
81
- minify: Boolean(this.config.build.minify),
82
- sourcemap: Boolean(this.config.build.sourcemap),
83
- ...$merge.mergeAndConcat(commonBuildOptions, chunkBuildOptions),
84
- outfile: chunkPath,
85
- entryPoints: [chunkPath],
86
- bundle: true,
87
- allowOverwrite: true,
88
- plugins: [
89
- ...(commonBuildOptions.plugins ?? []),
90
- ...(chunkBuildOptions.plugins ?? []),
91
- {
92
- name: 'logger',
93
- setup: build => {
94
- build.onEnd(result => {
95
- if (result.errors.length > 0)
96
- return;
97
- const dir = $chalk.dim(`${this.outDir}/`);
98
- const name = $chalk.cyan(chunk.fileName);
99
- const tag = $chalk.dim.cyan('rebundle');
100
- console.log(`${dir}${name} ${tag}`);
101
- });
49
+ const chunkPath = this.outPath(chunk.fileName);
50
+ const chunkBuildOptions = options[chunk.name] ?? {};
51
+ const chunkFiles = await this.readChunkFiles(chunk);
52
+ const chunkFilePaths = Object.keys(chunkFiles);
53
+ const chunkChanged = chunkFilePaths.some(path => chunkFiles[path] !== this.chunkFiles[path]);
54
+ // Update files cache
55
+ Object.assign(this.chunkFiles, chunkFiles);
56
+ // Modified? -> Rebundle
57
+ if (chunkChanged) {
58
+ // Build with esbuild
59
+ await $esbuild.build({
60
+ sourcemap: Boolean(this.config.build.sourcemap),
61
+ ...chunkBuildOptions,
62
+ outfile: chunkPath,
63
+ entryPoints: [chunkPath],
64
+ bundle: true,
65
+ minify: false,
66
+ allowOverwrite: true,
67
+ plugins: [
68
+ ...(chunkBuildOptions.plugins ?? []),
69
+ {
70
+ name: 'logger',
71
+ setup: build => {
72
+ build.onEnd(result => {
73
+ if (result.errors.length > 0)
74
+ return;
75
+ const outDir = $chalk.dim(`${this.outDir}/`);
76
+ const fileName = $chalk.cyan(chunk.fileName);
77
+ const rebundleTag = $chalk.dim.cyan('rebundle');
78
+ console.log(`${outDir}${fileName} ${rebundleTag}`);
79
+ });
80
+ },
102
81
  },
103
- },
104
- ],
105
- });
106
- // Update chunk and corresponding sourcemap chunk
107
- chunk.code = await $fs.readFile(chunkPath, 'utf-8');
82
+ ],
83
+ });
84
+ // Save chunk content
85
+ this.rebundledContent[chunk.fileName] = await this.outRead(chunk.fileName);
86
+ // Save sourcemap content
87
+ if (chunk.sourcemapFileName) {
88
+ this.rebundledContent[chunk.sourcemapFileName] = await this.outRead(chunk.sourcemapFileName);
89
+ }
90
+ }
91
+ // Overwrite chunk
92
+ await this.outWrite(chunk.fileName, this.rebundledContent[chunk.fileName]);
93
+ chunk.code = this.rebundledContent[chunk.fileName];
94
+ // Overwrite sourcemap
108
95
  if (chunk.sourcemapFileName) {
109
- const sourcemapChunk = bundle[chunk.sourcemapFileName];
110
- if (sourcemapChunk.type !== 'asset')
96
+ const sourcemapAsset = bundle[chunk.sourcemapFileName];
97
+ if (sourcemapAsset.type !== 'asset')
111
98
  throw this.never;
112
- const sourcemapChunkPath = $path.join(this.outDir, chunk.sourcemapFileName);
113
- sourcemapChunk.source = await $fs.readFile(sourcemapChunkPath, 'utf-8');
99
+ await this.outWrite(chunk.sourcemapFileName, this.rebundledContent[chunk.sourcemapFileName]);
100
+ sourcemapAsset.source = this.rebundledContent[chunk.sourcemapFileName];
114
101
  }
115
102
  }));
103
+ // Get non-entry chunks (.js, .js.map)
104
+ const nonEntryChunks = Object.values(bundle)
105
+ .filter(chunkOrAsset => chunkOrAsset.type === 'chunk')
106
+ .filter(chunk => !chunk.isEntry);
116
107
  // Remove all non-entry chunks
117
108
  for (const chunk of nonEntryChunks) {
118
- // Remove file itself
119
- const path = $path.resolve(this.outDir, chunk.fileName);
120
- await $fs.unlink(path);
109
+ // Remove chunk
110
+ await $fs.unlink(this.outPath(chunk.fileName));
121
111
  delete bundle[chunk.fileName];
122
- // Remove sourcemap if any
112
+ // Remove sourcemap
123
113
  if (chunk.sourcemapFileName) {
124
- const sourcemapPath = $path.resolve(this.outDir, chunk.sourcemapFileName);
125
- await $fs.unlink(sourcemapPath);
114
+ await $fs.unlink(this.outPath(chunk.sourcemapFileName));
126
115
  delete bundle[chunk.sourcemapFileName];
127
116
  }
128
- // Remove containing directory if empty
129
- const dir = $path.dirname(path);
130
- const files = await $fs.readdir(dir);
131
- if (files.length === 0) {
132
- console.warn(await $fs.stat(dir));
133
- await $fs.rmdir(dir);
134
- }
117
+ // Remove containing directory if empty (recursively)
118
+ const dir = $path.dirname(this.outPath(chunk.fileName));
119
+ await this.removeDirectoryIfEmpty(dir);
135
120
  }
136
121
  };
137
122
  // ---------------------------------------------------------------------------
@@ -142,21 +127,32 @@ export class Rebundle {
142
127
  throw this.never;
143
128
  return this.config.build.outDir;
144
129
  }
145
- async getChunkContentMap(chunk) {
146
- const content = {};
147
- const usedPaths = [chunk.fileName, ...chunk.imports];
148
- await Promise.all(usedPaths.map(async (path) => {
149
- const fullPath = $path.join(this.outDir, path);
150
- content[path] = await $fs.readFile(fullPath, 'utf-8');
151
- }));
152
- return content;
130
+ outPath(path) {
131
+ return $path.join(this.outDir, path);
132
+ }
133
+ async outRead(path) {
134
+ return await $fs.readFile(this.outPath(path), 'utf-8');
135
+ }
136
+ async outWrite(path, content) {
137
+ await $fs.writeFile(this.outPath(path), content, 'utf-8');
153
138
  }
154
- without(object, key) {
155
- const result = { ...object };
156
- delete result[key];
157
- return result;
139
+ async getOptions() {
140
+ if (typeof this.options !== 'function')
141
+ return this.options;
142
+ return await this.options();
143
+ }
144
+ async readChunkFiles(chunk) {
145
+ const files = {};
146
+ await Promise.all([chunk.fileName, ...chunk.imports].map(async (path) => {
147
+ files[path] = await this.outRead(path);
148
+ }));
149
+ return files;
158
150
  }
159
- get never() {
160
- throw new Error('never');
151
+ async removeDirectoryIfEmpty(dir) {
152
+ const files = await $fs.readdir(dir);
153
+ if (files.length > 0)
154
+ return;
155
+ await $fs.rmdir(dir);
156
+ await this.removeDirectoryIfEmpty($path.dirname(dir));
161
157
  }
162
158
  }
package/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "vite-plugin-rebundle",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "imkost",
7
7
  "description": "Vite plugin that forces single-file output per entry. Ensures each entry point is bundled into a standalone file without code-splitting.",
8
+ "keywords": [
9
+ "vite",
10
+ "vite-plugin"
11
+ ],
8
12
  "scripts": {
9
13
  "dev": "rm -rf dist && tsc --watch",
10
14
  "build": "rm -rf dist && tsc",
11
15
  "lint": "eslint src",
12
- "release": "npm run build && npm publish",
13
- "release:patch": "npm version patch && npm run release",
14
- "release:minor": "npm version minor && npm run release"
16
+ "release": "npm version patch && npm run release:raw",
17
+ "release:raw": "npm run build && npm publish",
18
+ "release:minor": "npm version minor && npm run release:raw"
15
19
  },
16
20
  "exports": {
17
21
  "import": "./dist/index.js"
@@ -19,12 +23,8 @@
19
23
  "files": [
20
24
  "dist"
21
25
  ],
22
- "keywords": [
23
- "vite",
24
- "vite-plugin"
25
- ],
26
26
  "dependencies": {
27
27
  "chalk": "^5.6.0",
28
- "merge-anything": "^6.0.6"
28
+ "esbuild": "^0.25.9"
29
29
  }
30
30
  }