quickbundle 3.0.0-next-c53d28a → 3.0.0-next-c70f8cb

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +51 -137
  3. package/package.json +4 -9
package/README.md CHANGED
@@ -105,6 +105,7 @@ yarn add quickbundle
105
105
  > [!warning]
106
106
  > The `compile` command relies on the [Node.js SEA (Single Executable Applications feature)](https://nodejs.org/api/single-executable-applications.html). This feature comes with some limitations which Quickbundle attempts to address:
107
107
  >
108
+ > - The minimum compatible Node.js version is v25.5.0 (to support `--build-sea` flag).
108
109
  > - The source code must not rely on [external dependencies](https://github.com/nodejs/single-executable/discussions/70) 🛑. To partially address this, Quickbundle bundles all dependencies (whatever their types, as long as they're used) and inline dynamic imports by default ✅.
109
110
  > - The bundled code must use the CommonJS module system 🛑. To address this, Quickbundle always outputs CJS modules in compile mode ✅.
110
111
 
package/dist/index.js CHANGED
@@ -6,16 +6,11 @@ import url from "@rollup/plugin-url";
6
6
  import { createRequire } from "node:module";
7
7
  import { dts } from "rolldown-plugin-dts";
8
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";
9
+ import { readFile, rename, rm } from "node:fs/promises";
15
10
 
16
11
  //#region package.json
17
12
  var name = "quickbundle";
18
- var version = "3.0.0-next-c53d28a";
13
+ var version = "3.0.0-next-c70f8cb";
19
14
 
20
15
  //#endregion
21
16
  //#region src/bundler/build.ts
@@ -28,17 +23,13 @@ const build = async (input) => {
28
23
  const bundle = await rolldown(config);
29
24
  if (config.output) {
30
25
  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
- }));
26
+ const promises = Array.from(outputEntries, async (outputEntry) => {
27
+ const { output: rolldownOutput } = await bundle.write(outputEntry);
28
+ return {
29
+ elapsedTime: Date.now() - initialTime,
30
+ filePath: join(outputEntry.dir ?? "", rolldownOutput.find((item) => item.type === "chunk" && item.isEntry)?.fileName ?? "")
31
+ };
32
+ });
42
33
  output.push(...await Promise.all(promises));
43
34
  }
44
35
  }
@@ -48,16 +39,6 @@ const build = async (input) => {
48
39
  //#endregion
49
40
  //#region src/helpers.ts
50
41
  /**
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);
59
- };
60
- /**
61
42
  * Resolve a relative path from the current working project directory.
62
43
  * @param paths - Relative paths.
63
44
  * @returns The resolved absolute path.
@@ -72,12 +53,8 @@ const createRegExpMatcher = (regex) => {
72
53
  return regex.exec(value)?.groups;
73
54
  };
74
55
  };
75
- const createDirectory = async (path) => {
76
- await mkdir(path, { recursive: true });
77
- };
78
- const copyFile$1 = async (fromPath, toPath) => {
79
- await createDirectory(dirname(toPath));
80
- await copyFile(fromPath, toPath);
56
+ const readFile$1 = async (filePath) => {
57
+ return readFile(filePath);
81
58
  };
82
59
  const removePath = async (path) => {
83
60
  await rm(path, {
@@ -85,28 +62,6 @@ const removePath = async (path) => {
85
62
  recursive: true
86
63
  });
87
64
  };
88
- const readFile$1 = async (filePath) => {
89
- return readFile(filePath);
90
- };
91
- const writeFile$1 = async (filePath, content) => {
92
- await createDirectory(dirname(filePath));
93
- await writeFile(filePath, content, "utf8");
94
- };
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)));
101
- };
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));
109
- };
110
65
  const createCommand = (program, input) => {
111
66
  return program.command(input).option({
112
67
  defaultValue: false,
@@ -135,7 +90,7 @@ const DEFAULT_OPTIONS = {
135
90
  sourceMaps: false,
136
91
  standalone: false
137
92
  };
138
- const createConfiguration = (options = DEFAULT_OPTIONS) => {
93
+ const createConfig = (options = DEFAULT_OPTIONS) => {
139
94
  const buildableExports = getBuildableExports(options);
140
95
  return {
141
96
  data: buildableExports.flatMap((buildableExport) => {
@@ -229,10 +184,10 @@ const getPlugins = (options) => {
229
184
  return output;
230
185
  };
231
186
  const createMainConfig = (entryPoints, options) => {
187
+ 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.");
232
188
  const { minification, sourceMaps } = options;
233
189
  const cjsInput = entryPoints.require;
234
190
  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
191
  const commonOutputConfig = {
237
192
  minify: minification,
238
193
  sourcemap: sourceMaps
@@ -240,7 +195,7 @@ const createMainConfig = (entryPoints, options) => {
240
195
  const output = [cjsInput && {
241
196
  ...commonOutputConfig,
242
197
  ...getFileOutput(cjsInput),
243
- codeSplitting: Boolean(options.standalone),
198
+ codeSplitting: options.standalone,
244
199
  format: "cjs"
245
200
  }, esmInput && {
246
201
  ...commonOutputConfig,
@@ -275,7 +230,7 @@ const createBuildCommand = (program) => {
275
230
  name: "build"
276
231
  }).task({
277
232
  async handler(context) {
278
- return build(createConfiguration({
233
+ return build(createConfig({
279
234
  minification: context.minification,
280
235
  sourceMaps: context.sourceMaps,
281
236
  standalone: false
@@ -331,9 +286,6 @@ const formatSize = (bytes) => {
331
286
 
332
287
  //#endregion
333
288
  //#region src/commands/compile.ts
334
- const TEMPORARY_PATH = resolveFromInternalDirectory("dist", "tmp");
335
- const TEMPORARY_DOWNLOAD_PATH = join(TEMPORARY_PATH, "zip");
336
- const TEMPORARY_RUNTIME_PATH = join(TEMPORARY_PATH, "runtime");
337
289
  const createCompileCommand = (program) => {
338
290
  return program.command({
339
291
  description: "Compiles the source code into a self-contained executable",
@@ -348,7 +300,7 @@ const createCompileCommand = (program) => {
348
300
  }
349
301
  }).task({
350
302
  handler() {
351
- return createConfiguration({
303
+ return createConfig({
352
304
  minification: true,
353
305
  sourceMaps: false,
354
306
  standalone: true
@@ -356,45 +308,42 @@ const createCompileCommand = (program) => {
356
308
  },
357
309
  key: "config",
358
310
  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
311
  }).task({
384
312
  async handler({ config }) {
385
313
  await build(config);
386
314
  },
387
315
  label: "Build"
388
316
  }).task({
389
- async handler({ config, osType }) {
390
- await Promise.all(config.metadata.map(async ({ bin, require }) => {
317
+ async handler({ config, targetInput }) {
318
+ for (const { bin, require } of config.metadata) {
391
319
  if (!require || !bin) return;
392
- return compile({
393
- bin,
394
- input: require,
395
- osType
396
- });
397
- }));
320
+ let os = process.platform === "win32" ? "win" : process.platform;
321
+ let architecture = process.arch;
322
+ let version = void 0;
323
+ if (targetInput !== "local") {
324
+ const nodeProperties = getNodeProperties(targetInput);
325
+ if (!nodeProperties) 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)`.");
326
+ architecture = nodeProperties.architecture;
327
+ os = nodeProperties.os;
328
+ version = nodeProperties.version;
329
+ }
330
+ const distributionPath = dirname(require);
331
+ const target = `${os}-${architecture}`;
332
+ const targetPath = join(distributionPath, target);
333
+ const packAppFlags = Object.entries({
334
+ "entry": require,
335
+ "output-dir": distributionPath,
336
+ "output-name": bin,
337
+ "runtime": version ? `node@${version}` : void 0,
338
+ target
339
+ }).map(([flagName, flagValue]) => {
340
+ if (!flagValue) return void 0;
341
+ return `--${flagName} ${flagValue}`;
342
+ }).filter(Boolean).join(" ");
343
+ await helpers.exec(`npx --yes pnpm pack-app ${packAppFlags}`);
344
+ await rename(join(targetPath, bin), join(distributionPath, bin));
345
+ await Promise.all([require, targetPath].map(async (path) => removePath(path)));
346
+ }
398
347
  },
399
348
  label({ config }) {
400
349
  return `Compile ${config.metadata.map(({ bin }) => {
@@ -404,44 +353,7 @@ const createCompileCommand = (program) => {
404
353
  }
405
354
  });
406
355
  };
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
- }
417
- };
418
- const matchRuntimeParts = createRegExpMatcher(/^node-(?<version>v\d+\.\d+\.\d+)-(?<os>darwin|linux|win)-(?<architecture>arm64|x64|x86)$/);
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)));
444
- };
356
+ const getNodeProperties = createRegExpMatcher(/^node-(?<version>v\d+\.\d+\.\d+)-(?<os>darwin|linux|win)-(?<architecture>arm64|x64|x86)$/);
445
357
 
446
358
  //#endregion
447
359
  //#region src/bundler/watch.ts
@@ -455,9 +367,11 @@ const watch$1 = (input) => {
455
367
  case "BUNDLE_END":
456
368
  await event.result.close();
457
369
  break;
458
- case "END":
459
- clearLog(`Build done in ${Date.now() - startDuration}ms (at ${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`, { type: "success" });
370
+ case "END": {
371
+ const duration = Date.now() - startDuration;
372
+ clearLog(`Build done in ${duration}ms (at ${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`, { type: "success" });
460
373
  return;
374
+ }
461
375
  case "ERROR": {
462
376
  const { error } = event;
463
377
  clearLog(error.message, { type: "error" });
@@ -484,7 +398,7 @@ const createWatchCommand = (program) => {
484
398
  description: "Watch and rebuild on any code change (development mode)",
485
399
  name: "watch"
486
400
  }).task({ handler(context) {
487
- watch$1(createConfiguration({
401
+ watch$1(createConfig({
488
402
  minification: context.minification,
489
403
  sourceMaps: context.sourceMaps,
490
404
  standalone: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickbundle",
3
- "version": "3.0.0-next-c53d28a",
3
+ "version": "3.0.0-next-c70f8cb",
4
4
  "description": "The zero-configuration transpiler and bundler for the web",
5
5
  "keywords": [
6
6
  "library",
@@ -41,16 +41,11 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@rollup/plugin-url": "^8.0.2",
44
- "decompress": "^4.2.1",
45
44
  "gzip-size": "^7.0.0",
46
- "rolldown": "^1.0.2",
47
- "rolldown-plugin-dts": "^0.25.1",
45
+ "rolldown": "^1.2.0",
46
+ "rolldown-plugin-dts": "^0.27.14",
48
47
  "rollup-plugin-node-externals": "^9.0.1",
49
- "termost": "^1.9.0"
50
- },
51
- "devDependencies": {
52
- "@types/decompress": "4.2.7",
53
- "@types/node": "24.12.4"
48
+ "termost": "^1.9.1"
54
49
  },
55
50
  "peerDependencies": {
56
51
  "typescript": "^4.7.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"