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.
- package/README.md +1 -0
- package/dist/index.js +51 -137
- 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
|
|
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-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
76
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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,
|
|
390
|
-
|
|
317
|
+
async handler({ config, targetInput }) {
|
|
318
|
+
for (const { bin, require } of config.metadata) {
|
|
391
319
|
if (!require || !bin) return;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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-
|
|
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
|
|
47
|
-
"rolldown-plugin-dts": "^0.
|
|
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.
|
|
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"
|