quickbundle 2.16.0 → 3.0.0-next-ad3f6ff

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.
Files changed (2) hide show
  1. package/dist/index.js +464 -572
  2. package/package.json +8 -13
package/dist/index.js CHANGED
@@ -1,615 +1,507 @@
1
- import { helpers, termost } from 'termost';
2
- import { finished } from 'node:stream/promises';
3
- import { Readable } from 'node:stream';
4
- import { resolve, dirname, join, basename } from 'node:path';
5
- import { copyFile as copyFile$1, rename, mkdir, writeFile as writeFile$1, rm, readFile as readFile$1 } from 'node:fs/promises';
6
- import { createWriteStream } from 'node:fs';
7
- import decompress from 'decompress';
8
- import { watch as watch$1, rollup } from 'rollup';
9
- import { createRequire } from 'node:module';
10
- import { swc } from 'rollup-plugin-swc3';
11
- import externals from 'rollup-plugin-node-externals';
12
- import dts from 'rollup-plugin-dts';
13
- import url from '@rollup/plugin-url';
14
- import { nodeResolve } from '@rollup/plugin-node-resolve';
15
- import json from '@rollup/plugin-json';
16
- import commonjs from '@rollup/plugin-commonjs';
17
- import os from 'node:os';
18
- import { gzipSize } from 'gzip-size';
1
+ import { helpers, termost } from "termost";
2
+ import { gzipSize } from "gzip-size";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import { rolldown, watch } from "rolldown";
5
+ import url from "@rollup/plugin-url";
6
+ import { createRequire } from "node:module";
7
+ import { dts } from "rolldown-plugin-dts";
8
+ import externals from "rollup-plugin-node-externals";
9
+ import decompress from "decompress";
10
+ import { createWriteStream } from "node:fs";
11
+ import { copyFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
12
+ import { Readable } from "node:stream";
13
+ import { finished } from "node:stream/promises";
14
+ import os from "node:os";
19
15
 
20
- /**
21
- * Resolve a relative path from the Quickbundle node modules directory.
22
- * @param paths - Relative paths.
23
- * @returns The resolved absolute path.
24
- * @example
25
- * resolveFromInternalDirectory("dist", "node");
26
- */ const resolveFromInternalDirectory = (...paths)=>{
27
- return resolve(import.meta.dirname, "../", ...paths);
16
+ //#region package.json
17
+ var name = "quickbundle";
18
+ var version = "3.0.0-next-ad3f6ff";
19
+
20
+ //#endregion
21
+ //#region src/bundler/build.ts
22
+ const build = async (input) => {
23
+ process.env.NODE_ENV ??= "production";
24
+ const { data: configurations } = input;
25
+ const output = [];
26
+ for (const config of configurations) {
27
+ const initialTime = Date.now();
28
+ const bundle = await rolldown(config);
29
+ if (config.output) {
30
+ const outputEntries = Array.isArray(config.output) ? config.output : [config.output];
31
+ const promises = [];
32
+ for (const outputEntry of outputEntries) promises.push(new Promise((resolve, reject) => {
33
+ bundle.write(outputEntry).then(({ output: rolldownOutput }) => {
34
+ resolve({
35
+ elapsedTime: Date.now() - initialTime,
36
+ filePath: join(outputEntry.dir ?? "", rolldownOutput.find((item) => item.type === "chunk" && item.isEntry)?.fileName ?? "")
37
+ });
38
+ }).catch((error) => {
39
+ if (error instanceof Error) reject(error);
40
+ });
41
+ }));
42
+ output.push(...await Promise.all(promises));
43
+ }
44
+ }
45
+ return output;
28
46
  };
47
+
48
+ //#endregion
49
+ //#region src/helpers.ts
29
50
  /**
30
- * Resolve a relative path from the current working project directory.
31
- * @param paths - Relative paths.
32
- * @returns The resolved absolute path.
33
- * @example
34
- * resolveFromExternalDirectory("package.json");
35
- */ const resolveFromExternalDirectory = (...paths)=>{
36
- return resolve(process.cwd(), ...paths);
51
+ * Resolve a relative path from the Quickbundle node modules directory.
52
+ * @param paths - Relative paths.
53
+ * @returns The resolved absolute path.
54
+ * @example
55
+ * resolveFromInternalDirectory("dist", "node");
56
+ */
57
+ const resolveFromInternalDirectory = (...paths) => {
58
+ return resolve(import.meta.dirname, "../", ...paths);
37
59
  };
38
- const createRegExpMatcher = (regex)=>{
39
- return (value)=>{
40
- return regex.exec(value)?.groups;
41
- };
42
- };
43
- const createDirectory = async (path)=>{
44
- await mkdir(path, {
45
- recursive: true
46
- });
60
+ /**
61
+ * Resolve a relative path from the current working project directory.
62
+ * @param paths - Relative paths.
63
+ * @returns The resolved absolute path.
64
+ * @example
65
+ * resolveFromExternalDirectory("package.json");
66
+ */
67
+ const resolveFromExternalDirectory = (...paths) => {
68
+ return resolve(process.cwd(), ...paths);
47
69
  };
48
- const copyFile = async (fromPath, toPath)=>{
49
- await createDirectory(dirname(toPath));
50
- await copyFile$1(fromPath, toPath);
70
+ const createRegExpMatcher = (regex) => {
71
+ return (value) => {
72
+ return regex.exec(value)?.groups;
73
+ };
51
74
  };
52
- const removePath = async (path)=>{
53
- await rm(path, {
54
- force: true,
55
- recursive: true
56
- });
75
+ const createDirectory = async (path) => {
76
+ await mkdir(path, { recursive: true });
57
77
  };
58
- const readFile = async (filePath)=>{
59
- return readFile$1(filePath);
78
+ const copyFile$1 = async (fromPath, toPath) => {
79
+ await createDirectory(dirname(toPath));
80
+ await copyFile(fromPath, toPath);
60
81
  };
61
- const writeFile = async (filePath, content)=>{
62
- await createDirectory(dirname(filePath));
63
- await writeFile$1(filePath, content, "utf8");
82
+ const removePath = async (path) => {
83
+ await rm(path, {
84
+ force: true,
85
+ recursive: true
86
+ });
64
87
  };
65
- const download = async (url, filePath)=>{
66
- await createDirectory(dirname(filePath));
67
- const { body, ok, status, statusText } = await fetch(url);
68
- if (!ok) {
69
- throw new Error(`An error ocurred while downloading \`${url}\`. Received \`${status}\` status code with the following message \`${statusText}\`.`);
70
- }
71
- if (!body) {
72
- throw new Error(`Empty body received while downloading \`${url}\`.`);
73
- }
74
- await finished(Readable.fromWeb(body).pipe(createWriteStream(filePath)));
88
+ const readFile$1 = async (filePath) => {
89
+ return readFile(filePath);
75
90
  };
76
- const unzip = async (input, output)=>{
77
- const { targetedArchivePath } = input;
78
- const { directoryPath } = output;
79
- await decompress(input.path, directoryPath, {
80
- filter (file) {
81
- return file.path === targetedArchivePath;
82
- }
83
- });
84
- await rename(join(directoryPath, targetedArchivePath), join(directoryPath, output.filename));
91
+ const writeFile$1 = async (filePath, content) => {
92
+ await createDirectory(dirname(filePath));
93
+ await writeFile(filePath, content, "utf8");
85
94
  };
86
- const createCommand = (program, input)=>{
87
- return program.command(input).option({
88
- key: "minification",
89
- name: "minification",
90
- description: "Enable minification",
91
- defaultValue: false
92
- }).option({
93
- key: "sourceMaps",
94
- name: "source-maps",
95
- description: "Enable source maps generation",
96
- defaultValue: false
97
- });
95
+ const download = async (url, filePath) => {
96
+ await createDirectory(dirname(filePath));
97
+ const { body, ok, status, statusText } = await fetch(url);
98
+ if (!ok) throw new Error(`An error ocurred while downloading \`${url}\`. Received \`${status}\` status code with the following message \`${statusText}\`.`);
99
+ if (!body) throw new Error(`Empty body received while downloading \`${url}\`.`);
100
+ await finished(Readable.fromWeb(body).pipe(createWriteStream(filePath)));
98
101
  };
99
-
100
- const onLog = (_, log)=>{
101
- if (log.message.includes("Generated an empty chunk")) return;
102
+ const unzip = async (input, output) => {
103
+ const { targetedArchivePath } = input;
104
+ const { directoryPath } = output;
105
+ await decompress(input.path, directoryPath, { filter(file) {
106
+ return file.path === targetedArchivePath;
107
+ } });
108
+ await rename(join(directoryPath, targetedArchivePath), join(directoryPath, output.filename));
102
109
  };
103
- const isRecord = (value)=>{
104
- return typeof value === "object" && value !== null && !Array.isArray(value);
110
+ const createCommand = (program, input) => {
111
+ return program.command(input).option({
112
+ defaultValue: false,
113
+ description: "Enable minification",
114
+ key: "minification",
115
+ name: "minification"
116
+ }).option({
117
+ defaultValue: false,
118
+ description: "Enable source maps generation",
119
+ key: "sourceMaps",
120
+ name: "source-maps"
121
+ });
105
122
  };
106
123
 
107
- const watch = (input)=>{
108
- process.env.NODE_ENV ??= "development";
109
- const watcher = watch$1(input.data.map((configItem)=>({
110
- ...configItem,
111
- onLog
112
- })));
113
- let startDuration;
114
- console.clear();
115
- watcher.on("event", async (event)=>{
116
- switch(event.code){
117
- case "START":
118
- {
119
- startDuration = Date.now();
120
- clearLog("Build in progress…", {
121
- type: "information"
122
- });
123
- return;
124
- }
125
- case "BUNDLE_END":
126
- {
127
- await event.result.close();
128
- break;
129
- }
130
- case "END":
131
- {
132
- const duration = Date.now() - startDuration;
133
- clearLog(`Build done in ${duration}ms (at ${new Date().toLocaleTimeString()})`, {
134
- type: "success"
135
- });
136
- return;
137
- }
138
- case "ERROR":
139
- {
140
- const { error } = event;
141
- clearLog(error.message, {
142
- type: "error"
143
- });
144
- console.error("\n", error);
145
- return;
146
- }
147
- }
148
- });
149
- };
150
- const clearLog = (...input)=>{
151
- console.clear();
152
- helpers.message(...input);
124
+ //#endregion
125
+ //#region src/bundler/helpers.ts
126
+ const isRecord = (value) => {
127
+ return typeof value === "object" && value !== null && !Array.isArray(value);
153
128
  };
154
129
 
155
- const require$1 = createRequire(import.meta.url);
156
- const PKG = require$1(resolveFromExternalDirectory("package.json"));
130
+ //#endregion
131
+ //#region src/bundler/config.ts
132
+ const PKG = createRequire(import.meta.url)(resolveFromExternalDirectory("package.json"));
157
133
  const DEFAULT_OPTIONS = {
158
- minification: false,
159
- sourceMaps: false,
160
- standalone: false
134
+ minification: false,
135
+ sourceMaps: false,
136
+ standalone: false
161
137
  };
162
- const createConfiguration = (options = DEFAULT_OPTIONS)=>{
163
- const buildableExports = getBuildableExports(options);
164
- return {
165
- data: buildableExports.flatMap((buildableExport)=>{
166
- return [
167
- buildableExport.source && createMainConfig({
168
- ...buildableExport,
169
- source: buildableExport.source
170
- }, options),
171
- buildableExport.source && buildableExport.types && createTypesConfig({
172
- source: buildableExport.source,
173
- types: buildableExport.types
174
- }, options)
175
- ].filter(Boolean);
176
- }),
177
- metadata: buildableExports
178
- };
138
+ const createConfiguration = (options = DEFAULT_OPTIONS) => {
139
+ const buildableExports = getBuildableExports(options);
140
+ return {
141
+ data: buildableExports.flatMap((buildableExport) => {
142
+ return [buildableExport.source && createMainConfig({
143
+ ...buildableExport,
144
+ source: buildableExport.source
145
+ }, options), buildableExport.source && buildableExport.types && createTypesConfig({
146
+ source: buildableExport.source,
147
+ types: buildableExport.types
148
+ }, options)].filter(Boolean);
149
+ }),
150
+ metadata: buildableExports
151
+ };
179
152
  };
180
- // eslint-disable-next-line sonarjs/cyclomatic-complexity
181
- const getBuildableExports = ({ standalone })=>{
182
- if (standalone) {
183
- /**
184
- * Entry-point resolution invariants for standalone target (mostly binaries).
185
- */ if (!PKG.source || !PKG.bin || !PKG.name) {
186
- throw new Error("Invalid package entry points contract. Standalone compilation is enabled but required fields are missing. Make sure to set `name`, `source`, and `bin` fields.");
187
- }
188
- const bin = PKG.bin;
189
- const name = PKG.name;
190
- const source = PKG.source;
191
- if (isRecord(bin)) {
192
- return Object.entries(bin).map((data)=>({
193
- bin: data[0],
194
- require: data[1],
195
- source
196
- }));
197
- }
198
- return [
199
- {
200
- // For scoped packages and if the `bin` is defined with a string value, the [scope name is discarded](the scope name is discarded when creating a binary) when creating a binary.
201
- bin: name.replace(/^(@.*?\/)/, ""),
202
- require: bin,
203
- source
204
- }
205
- ];
206
- }
207
- /**
208
- * Entry-point resolution invariants for non-standalone target (mostly libraries):
209
- * Following the [package entry-point specification](https://nodejs.org/api/packages.html#package-entry-points),
210
- * whenever an export object is defined, it take precedence over other classical entry-point fields
211
- * (such as main, module, and types defined at the root package.json level).
212
- */ if (PKG.main || PKG.module || PKG.types || !PKG.exports) {
213
- throw new Error("Invalid package entry points contract. Use the recommended [`exports` field](https://nodejs.org/api/packages.html#package-entry-points) instead and, for TypeScript-based projects, update the `tsconfig.json` file to resolve it properly (`moduleResolution` must be set to `Bundler` (or `NodeNext`)).");
214
- }
215
- const buildableExportFields = [
216
- "default",
217
- "import",
218
- "require",
219
- "types"
220
- ];
221
- let singleExport = undefined;
222
- const output = Object.entries(PKG.exports).map(([field, value])=>{
223
- if (isRecord(value)) {
224
- return [
225
- field,
226
- value
227
- ];
228
- }
229
- if ([
230
- "source",
231
- ...buildableExportFields
232
- ].includes(field)) {
233
- if (!singleExport) {
234
- singleExport = {
235
- [field]: value
236
- };
237
- return [
238
- ".",
239
- singleExport
240
- ];
241
- }
242
- singleExport[field] = value;
243
- }
244
- return undefined;
245
- }).reduce((buildableExports, currentExport)=>{
246
- if (!currentExport) return buildableExports;
247
- const [exportField, exportValue] = currentExport;
248
- const conditionalExportFields = Object.keys(exportValue);
249
- if (!conditionalExportFields.includes("source")) return buildableExports;
250
- const hasAtLeastOneRequiredField = buildableExportFields.some((entryPointField)=>conditionalExportFields.includes(entryPointField));
251
- if (hasAtLeastOneRequiredField) {
252
- buildableExports.push(exportValue);
253
- return buildableExports;
254
- }
255
- throw new Error(`A \`source\` field is defined without an output defined for the \`${exportField}\` export. Make sure to define at least one conditional entry point (including ${buildableExportFields.map((field)=>`\`${field}\``).join(", ")})`);
256
- }, []);
257
- if (output.length === 0) {
258
- throw new Error("No `source` field is set for the targeted package. If a build step is necessary, make sure to configure at least one `source` field in the package `exports` contract. If not, do not execute quickbundle on this package.");
259
- }
260
- return output;
153
+ const getBuildableExports = ({ standalone }) => {
154
+ if (standalone) {
155
+ /**
156
+ * Entry-point resolution invariants for standalone target (mostly binaries).
157
+ */
158
+ if (!PKG.source || !PKG.bin || !PKG.name) throw new Error("Invalid package entry points contract. Standalone compilation is enabled but required fields are missing. Make sure to set `name`, `source`, and `bin` fields.");
159
+ const bin = PKG.bin;
160
+ const name = PKG.name;
161
+ const source = PKG.source;
162
+ if (isRecord(bin)) return Object.entries(bin).map((data) => ({
163
+ bin: data[0],
164
+ require: data[1],
165
+ source
166
+ }));
167
+ return [{
168
+ bin: name.replace(/^(@.*?\/)/, ""),
169
+ require: bin,
170
+ source
171
+ }];
172
+ }
173
+ /**
174
+ * Entry-point resolution invariants for non-standalone target (mostly libraries):
175
+ * Following the [package entry-point specification](https://nodejs.org/api/packages.html#package-entry-points),
176
+ * whenever an export object is defined, it take precedence over other classical entry-point fields
177
+ * (such as main, module, and types defined at the root package.json level).
178
+ */
179
+ if (PKG.main || PKG.module || PKG.types || !PKG.exports) throw new Error("Invalid package entry points contract. Use the recommended [`exports` field](https://nodejs.org/api/packages.html#package-entry-points) instead and, for TypeScript-based projects, update the `tsconfig.json` file to resolve it properly (`moduleResolution` must be set to `Bundler` (or `NodeNext`)).");
180
+ const buildableExportFields = [
181
+ "default",
182
+ "import",
183
+ "require",
184
+ "types"
185
+ ];
186
+ let singleExport = void 0;
187
+ const output = Object.entries(PKG.exports).map(([field, value]) => {
188
+ if (isRecord(value)) return [field, value];
189
+ if (["source", ...buildableExportFields].includes(field)) {
190
+ if (!singleExport) {
191
+ singleExport = { [field]: value };
192
+ return [".", singleExport];
193
+ }
194
+ singleExport[field] = value;
195
+ }
196
+ }).reduce((buildableExports, currentExport) => {
197
+ if (!currentExport) return buildableExports;
198
+ const [exportField, exportValue] = currentExport;
199
+ const conditionalExportFields = Object.keys(exportValue);
200
+ if (!conditionalExportFields.includes("source")) return buildableExports;
201
+ if (buildableExportFields.some((entryPointField) => conditionalExportFields.includes(entryPointField))) {
202
+ buildableExports.push(exportValue);
203
+ return buildableExports;
204
+ }
205
+ throw new Error(`A \`source\` field is defined without an output defined for the \`${exportField}\` export. Make sure to define at least one conditional entry point (including ${buildableExportFields.map((field) => `\`${field}\``).join(", ")})`);
206
+ }, []);
207
+ if (output.length === 0) throw new Error("No `source` field is set for the targeted package. If a build step is necessary, make sure to configure at least one `source` field in the package `exports` contract. If not, do not execute quickbundle on this package.");
208
+ return output;
261
209
  };
262
- const getFileOutput = (filePath)=>{
263
- return {
264
- dir: dirname(filePath),
265
- entryFileNames: basename(filePath)
266
- };
210
+ const getFileOutput = (filePath) => {
211
+ return {
212
+ dir: dirname(filePath),
213
+ entryFileNames: basename(filePath)
214
+ };
267
215
  };
268
- const getPlugins = (customPlugins, options)=>{
269
- return [
270
- !options.standalone && externals({
271
- builtins: true,
272
- deps: true,
273
- /**
274
- * As they're not installed consumer side, `devDependencies` are declared as internal dependencies (via the `false` value)
275
- * and bundled into the dist if and only if imported and not listed as `peerDependencies` (otherwise, they're considered external).
276
- */ devDeps: false,
277
- optDeps: true,
278
- peerDeps: true
279
- }),
280
- commonjs(),
281
- url(),
282
- json(),
283
- ...customPlugins
284
- ].filter(Boolean);
216
+ const getPlugins = (options) => {
217
+ const output = [url()];
218
+ if (!options.standalone) output.push(externals({
219
+ builtins: true,
220
+ deps: true,
221
+ /**
222
+ * As they're not installed consumer side, `devDependencies` are declared as internal dependencies (via the `false` value)
223
+ * and bundled into the dist if and only if imported and not listed as `peerDependencies` (otherwise, they're considered external).
224
+ */
225
+ devDeps: false,
226
+ optDeps: true,
227
+ peerDeps: true
228
+ }));
229
+ return output;
285
230
  };
286
- const createMainConfig = (entryPoints, options)=>{
287
- const { minification, sourceMaps } = options;
288
- const cjsInput = entryPoints.require;
289
- const esmInput = entryPoints.import ?? entryPoints.default;
290
- if (entryPoints.import && entryPoints.default && entryPoints.import !== entryPoints.default) {
291
- throw new Error("Both `import` and `default` export fields have been defined but with different values. To preserve proper `default` field resolution on the consumer side (i.e. to target ESM format), make sure to provide the same file path for both fields.");
292
- }
293
- const commonOutputConfig = {
294
- importAttributesKey: "with",
295
- sourcemap: sourceMaps
296
- };
297
- const output = [
298
- cjsInput && {
299
- ...commonOutputConfig,
300
- ...getFileOutput(cjsInput),
301
- format: "cjs",
302
- inlineDynamicImports: Boolean(options.standalone)
303
- },
304
- esmInput && {
305
- ...commonOutputConfig,
306
- ...getFileOutput(esmInput),
307
- format: "es"
308
- }
309
- ].filter(Boolean);
310
- return {
311
- input: entryPoints.source,
312
- output,
313
- plugins: getPlugins([
314
- nodeResolve(),
315
- swc({
316
- minify: minification,
317
- sourceMaps
318
- })
319
- ], options)
320
- };
231
+ const createMainConfig = (entryPoints, options) => {
232
+ const { minification, sourceMaps } = options;
233
+ const cjsInput = entryPoints.require;
234
+ const esmInput = entryPoints.import ?? entryPoints.default;
235
+ if (entryPoints.import && entryPoints.default && entryPoints.import !== entryPoints.default) throw new Error("Both `import` and `default` export fields have been defined but with different values. To preserve proper `default` field resolution on the consumer side (i.e. to target ESM format), make sure to provide the same file path for both fields.");
236
+ const commonOutputConfig = {
237
+ minify: minification,
238
+ sourcemap: sourceMaps
239
+ };
240
+ const output = [cjsInput && {
241
+ ...commonOutputConfig,
242
+ ...getFileOutput(cjsInput),
243
+ codeSplitting: Boolean(options.standalone),
244
+ format: "cjs"
245
+ }, esmInput && {
246
+ ...commonOutputConfig,
247
+ ...getFileOutput(esmInput),
248
+ format: "es"
249
+ }].filter(Boolean);
250
+ return {
251
+ input: entryPoints.source,
252
+ output,
253
+ plugins: getPlugins(options)
254
+ };
321
255
  };
322
- const createTypesConfig = (entryPoints, options)=>{
323
- return {
324
- input: entryPoints.source,
325
- output: [
326
- getFileOutput(entryPoints.types)
327
- ],
328
- plugins: getPlugins([
329
- nodeResolve({
330
- /**
331
- * The `exports` conditional fields definition order is important in the `package.json file`.
332
- * To be resolved first, `types` field must always come first in the package.json exports definition.
333
- * @see https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#package-json-exports-imports-and-self-referencing.
334
- */ exportConditions: [
335
- "types"
336
- ]
337
- }),
338
- dts({
339
- compilerOptions: {
340
- declaration: true,
341
- emitDeclarationOnly: true,
342
- incremental: false,
343
- preserveSymlinks: false
344
- },
345
- respectExternal: true
346
- })
347
- ], options)
348
- };
256
+ const createTypesConfig = (entryPoints, options) => {
257
+ const { dir, entryFileNames } = getFileOutput(entryPoints.types);
258
+ return {
259
+ input: entryPoints.source,
260
+ output: {
261
+ dir,
262
+ entryFileNames({ name }) {
263
+ return name.endsWith(".d") ? entryFileNames : "";
264
+ }
265
+ },
266
+ plugins: [...getPlugins(options), dts({ emitDtsOnly: true })]
267
+ };
349
268
  };
350
269
 
351
- const createWatchCommand = (program)=>{
352
- return createCommand(program, {
353
- name: "watch",
354
- description: "Watch and rebuild on any code change (development mode)"
355
- }).task({
356
- handler (context) {
357
- watch(createConfiguration({
358
- minification: context.minification,
359
- sourceMaps: context.sourceMaps,
360
- standalone: false
361
- }));
362
- }
363
- });
270
+ //#endregion
271
+ //#region src/commands/build.ts
272
+ const createBuildCommand = (program) => {
273
+ return createCommand(program, {
274
+ description: "Build the source code (production mode)",
275
+ name: "build"
276
+ }).task({
277
+ async handler(context) {
278
+ return build(createConfiguration({
279
+ minification: context.minification,
280
+ sourceMaps: context.sourceMaps,
281
+ standalone: false
282
+ }));
283
+ },
284
+ key: "buildOutput",
285
+ label: "Bundle assets 📦"
286
+ }).task({
287
+ async handler(context) {
288
+ return computeBundleSize(context.buildOutput);
289
+ },
290
+ key: "logInput",
291
+ label: "Generate report 📝",
292
+ skip(context) {
293
+ return context.buildOutput.length === 0;
294
+ }
295
+ }).task({
296
+ handler(context) {
297
+ context.logInput.forEach((item) => {
298
+ helpers.message([`${formatSize(item.rawSize)} raw`, `${formatSize(item.compressedSize)} gzip`].map((message, index) => {
299
+ return index === 0 ? message : ` ${message}`;
300
+ }).join("\n"), {
301
+ label: `${item.filePath} (took ${item.elapsedTime}ms)`,
302
+ lineBreak: {
303
+ end: false,
304
+ start: true
305
+ },
306
+ type: "information"
307
+ });
308
+ });
309
+ },
310
+ skip(context) {
311
+ return context.buildOutput.length === 0;
312
+ }
313
+ });
364
314
  };
365
-
366
- // eslint-disable-next-line sonarjs/cognitive-complexity
367
- const build = async (input)=>{
368
- process.env.NODE_ENV ??= "production";
369
- const { data: configurations } = input;
370
- const output = [];
371
- for (const config of configurations){
372
- const initialTime = Date.now();
373
- const bundle = await rollup({
374
- ...config,
375
- onLog
376
- });
377
- if (config.output) {
378
- const outputEntries = Array.isArray(config.output) ? config.output : [
379
- config.output
380
- ];
381
- const promises = [];
382
- for (const outputEntry of outputEntries){
383
- const entryFileName = outputEntry.entryFileNames;
384
- const outputFilePath = join(outputEntry.dir ?? "", typeof entryFileName === "string" ? entryFileName : "");
385
- if (!outputFilePath) {
386
- throw new Error("Misconfigured file entry point. Make sure to define an `import`, `require`, or `default` field.");
387
- }
388
- promises.push(new Promise((resolve, reject)=>{
389
- bundle.write(outputEntry).then(()=>{
390
- resolve({
391
- elapsedTime: Date.now() - initialTime,
392
- filePath: outputFilePath
393
- });
394
- }).catch((error)=>{
395
- reject(error);
396
- });
397
- }));
398
- }
399
- output.push(...await Promise.all(promises));
400
- }
401
- }
402
- return output;
315
+ const computeBundleSize = async (buildOutput) => {
316
+ const computeFileSize = async (buildItemOutput) => {
317
+ const content = await readFile$1(buildItemOutput.filePath);
318
+ const gzSize = await gzipSize(content);
319
+ return {
320
+ ...buildItemOutput,
321
+ compressedSize: gzSize,
322
+ rawSize: content.byteLength
323
+ };
324
+ };
325
+ return Promise.all(buildOutput.map(async (item) => computeFileSize(item)));
326
+ };
327
+ const formatSize = (bytes) => {
328
+ const kiloBytes = bytes / 1e3;
329
+ return kiloBytes < 1 ? `${bytes} B` : `${kiloBytes.toFixed(2)} kB`;
403
330
  };
404
331
 
332
+ //#endregion
333
+ //#region src/commands/compile.ts
405
334
  const TEMPORARY_PATH = resolveFromInternalDirectory("dist", "tmp");
406
335
  const TEMPORARY_DOWNLOAD_PATH = join(TEMPORARY_PATH, "zip");
407
336
  const TEMPORARY_RUNTIME_PATH = join(TEMPORARY_PATH, "runtime");
408
- const createCompileCommand = (program)=>{
409
- return program.command({
410
- name: "compile",
411
- description: "Compiles the source code into a self-contained executable"
412
- }).option({
413
- key: "targetInput",
414
- name: {
415
- long: "target",
416
- short: "t"
417
- },
418
- description: "Set a different cross-compilation target",
419
- defaultValue: "local"
420
- }).task({
421
- key: "config",
422
- label: "Create configuration",
423
- handler () {
424
- return createConfiguration({
425
- minification: true,
426
- sourceMaps: false,
427
- standalone: true
428
- });
429
- }
430
- }).task({
431
- key: "osType",
432
- label ({ targetInput }) {
433
- return `Get \`${targetInput}\` runtime`;
434
- },
435
- async handler ({ targetInput }) {
436
- if (targetInput === "local") {
437
- await copyFile(process.execPath, TEMPORARY_RUNTIME_PATH);
438
- return getOsType(os.type());
439
- }
440
- const matchedRuntimeParts = matchRuntimeParts(targetInput);
441
- if (!matchedRuntimeParts) {
442
- throw new Error("Invalid `runtime` flag input. The accepted targets are the one listed in https://nodejs.org/download/release/ with the following format `node-vx.y.z-(darwin|linux|win)-(arm64|x64|x86)`.");
443
- }
444
- const osType = getOsType(matchedRuntimeParts.os);
445
- const extension = osType === "windows" ? "zip" : "tar.gz";
446
- await download(`https://nodejs.org/download/release/${matchedRuntimeParts.version}/${targetInput}.${extension}`, TEMPORARY_DOWNLOAD_PATH);
447
- await unzip({
448
- path: TEMPORARY_DOWNLOAD_PATH,
449
- targetedArchivePath: osType === "windows" ? join(targetInput, "node.exe") : join(targetInput, "bin", "node")
450
- }, {
451
- directoryPath: dirname(TEMPORARY_RUNTIME_PATH),
452
- filename: basename(TEMPORARY_RUNTIME_PATH)
453
- });
454
- return osType;
455
- }
456
- }).task({
457
- label: "Build",
458
- async handler ({ config }) {
459
- await build(config);
460
- }
461
- }).task({
462
- label ({ config }) {
463
- const binaries = config.metadata.map(({ bin })=>{
464
- if (!bin) return undefined;
465
- return `\`${bin}\``;
466
- }).filter(Boolean).join(", ");
467
- return `Compile ${binaries}`;
468
- },
469
- async handler ({ config, osType }) {
470
- await Promise.all(config.metadata.map(async ({ bin, require })=>{
471
- if (!require || !bin) return;
472
- return compile({
473
- bin,
474
- input: require,
475
- osType
476
- });
477
- }));
478
- }
479
- });
337
+ const createCompileCommand = (program) => {
338
+ return program.command({
339
+ description: "Compiles the source code into a self-contained executable",
340
+ name: "compile"
341
+ }).option({
342
+ defaultValue: "local",
343
+ description: "Set a different cross-compilation target",
344
+ key: "targetInput",
345
+ name: {
346
+ long: "target",
347
+ short: "t"
348
+ }
349
+ }).task({
350
+ handler() {
351
+ return createConfiguration({
352
+ minification: true,
353
+ sourceMaps: false,
354
+ standalone: true
355
+ });
356
+ },
357
+ key: "config",
358
+ label: "Create configuration"
359
+ }).task({
360
+ async handler({ targetInput }) {
361
+ if (targetInput === "local") {
362
+ await copyFile$1(process.execPath, TEMPORARY_RUNTIME_PATH);
363
+ return getOsType(os.type());
364
+ }
365
+ const matchedRuntimeParts = matchRuntimeParts(targetInput);
366
+ if (!matchedRuntimeParts) throw new Error("Invalid `runtime` flag input. The accepted targets are the one listed in https://nodejs.org/download/release/ with the following format `node-vx.y.z-(darwin|linux|win)-(arm64|x64|x86)`.");
367
+ const osType = getOsType(matchedRuntimeParts.os);
368
+ const extension = osType === "windows" ? "zip" : "tar.gz";
369
+ await download(`https://nodejs.org/download/release/${matchedRuntimeParts.version}/${targetInput}.${extension}`, TEMPORARY_DOWNLOAD_PATH);
370
+ await unzip({
371
+ path: TEMPORARY_DOWNLOAD_PATH,
372
+ targetedArchivePath: osType === "windows" ? join(targetInput, "node.exe") : join(targetInput, "bin", "node")
373
+ }, {
374
+ directoryPath: dirname(TEMPORARY_RUNTIME_PATH),
375
+ filename: basename(TEMPORARY_RUNTIME_PATH)
376
+ });
377
+ return osType;
378
+ },
379
+ key: "osType",
380
+ label({ targetInput }) {
381
+ return `Get \`${targetInput}\` runtime`;
382
+ }
383
+ }).task({
384
+ async handler({ config }) {
385
+ await build(config);
386
+ },
387
+ label: "Build"
388
+ }).task({
389
+ async handler({ config, osType }) {
390
+ await Promise.all(config.metadata.map(async ({ bin, require }) => {
391
+ if (!require || !bin) return;
392
+ return compile({
393
+ bin,
394
+ input: require,
395
+ osType
396
+ });
397
+ }));
398
+ },
399
+ label({ config }) {
400
+ return `Compile ${config.metadata.map(({ bin }) => {
401
+ if (!bin) return void 0;
402
+ return `\`${bin}\``;
403
+ }).filter(Boolean).join(", ")}`;
404
+ }
405
+ });
480
406
  };
481
- const getOsType = (input)=>{
482
- switch(input){
483
- case "Windows_NT":
484
- case "win":
485
- {
486
- return "windows";
487
- }
488
- case "Darwin":
489
- case "darwin":
490
- {
491
- return "macos";
492
- }
493
- case "Linux":
494
- case "linux":
495
- {
496
- return "linux";
497
- }
498
- default:
499
- {
500
- throw new Error(`Unsupported operating system \`${input}\``);
501
- }
502
- }
407
+ const getOsType = (input) => {
408
+ switch (input) {
409
+ case "Darwin":
410
+ case "darwin": return "macos";
411
+ case "Linux":
412
+ case "linux": return "linux";
413
+ case "win":
414
+ case "Windows_NT": return "windows";
415
+ default: throw new Error(`Unsupported operating system \`${input}\``);
416
+ }
503
417
  };
504
418
  const matchRuntimeParts = createRegExpMatcher(/^node-(?<version>v\d+\.\d+\.\d+)-(?<os>darwin|linux|win)-(?<architecture>arm64|x64|x86)$/);
505
- const compile = async ({ bin, input, osType })=>{
506
- const inputFileName = basename(input);
507
- const inputDirectory = dirname(input);
508
- const resolveFromInputDirectory = (...paths)=>{
509
- return resolve(inputDirectory, ...paths);
510
- };
511
- const blobFileName = resolveFromInputDirectory(`${inputFileName}.blob`);
512
- const executableFileName = resolveFromInputDirectory(`${bin}${osType === "windows" ? ".exe" : ""}`);
513
- const seaConfigFileName = resolveFromInputDirectory(`${inputFileName}.sea-config.json`);
514
- await writeFile(seaConfigFileName, JSON.stringify({
515
- disableExperimentalSEAWarning: true,
516
- main: input,
517
- output: blobFileName,
518
- useCodeCache: false,
519
- useSnapshot: false
520
- }));
521
- await Promise.all([
522
- helpers.exec(`node --experimental-sea-config ${seaConfigFileName}`),
523
- copyFile(TEMPORARY_RUNTIME_PATH, executableFileName)
524
- ]);
525
- if (osType === "macos") {
526
- await helpers.exec(`codesign --remove-signature ${executableFileName}`);
527
- }
528
- await helpers.exec(`npx postject ${executableFileName} NODE_SEA_BLOB ${blobFileName} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ${osType === "macos" ? "--macho-segment-name NODE_SEA" : ""}`);
529
- if (osType === "macos") {
530
- await helpers.exec(`codesign --sign - ${executableFileName}`);
531
- }
532
- await Promise.all([
533
- blobFileName,
534
- seaConfigFileName,
535
- TEMPORARY_PATH
536
- ].map(async (path)=>removePath(path)));
419
+ const compile = async ({ bin, input, osType }) => {
420
+ const inputFileName = basename(input);
421
+ const inputDirectory = dirname(input);
422
+ const resolveFromInputDirectory = (...paths) => {
423
+ return resolve(inputDirectory, ...paths);
424
+ };
425
+ const blobFileName = resolveFromInputDirectory(`${inputFileName}.blob`);
426
+ const executableFileName = resolveFromInputDirectory(`${bin}${osType === "windows" ? ".exe" : ""}`);
427
+ const seaConfigFileName = resolveFromInputDirectory(`${inputFileName}.sea-config.json`);
428
+ await writeFile$1(seaConfigFileName, JSON.stringify({
429
+ disableExperimentalSEAWarning: true,
430
+ main: input,
431
+ output: blobFileName,
432
+ useCodeCache: false,
433
+ useSnapshot: false
434
+ }));
435
+ await Promise.all([helpers.exec(`node --experimental-sea-config ${seaConfigFileName}`), copyFile$1(TEMPORARY_RUNTIME_PATH, executableFileName)]);
436
+ if (osType === "macos") await helpers.exec(`codesign --remove-signature ${executableFileName}`);
437
+ await helpers.exec(`npx postject ${executableFileName} NODE_SEA_BLOB ${blobFileName} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ${osType === "macos" ? "--macho-segment-name NODE_SEA" : ""}`);
438
+ if (osType === "macos") await helpers.exec(`codesign --sign - ${executableFileName}`);
439
+ await Promise.all([
440
+ blobFileName,
441
+ seaConfigFileName,
442
+ TEMPORARY_PATH
443
+ ].map(async (path) => removePath(path)));
537
444
  };
538
445
 
539
- const createBuildCommand = (program)=>{
540
- return createCommand(program, {
541
- name: "build",
542
- description: "Build the source code (production mode)"
543
- }).task({
544
- key: "buildOutput",
545
- label: "Bundle assets 📦",
546
- async handler (context) {
547
- return build(createConfiguration({
548
- minification: context.minification,
549
- sourceMaps: context.sourceMaps,
550
- standalone: false
551
- }));
552
- }
553
- }).task({
554
- key: "logInput",
555
- label: "Generate report 📝",
556
- async handler (context) {
557
- return computeBundleSize(context.buildOutput);
558
- },
559
- skip (context) {
560
- return context.buildOutput.length === 0;
561
- }
562
- }).task({
563
- handler (context) {
564
- context.logInput.forEach((item)=>{
565
- helpers.message([
566
- `${formatSize(item.rawSize)} raw`,
567
- `${formatSize(item.compressedSize)} gzip`
568
- ].map((message, index)=>{
569
- return index === 0 ? message : ` ${message}`;
570
- }).join("\n"), {
571
- label: `${item.filePath} (took ${item.elapsedTime}ms)`,
572
- lineBreak: {
573
- end: false,
574
- start: true
575
- },
576
- type: "information"
577
- });
578
- });
579
- },
580
- skip (context) {
581
- return context.buildOutput.length === 0;
582
- }
583
- });
584
- };
585
- const computeBundleSize = async (buildOutput)=>{
586
- const computeFileSize = async (buildItemOutput)=>{
587
- const content = await readFile(buildItemOutput.filePath);
588
- const gzSize = await gzipSize(content);
589
- return {
590
- ...buildItemOutput,
591
- compressedSize: gzSize,
592
- rawSize: content.byteLength
593
- };
594
- };
595
- return Promise.all(buildOutput.map(async (item)=>computeFileSize(item)));
446
+ //#endregion
447
+ //#region src/bundler/watch.ts
448
+ const watch$1 = (input) => {
449
+ process.env.NODE_ENV ??= "development";
450
+ const watcher = watch(input.data);
451
+ let startDuration;
452
+ console.clear();
453
+ watcher.on("event", async (event) => {
454
+ switch (event.code) {
455
+ case "BUNDLE_END":
456
+ await event.result.close();
457
+ break;
458
+ case "END":
459
+ clearLog(`Build done in ${Date.now() - startDuration}ms (at ${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`, { type: "success" });
460
+ return;
461
+ case "ERROR": {
462
+ const { error } = event;
463
+ clearLog(error.message, { type: "error" });
464
+ console.error("\n", error);
465
+ return;
466
+ }
467
+ case "START":
468
+ startDuration = Date.now();
469
+ clearLog("Build in progress…", { type: "information" });
470
+ return;
471
+ default: break;
472
+ }
473
+ });
596
474
  };
597
- const formatSize = (bytes)=>{
598
- const kiloBytes = bytes / 1000;
599
- return kiloBytes < 1 ? `${bytes} B` : `${kiloBytes.toFixed(2)} kB`;
475
+ const clearLog = (...input) => {
476
+ console.clear();
477
+ helpers.message(...input);
600
478
  };
601
479
 
602
- var name = "quickbundle";
603
- var version = "2.16.0";
480
+ //#endregion
481
+ //#region src/commands/watch.ts
482
+ const createWatchCommand = (program) => {
483
+ return createCommand(program, {
484
+ description: "Watch and rebuild on any code change (development mode)",
485
+ name: "watch"
486
+ }).task({ handler(context) {
487
+ watch$1(createConfiguration({
488
+ minification: context.minification,
489
+ sourceMaps: context.sourceMaps,
490
+ standalone: false
491
+ }));
492
+ } });
493
+ };
604
494
 
605
- const createProgram = (...commandBuilders)=>{
606
- const program = termost({
607
- name,
608
- description: "The zero-configuration transpiler and bundler for the web",
609
- version
610
- });
611
- for (const commandBuilder of commandBuilders){
612
- commandBuilder(program);
613
- }
495
+ //#endregion
496
+ //#region src/index.ts
497
+ const createProgram = (...commandBuilders) => {
498
+ const program = termost({
499
+ description: "The zero-configuration transpiler and bundler for the web",
500
+ name,
501
+ version
502
+ });
503
+ for (const commandBuilder of commandBuilders) commandBuilder(program);
614
504
  };
615
505
  createProgram(createBuildCommand, createWatchCommand, createCompileCommand);
506
+
507
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickbundle",
3
- "version": "2.16.0",
3
+ "version": "3.0.0-next-ad3f6ff",
4
4
  "description": "The zero-configuration transpiler and bundler for the web",
5
5
  "keywords": [
6
6
  "library",
@@ -40,25 +40,20 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "@rollup/plugin-commonjs": "^29.0.0",
44
- "@rollup/plugin-json": "^6.1.0",
45
- "@rollup/plugin-node-resolve": "^16.0.3",
46
43
  "@rollup/plugin-url": "^8.0.2",
47
- "@swc/core": "^1.15.2",
48
44
  "decompress": "^4.2.1",
49
45
  "gzip-size": "^7.0.0",
50
- "rollup": "^4.53.3",
51
- "rollup-plugin-dts": "^6.2.3",
52
- "rollup-plugin-node-externals": "^8.1.2",
53
- "rollup-plugin-swc3": "^0.12.1",
46
+ "rolldown": "^1.0.2",
47
+ "rolldown-plugin-dts": "^0.25.1",
48
+ "rollup-plugin-node-externals": "^9.0.1",
54
49
  "termost": "^1.9.0"
55
50
  },
56
51
  "devDependencies": {
57
52
  "@types/decompress": "4.2.7",
58
- "@types/node": "24.10.1"
53
+ "@types/node": "24.12.4"
59
54
  },
60
55
  "peerDependencies": {
61
- "typescript": "^4.7.0 || ^5.0.0"
56
+ "typescript": "^4.7.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
62
57
  },
63
58
  "peerDependenciesMeta": {
64
59
  "typescript": {
@@ -66,8 +61,8 @@
66
61
  }
67
62
  },
68
63
  "scripts": {
69
- "build": "rollup --config ./bundler.config.ts --configPlugin rollup-plugin-swc3",
64
+ "build": "rolldown --config ./bundler.config.ts",
70
65
  "test": "exit 0",
71
- "watch": "rollup --watch --config ./bundler.config.ts --configPlugin rollup-plugin-swc3"
66
+ "watch": "rolldown --watch --config ./bundler.config.ts"
72
67
  }
73
68
  }