quickbundle 2.7.0 → 2.9.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
@@ -10,15 +10,15 @@
10
10
 
11
11
  Quickbundle allows you to bundle a library in a **quick**, **fast** and **easy** way:
12
12
 
13
- - Fast build and watch mode powered by Rollup[^1] and SWC[^2].
14
- - Compile mode to create standalone binaries for systems that do not have Node.js installed[^3].
15
13
  - Zero configuration: define the build artifacts in your `package.json`, and you're all set!
14
+ - Fast build and watch mode powered by Rollup[^1] and SWC[^2].
15
+ - Compile and cross compile standalone executables for systems that do not have Node.js installed[^3].
16
16
  - Support of `cjs` & `esm` module formats output.
17
17
  - Support of several loaders including JavaScript, TypeScript, JSX, JSON, and Images.
18
18
  - TypeScript's declaration file (`.d.ts`) bundling.
19
19
  - Automatic dependency inclusion:
20
- - For the build and watch mode, `peerDependencies` and `dependencies` are not bundled in the final output, `devDependencies` are unless they're not imported.
21
- - For the compile mode, all dependencies are included to make the code standalone.
20
+ - For the build and watch mode, `peerDependencies` and `dependencies` are not bundled in the final output, `devDependencies` are unless they're not imported.
21
+ - For the compile mode, all dependencies are included to make the code standalone.
22
22
 
23
23
  [^1]: A [module bundler](https://rollupjs.org/) optimized for better tree-shaking processing and seamless interoperability of CommonJS and ESM formats with minimal code footprint.
24
24
 
@@ -113,7 +113,7 @@ yarn add quickbundle
113
113
  "name": "lib", // Package name
114
114
  "source": "./src/index.ts", // Source code entry point. Make sure that it starts with `#!/usr/bin/env node` pragme to make the binary portable for consumers who would like to use it by installing the package instead of using the generated standalone executable.
115
115
  "bin": {
116
- "your-binary-name": "./dist/index.cjs", // Binary information to get the executable name from the key and, from the value, the bundled file to generate from the source code and inject into the executable. The generated executable will be located in the same folder as the bundled file and, dependending on the current operating system running the `compile` command, the executable will be named either `your-binary-name.exe` on Windows or `your-binary-name` on Linux and macOS.
116
+ "your-binary-name": "./dist/index.cjs", // Binary information to get the executable name from the key and, from the value, the bundled file to generate from the source code and inject into the executable. The generated executable will be located in the same folder as the bundled file and, by default, dependending on the current operating system running the `compile` command, the executable will be named either `your-binary-name.exe` on Windows or `your-binary-name` on Linux and macOS. For cross-compilation output, check the `Patterns` section.
117
117
  },
118
118
  // "bin": "./dist/index.cjs", // Or, if the binary name follows the package name, you can define a string-based `bin` value.
119
119
  "scripts": {
@@ -171,6 +171,24 @@ quickbundle watch --source-maps
171
171
 
172
172
  Enabling source map generation is needed only if a build is [obfuscated (minified)](#optimize-the-build-output) for debugging-easing purposes. It generally pairs with the [`minification` flag](#optimize-the-build-output).
173
173
 
174
+ ### Cross compilation to other platforms
175
+
176
+ By default, the `compile` command embeds the runtime at the origin of its execution which means it generates executables compatible only with machines running the same operating system, processor architecture, and Node.js version.
177
+
178
+ However, Quickbundle provides the ability to target a different operating system, processor architecture, or Node.js version (also known as cross-compilation):
179
+
180
+ ```bash
181
+ quickbundle compile --target node-v23.1.0-darwin-arm64 # Embeds Node v23 runtime built for macOS ARM64 target
182
+ quickbundle compile --target node-v23.1.0-darwin-x64 # Embeds Node v23 runtime built for macOS X64 target
183
+ quickbundle compile --target node-v23.1.0-linux-arm64 # Embeds Node v23 runtime built for Linux ARM64 target
184
+ quickbundle compile --target node-v23.1.0-linux-x64 # Embeds Node v23 runtime built for Linux X64 target
185
+ quickbundle compile --target node-v23.1.0-win-arm64 # Embeds Node v23 runtime built for Windows ARM64 target
186
+ quickbundle compile --target node-v23.1.0-win-x64 # Embeds Node v23 runtime built for Windows X64 target
187
+ ```
188
+
189
+ > [!note]
190
+ > The target input must follow the `node-vx.y.z-(darwin|linux|win)-(arm64|x64|x86)` format (for an exhaustive view, check available filenames in [https://nodejs.org/download/release/vx.y.z](https://nodejs.org/download/release/latest/)).
191
+
174
192
  <br>
175
193
 
176
194
  ## 🤩 Used by
@@ -195,3 +213,5 @@ We're open to new contributions, you can find more details [here](./CONTRIBUTING
195
213
  ## 📖 License
196
214
 
197
215
  [MIT](./LICENSE "License MIT").
216
+
217
+ <br>
package/bin/index.mjs CHANGED
@@ -3,6 +3,6 @@
3
3
  import { join } from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
 
6
- const pkg = createRequire(import.meta.url)("../package.json");
6
+ const package_ = createRequire(import.meta.url)("../package.json");
7
7
 
8
- import(join("..", pkg.exports["."].default));
8
+ import(join("..", package_.exports["."].default));
package/dist/index.mjs CHANGED
@@ -1,8 +1,12 @@
1
1
  import { helpers, termost } from 'termost';
2
- import { readFile as readFile$1, writeFile, rm } from 'node:fs/promises';
2
+ import { finished } from 'node:stream/promises';
3
+ import { Readable } from 'node:stream';
3
4
  import process from 'node:process';
5
+ import { resolve, dirname, join, basename } from 'node:path';
6
+ import { copyFile as copyFile$1, rename, mkdir, writeFile as writeFile$1, rm, readFile as readFile$1 } from 'node:fs/promises';
7
+ import { createWriteStream } from 'node:fs';
8
+ import decompress from 'decompress';
4
9
  import { watch as watch$1, rollup } from 'rollup';
5
- import { join, basename, dirname, resolve } from 'node:path';
6
10
  import { createRequire } from 'node:module';
7
11
  import { swc } from 'rollup-plugin-swc3';
8
12
  import externals from 'rollup-plugin-node-externals';
@@ -15,11 +19,74 @@ import os from 'node:os';
15
19
  import { gzipSize } from 'gzip-size';
16
20
 
17
21
  var name = "quickbundle";
18
- var version = "2.7.0";
22
+ var version = "2.9.0";
19
23
 
20
- const CWD = process.cwd();
21
-
22
- const readFile = readFile$1;
24
+ /**
25
+ * Resolve a relative path from the Quickbundle node modules directory.
26
+ * @param paths - Relative paths.
27
+ * @returns The resolved absolute path.
28
+ * @example
29
+ * resolveFromInternalDirectory("dist", "node");
30
+ */ const resolveFromInternalDirectory = (...paths)=>{
31
+ return resolve(import.meta.dirname, "../", ...paths);
32
+ };
33
+ /**
34
+ * Resolve a relative path from the current working project directory.
35
+ * @param paths - Relative paths.
36
+ * @returns The resolved absolute path.
37
+ * @example
38
+ * resolveFromExternalDirectory("package.json");
39
+ */ const resolveFromExternalDirectory = (...paths)=>{
40
+ return resolve(process.cwd(), ...paths);
41
+ };
42
+ const createRegExpMatcher = (regex)=>{
43
+ return (value)=>{
44
+ return regex.exec(value)?.groups;
45
+ };
46
+ };
47
+ const createDirectory = async (path)=>{
48
+ await mkdir(path, {
49
+ recursive: true
50
+ });
51
+ };
52
+ const copyFile = async (fromPath, toPath)=>{
53
+ await createDirectory(dirname(toPath));
54
+ await copyFile$1(fromPath, toPath);
55
+ };
56
+ const removePath = async (path)=>{
57
+ await rm(path, {
58
+ force: true,
59
+ recursive: true
60
+ });
61
+ };
62
+ const readFile = async (filePath)=>{
63
+ return readFile$1(filePath);
64
+ };
65
+ const writeFile = async (filePath, content)=>{
66
+ await createDirectory(dirname(filePath));
67
+ await writeFile$1(filePath, content, "utf8");
68
+ };
69
+ const download = async (url, filePath)=>{
70
+ await createDirectory(dirname(filePath));
71
+ const { body, ok, status, statusText } = await fetch(url);
72
+ if (!ok) {
73
+ throw new Error(`An error ocurred while downloading \`${url}\`. Received \`${status}\` status code with the following message \`${statusText}\`.`);
74
+ }
75
+ if (!body) {
76
+ throw new Error(`Empty body received while downloading \`${url}\`.`);
77
+ }
78
+ await finished(Readable.fromWeb(body).pipe(createWriteStream(filePath)));
79
+ };
80
+ const unzip = async (input, output)=>{
81
+ const { targetedArchivePath } = input;
82
+ const { directoryPath } = output;
83
+ await decompress(input.path, directoryPath, {
84
+ filter (file) {
85
+ return file.path === targetedArchivePath;
86
+ }
87
+ });
88
+ await rename(join(directoryPath, targetedArchivePath), join(directoryPath, output.filename));
89
+ };
23
90
  const createCommand = (program, input)=>{
24
91
  return program.command(input).option({
25
92
  key: "minification",
@@ -54,14 +121,16 @@ const watch = (input)=>{
54
121
  case "START":
55
122
  {
56
123
  startDuration = Date.now();
57
- clearLog(`Build in progress...`, {
124
+ clearLog("Build in progress…", {
58
125
  type: "information"
59
126
  });
60
127
  return;
61
128
  }
62
129
  case "BUNDLE_END":
63
- await event.result.close();
64
- break;
130
+ {
131
+ await event.result.close();
132
+ break;
133
+ }
65
134
  case "END":
66
135
  {
67
136
  const duration = Date.now() - startDuration;
@@ -88,12 +157,13 @@ const clearLog = (...input)=>{
88
157
  };
89
158
 
90
159
  const require = createRequire(import.meta.url);
91
- const PKG = require(join(CWD, "./package.json"));
92
- const createConfiguration = (options = {
160
+ const PKG = require(resolveFromExternalDirectory("package.json"));
161
+ const DEFAULT_OPTIONS = {
93
162
  minification: false,
94
163
  sourceMaps: false,
95
164
  standalone: false
96
- })=>{
165
+ };
166
+ const createConfiguration = (options = DEFAULT_OPTIONS)=>{
97
167
  const buildableExports = getBuildableExports(options);
98
168
  return {
99
169
  data: buildableExports.flatMap((buildableExport)=>{
@@ -310,8 +380,8 @@ const build = async (input)=>{
310
380
  elapsedTime: Date.now() - initialTime,
311
381
  filePath: outputFilePath
312
382
  });
313
- }).catch((reason)=>{
314
- reject(reason);
383
+ }).catch((error)=>{
384
+ reject(error);
315
385
  });
316
386
  }));
317
387
  }
@@ -321,17 +391,21 @@ const build = async (input)=>{
321
391
  return output;
322
392
  };
323
393
 
394
+ const TEMPORARY_PATH = resolveFromInternalDirectory("dist", "tmp");
395
+ const TEMPORARY_DOWNLOAD_PATH = join(TEMPORARY_PATH, "zip");
396
+ const TEMPORARY_RUNTIME_PATH = join(TEMPORARY_PATH, "runtime");
324
397
  const createCompileCommand = (program)=>{
325
398
  return program.command({
326
399
  name: "compile",
327
400
  description: "Compiles the source code into a self-contained executable"
328
- }).task({
329
- key: "osType",
330
- label: "Get context",
331
- handler () {
332
- const type = os.type();
333
- return type === "Windows_NT" ? "windows" : type === "Darwin" ? "macos" : "linux";
334
- }
401
+ }).option({
402
+ key: "targetInput",
403
+ name: {
404
+ long: "target",
405
+ short: "t"
406
+ },
407
+ description: "Set a different cross-compilation target",
408
+ defaultValue: "local"
335
409
  }).task({
336
410
  key: "config",
337
411
  label: "Create configuration",
@@ -342,6 +416,32 @@ const createCompileCommand = (program)=>{
342
416
  standalone: true
343
417
  });
344
418
  }
419
+ }).task({
420
+ key: "osType",
421
+ label ({ targetInput }) {
422
+ return `Get \`${targetInput}\` runtime`;
423
+ },
424
+ async handler ({ targetInput }) {
425
+ if (targetInput === "local") {
426
+ await copyFile(process.execPath, TEMPORARY_RUNTIME_PATH);
427
+ return getOsType(os.type());
428
+ }
429
+ const matchedRuntimeParts = matchRuntimeParts(targetInput);
430
+ if (!matchedRuntimeParts) {
431
+ 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)`.");
432
+ }
433
+ const osType = getOsType(matchedRuntimeParts.os);
434
+ const extension = osType === "windows" ? "zip" : "tar.gz";
435
+ await download(`https://nodejs.org/download/release/${matchedRuntimeParts.version}/${targetInput}.${extension}`, TEMPORARY_DOWNLOAD_PATH);
436
+ await unzip({
437
+ path: TEMPORARY_DOWNLOAD_PATH,
438
+ targetedArchivePath: osType === "windows" ? join(targetInput, "node.exe") : join(targetInput, "bin", "node")
439
+ }, {
440
+ directoryPath: dirname(TEMPORARY_RUNTIME_PATH),
441
+ filename: basename(TEMPORARY_RUNTIME_PATH)
442
+ });
443
+ return osType;
444
+ }
345
445
  }).task({
346
446
  label: "Build",
347
447
  async handler ({ config }) {
@@ -367,6 +467,30 @@ const createCompileCommand = (program)=>{
367
467
  }
368
468
  });
369
469
  };
470
+ const getOsType = (input)=>{
471
+ switch(input){
472
+ case "Windows_NT":
473
+ case "win":
474
+ {
475
+ return "windows";
476
+ }
477
+ case "Darwin":
478
+ case "darwin":
479
+ {
480
+ return "macos";
481
+ }
482
+ case "Linux":
483
+ case "linux":
484
+ {
485
+ return "linux";
486
+ }
487
+ default:
488
+ {
489
+ throw new Error(`Unsupported operating system \`${input}\``);
490
+ }
491
+ }
492
+ };
493
+ const matchRuntimeParts = createRegExpMatcher(/^node-(?<version>v\d+\.\d+\.\d+)-(?<os>darwin|linux|win)-(?<architecture>arm64|x64|x86)$/);
370
494
  const compile = async ({ bin, input, osType })=>{
371
495
  const inputFileName = basename(input);
372
496
  const inputDirectory = dirname(input);
@@ -382,11 +506,11 @@ const compile = async ({ bin, input, osType })=>{
382
506
  output: blobFileName,
383
507
  useCodeCache: false,
384
508
  useSnapshot: false
385
- }), "utf-8");
509
+ }));
386
510
  await Promise.all([
387
- `node --experimental-sea-config ${seaConfigFileName}`,
388
- `node -e "require('fs').copyFileSync(process.execPath, '${executableFileName}')"`
389
- ].map(async (command)=>helpers.exec(command)));
511
+ helpers.exec(`node --experimental-sea-config ${seaConfigFileName}`),
512
+ copyFile(TEMPORARY_RUNTIME_PATH, executableFileName)
513
+ ]);
390
514
  if (osType === "macos") {
391
515
  await helpers.exec(`codesign --remove-signature ${executableFileName}`);
392
516
  }
@@ -396,8 +520,9 @@ const compile = async ({ bin, input, osType })=>{
396
520
  }
397
521
  await Promise.all([
398
522
  blobFileName,
399
- seaConfigFileName
400
- ].map(async (file)=>rm(file)));
523
+ seaConfigFileName,
524
+ TEMPORARY_PATH
525
+ ].map(async (path)=>removePath(path)));
401
526
  };
402
527
 
403
528
  const createBuildCommand = (program)=>{
@@ -433,6 +558,10 @@ const createBuildCommand = (program)=>{
433
558
  return index === 0 ? message : ` ${message}`;
434
559
  }).join("\n"), {
435
560
  label: `${item.filePath} (took ${item.elapsedTime}ms)`,
561
+ lineBreak: {
562
+ end: false,
563
+ start: true
564
+ },
436
565
  type: "information"
437
566
  });
438
567
  });
package/package.json CHANGED
@@ -1,18 +1,7 @@
1
1
  {
2
2
  "name": "quickbundle",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "The zero-configuration transpiler and bundler for the web",
5
- "author": {
6
- "name": "Ayoub Adib",
7
- "email": "adbayb@gmail.com",
8
- "url": "https://twitter.com/adbayb"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git@github.com:adbayb/quickbundle.git",
13
- "directory": "quickbundle"
14
- },
15
- "license": "MIT",
16
5
  "keywords": [
17
6
  "library",
18
7
  "bundle",
@@ -25,13 +14,17 @@
25
14
  "swc",
26
15
  "microbundle"
27
16
  ],
28
- "bin": {
29
- "quickbundle": "bin/index.mjs"
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git@github.com:adbayb/quickbundle.git",
20
+ "directory": "quickbundle"
21
+ },
22
+ "license": "MIT",
23
+ "author": {
24
+ "name": "Ayoub Adib",
25
+ "email": "adbayb@gmail.com",
26
+ "url": "https://twitter.com/adbayb"
30
27
  },
31
- "files": [
32
- "bin",
33
- "dist"
34
- ],
35
28
  "type": "module",
36
29
  "exports": {
37
30
  ".": {
@@ -39,32 +32,42 @@
39
32
  "default": "./dist/index.mjs"
40
33
  }
41
34
  },
42
- "peerDependencies": {
43
- "typescript": "^4.7.0 || ^5.0.0"
44
- },
45
- "peerDependenciesMeta": {
46
- "typescript": {
47
- "optional": true
48
- }
35
+ "bin": {
36
+ "quickbundle": "bin/index.mjs"
49
37
  },
38
+ "files": [
39
+ "bin",
40
+ "dist"
41
+ ],
50
42
  "dependencies": {
51
- "@rollup/plugin-commonjs": "^28.0.1",
43
+ "@rollup/plugin-commonjs": "^28.0.2",
52
44
  "@rollup/plugin-json": "^6.1.0",
53
- "@rollup/plugin-node-resolve": "^15.3.0",
45
+ "@rollup/plugin-node-resolve": "^16.0.0",
54
46
  "@rollup/plugin-url": "^8.0.2",
55
- "@swc/core": "^1.9.1",
47
+ "@swc/core": "^1.10.18",
48
+ "decompress": "^4.2.1",
56
49
  "gzip-size": "^7.0.0",
57
- "rollup": "^4.24.4",
50
+ "rollup": "^4.34.8",
58
51
  "rollup-plugin-dts": "^6.1.1",
59
- "rollup-plugin-node-externals": "^7.1.3",
52
+ "rollup-plugin-node-externals": "^8.0.0",
60
53
  "rollup-plugin-swc3": "^0.12.1",
61
- "termost": "^1.2.0"
54
+ "termost": "^1.4.0"
62
55
  },
63
56
  "devDependencies": {
64
- "@types/node": "22.9.0"
57
+ "@types/decompress": "4.2.7",
58
+ "@types/node": "22.13.5"
59
+ },
60
+ "peerDependencies": {
61
+ "typescript": "^4.7.0 || ^5.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "typescript": {
65
+ "optional": true
66
+ }
65
67
  },
66
68
  "scripts": {
67
69
  "build": "rollup --config ./bundler.config.ts --configPlugin rollup-plugin-swc3",
70
+ "test": "exit 0",
68
71
  "watch": "rollup --watch --config ./bundler.config.ts --configPlugin rollup-plugin-swc3"
69
72
  }
70
73
  }