quickbundle 2.6.0 → 2.8.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 +76 -23
- package/bin/index.mjs +1 -1
- package/dist/index.mjs +406 -165
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -10,22 +10,27 @@
|
|
|
10
10
|
|
|
11
11
|
Quickbundle allows you to bundle a library in a **quick**, **fast** and **easy** way:
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
+
- Support of `cjs` & `esm` module formats output.
|
|
17
|
+
- Support of several loaders including JavaScript, TypeScript, JSX, JSON, and Images.
|
|
18
|
+
- TypeScript's declaration file (`.d.ts`) bundling.
|
|
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.
|
|
19
22
|
|
|
20
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.
|
|
21
24
|
|
|
22
25
|
[^2]: A [TypeScript / JavaScript transpiler](https://swc.rs/) for quicker code processing including TypeScript transpilation, JavaScript transformation, and, minification.
|
|
23
26
|
|
|
27
|
+
[^3]: It relies on the [Node.js single executable applications feature](https://nodejs.org/api/single-executable-applications.html).
|
|
28
|
+
|
|
24
29
|
<br>
|
|
25
30
|
|
|
26
31
|
## 🚀 Quick Start
|
|
27
32
|
|
|
28
|
-
1️⃣ Install
|
|
33
|
+
### 1️⃣ Install
|
|
29
34
|
|
|
30
35
|
```bash
|
|
31
36
|
# Npm
|
|
@@ -36,9 +41,11 @@ pnpm add quickbundle
|
|
|
36
41
|
yarn add quickbundle
|
|
37
42
|
```
|
|
38
43
|
|
|
39
|
-
2️⃣
|
|
44
|
+
### 2️⃣ Update your package configuration (`package.json`)
|
|
45
|
+
|
|
46
|
+
#### For building libraries
|
|
40
47
|
|
|
41
|
-
-
|
|
48
|
+
- When exporting exclusively ESM format:
|
|
42
49
|
|
|
43
50
|
```jsonc
|
|
44
51
|
{
|
|
@@ -53,8 +60,8 @@ yarn add quickbundle
|
|
|
53
60
|
},
|
|
54
61
|
"./otherModulePath": {
|
|
55
62
|
// ...
|
|
56
|
-
}
|
|
57
|
-
}
|
|
63
|
+
},
|
|
64
|
+
},
|
|
58
65
|
"scripts": {
|
|
59
66
|
"build": "quickbundle build", // Production mode (optimizes bundle)
|
|
60
67
|
"watch": "quickbundle watch", // Development mode (watches each file change)
|
|
@@ -63,7 +70,10 @@ yarn add quickbundle
|
|
|
63
70
|
}
|
|
64
71
|
```
|
|
65
72
|
|
|
66
|
-
-
|
|
73
|
+
- When exporting both CommonJS (CJS) and ECMAScript Modules (ESM) format:
|
|
74
|
+
|
|
75
|
+
> [!warning]
|
|
76
|
+
> Please be aware of [dual-package-hazard-related risks](https://github.com/nodejs/package-examples?tab=readme-ov-file#dual-package-hazard) first.
|
|
67
77
|
|
|
68
78
|
```jsonc
|
|
69
79
|
{
|
|
@@ -80,8 +90,8 @@ yarn add quickbundle
|
|
|
80
90
|
},
|
|
81
91
|
"./otherModulePath": {
|
|
82
92
|
// ...
|
|
83
|
-
}
|
|
84
|
-
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
85
95
|
"scripts": {
|
|
86
96
|
"build": "quickbundle build", // Production mode (optimizes bundle)
|
|
87
97
|
"watch": "quickbundle watch", // Development mode (watches each file change)
|
|
@@ -90,7 +100,30 @@ yarn add quickbundle
|
|
|
90
100
|
}
|
|
91
101
|
```
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
#### For compiling executables
|
|
104
|
+
|
|
105
|
+
> [!warning]
|
|
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
|
+
>
|
|
108
|
+
> - 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
|
+
> - The bundled code must use the CommonJS module system 🛑. To address this, Quickbundle always outputs CJS modules in compile mode ✅.
|
|
110
|
+
|
|
111
|
+
```jsonc
|
|
112
|
+
{
|
|
113
|
+
"name": "lib", // Package name
|
|
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
|
+
"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, 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
|
+
},
|
|
118
|
+
// "bin": "./dist/index.cjs", // Or, if the binary name follows the package name, you can define a string-based `bin` value.
|
|
119
|
+
"scripts": {
|
|
120
|
+
"build": "quickbundle compile",
|
|
121
|
+
},
|
|
122
|
+
// ...
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3️⃣ Try it
|
|
94
127
|
|
|
95
128
|
```bash
|
|
96
129
|
# Npm
|
|
@@ -109,14 +142,14 @@ yarn build
|
|
|
109
142
|
|
|
110
143
|
By default, Quickbundle does the following built-in optimizations during the bundling process:
|
|
111
144
|
|
|
112
|
-
-
|
|
113
|
-
-
|
|
145
|
+
- Include, in the build output, only the code that is effectively imported and used in the source code. Setting the `sideEffects` package.json field to `false` marks the package as a side-effect-free one and helps Quickbundle to safely prune unused exports.
|
|
146
|
+
- [Identify and annotate](https://rollupjs.org/configuration-options/#treeshake-annotations) side-effect-free code (functions, ...) to enable a fine-grained dead-code elimination process later consumer side. For example, if a consumer uses only one library API, build output annotations added by Quickbundle allow the consumer's bundler remove all other unused APIs.
|
|
114
147
|
|
|
115
148
|
However, Quickbundle doesn't minify the build output. Indeed, in general, **if the build targets a library (the most Quickbundle use case)**, minification is not necessary since enabling it can introduce some challenges:
|
|
116
149
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
150
|
+
- Reduce the build output discoverability inside the `node_modules` folder (minified code is an obfuscated code that can be hard to read for code audit/debugging purposes).
|
|
151
|
+
- Generate suboptimal source maps for the bundled library, as the consumer bundler will generate source maps based on the already minified library build (transformed code, mangled variable names, etc.).
|
|
152
|
+
- Risk of side effects with double optimizations (producer side and then consumer side).
|
|
120
153
|
|
|
121
154
|
Popular open source libraries ([Vue](https://unpkg.com/browse/vue@3.4.24/dist/), [SolidJS](https://unpkg.com/browse/solid-js@1.8.17/dist/), [Material UI](https://unpkg.com/browse/@material-ui/core@4.12.4/), ...) do not provide minified builds as the build optimization is sensitive to the consumer context (e.g. environment targets (browser support), ...) and needs to be fully owned upstream (i.e. consumer/application-side).
|
|
122
155
|
|
|
@@ -138,11 +171,29 @@ quickbundle watch --source-maps
|
|
|
138
171
|
|
|
139
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).
|
|
140
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
|
+
|
|
141
192
|
<br>
|
|
142
193
|
|
|
143
194
|
## 🤩 Used by
|
|
144
195
|
|
|
145
|
-
-
|
|
196
|
+
- [@adbayb/stack](https://github.com/adbayb/stack) My opinionated toolbox for JavaScript/TypeScript projects.
|
|
146
197
|
|
|
147
198
|
<br>
|
|
148
199
|
|
|
@@ -154,11 +205,13 @@ We're open to new contributions, you can find more details [here](./CONTRIBUTING
|
|
|
154
205
|
|
|
155
206
|
## 💙 Acknowledgements
|
|
156
207
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
208
|
+
- The backend is powered by [Rollup](https://github.com/rollup/rollup) and its plugin ecosystem (including [SWC](https://github.com/swc-project/swc)) to make blazing-fast builds. A special shoutout to all contributors involved.
|
|
209
|
+
- The zero-configuration approach was inspired by [microbundle](https://github.com/developit/microbundle). A special shoutout to its author [Jason Miller](https://github.com/developit) and [all contributors](https://github.com/developit/microbundle/graphs/contributors).
|
|
159
210
|
|
|
160
211
|
<br>
|
|
161
212
|
|
|
162
213
|
## 📖 License
|
|
163
214
|
|
|
164
215
|
[MIT](./LICENSE "License MIT").
|
|
216
|
+
|
|
217
|
+
<br>
|
package/bin/index.mjs
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,105 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { helpers, termost } from 'termost';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
2
|
+
import { finished } from 'node:stream/promises';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { resolve, dirname, join, basename } from 'node:path';
|
|
6
|
+
import { copyFile as copyFile$1, rm, writeFile as writeFile$1, rename, mkdir, readFile as readFile$1 } from 'node:fs/promises';
|
|
7
|
+
import { createWriteStream } from 'node:fs';
|
|
8
|
+
import decompress from 'decompress';
|
|
9
|
+
import { watch as watch$1, rollup } from 'rollup';
|
|
9
10
|
import { createRequire } from 'node:module';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import dts from 'rollup-plugin-dts';
|
|
12
|
-
import externals from 'rollup-plugin-node-externals';
|
|
13
11
|
import { swc } from 'rollup-plugin-swc3';
|
|
14
|
-
import
|
|
12
|
+
import externals from 'rollup-plugin-node-externals';
|
|
13
|
+
import dts from 'rollup-plugin-dts';
|
|
14
|
+
import url from '@rollup/plugin-url';
|
|
15
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
16
|
+
import json from '@rollup/plugin-json';
|
|
17
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import { gzipSize } from 'gzip-size';
|
|
20
|
+
|
|
21
|
+
var name = "quickbundle";
|
|
22
|
+
var version = "2.8.0";
|
|
23
|
+
|
|
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
|
+
};
|
|
90
|
+
const createCommand = (program, input)=>{
|
|
91
|
+
return program.command(input).option({
|
|
92
|
+
key: "minification",
|
|
93
|
+
name: "minification",
|
|
94
|
+
description: "Enable minification",
|
|
95
|
+
defaultValue: false
|
|
96
|
+
}).option({
|
|
97
|
+
key: "sourceMaps",
|
|
98
|
+
name: "source-maps",
|
|
99
|
+
description: "Enable source maps generation",
|
|
100
|
+
defaultValue: false
|
|
101
|
+
});
|
|
102
|
+
};
|
|
15
103
|
|
|
16
104
|
const onLog = (_, log)=>{
|
|
17
105
|
if (log.message.includes("Generated an empty chunk")) return;
|
|
@@ -20,68 +108,109 @@ const isRecord = (value)=>{
|
|
|
20
108
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
109
|
};
|
|
22
110
|
|
|
23
|
-
const
|
|
24
|
-
process.env.NODE_ENV ??= "
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const initialTime = Date.now();
|
|
28
|
-
const bundle = await rollup({
|
|
29
|
-
...config,
|
|
111
|
+
const watch = (input)=>{
|
|
112
|
+
process.env.NODE_ENV ??= "development";
|
|
113
|
+
const watcher = watch$1(input.data.map((configItem)=>({
|
|
114
|
+
...configItem,
|
|
30
115
|
onLog
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
116
|
+
})));
|
|
117
|
+
let startDuration;
|
|
118
|
+
console.clear();
|
|
119
|
+
watcher.on("event", async (event)=>{
|
|
120
|
+
switch(event.code){
|
|
121
|
+
case "START":
|
|
122
|
+
{
|
|
123
|
+
startDuration = Date.now();
|
|
124
|
+
clearLog(`Build in progress...`, {
|
|
125
|
+
type: "information"
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
case "BUNDLE_END":
|
|
130
|
+
await event.result.close();
|
|
131
|
+
break;
|
|
132
|
+
case "END":
|
|
133
|
+
{
|
|
134
|
+
const duration = Date.now() - startDuration;
|
|
135
|
+
clearLog(`Build done in ${duration}ms (at ${new Date().toLocaleTimeString()})`, {
|
|
136
|
+
type: "success"
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
case "ERROR":
|
|
141
|
+
{
|
|
142
|
+
const { error } = event;
|
|
143
|
+
clearLog(error.message, {
|
|
144
|
+
type: "error"
|
|
145
|
+
});
|
|
146
|
+
console.error("\n", error);
|
|
147
|
+
return;
|
|
41
148
|
}
|
|
42
|
-
promises.push(new Promise((resolve, reject)=>{
|
|
43
|
-
bundle.write(outputEntry).then(()=>{
|
|
44
|
-
resolve({
|
|
45
|
-
elapsedTime: Date.now() - initialTime,
|
|
46
|
-
filename: outputFilename
|
|
47
|
-
});
|
|
48
|
-
}).catch(reject);
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
output.push(...await Promise.all(promises));
|
|
52
149
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const clearLog = (...input)=>{
|
|
153
|
+
console.clear();
|
|
154
|
+
helpers.message(...input);
|
|
55
155
|
};
|
|
56
|
-
|
|
57
|
-
const CWD = process.cwd();
|
|
58
156
|
|
|
59
157
|
const require = createRequire(import.meta.url);
|
|
60
|
-
const PKG = require(
|
|
61
|
-
const
|
|
158
|
+
const PKG = require(resolveFromExternalDirectory("package.json"));
|
|
159
|
+
const createConfiguration = (options = {
|
|
62
160
|
minification: false,
|
|
63
|
-
sourceMaps: false
|
|
161
|
+
sourceMaps: false,
|
|
162
|
+
standalone: false
|
|
64
163
|
})=>{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
164
|
+
const buildableExports = getBuildableExports(options);
|
|
165
|
+
return {
|
|
166
|
+
data: buildableExports.flatMap((buildableExport)=>{
|
|
167
|
+
return [
|
|
168
|
+
buildableExport.source && createMainConfig({
|
|
169
|
+
...buildableExport,
|
|
170
|
+
source: buildableExport.source
|
|
171
|
+
}, options),
|
|
172
|
+
buildableExport.source && buildableExport.types && createTypesConfig({
|
|
173
|
+
source: buildableExport.source,
|
|
174
|
+
types: buildableExport.types
|
|
175
|
+
}, options)
|
|
176
|
+
].filter(Boolean);
|
|
177
|
+
}),
|
|
178
|
+
metadata: buildableExports
|
|
179
|
+
};
|
|
77
180
|
};
|
|
78
|
-
|
|
181
|
+
// eslint-disable-next-line sonarjs/cyclomatic-complexity
|
|
182
|
+
const getBuildableExports = ({ standalone })=>{
|
|
183
|
+
if (standalone) {
|
|
184
|
+
/**
|
|
185
|
+
* Entry-point resolution invariants for standalone target (mostly binaries).
|
|
186
|
+
*/ if (!PKG.source || !PKG.bin || !PKG.name) {
|
|
187
|
+
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.");
|
|
188
|
+
}
|
|
189
|
+
const bin = PKG.bin;
|
|
190
|
+
const name = PKG.name;
|
|
191
|
+
const source = PKG.source;
|
|
192
|
+
if (isRecord(bin)) {
|
|
193
|
+
return Object.entries(bin).map((data)=>({
|
|
194
|
+
bin: data[0],
|
|
195
|
+
require: data[1],
|
|
196
|
+
source
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
// 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.
|
|
202
|
+
bin: name.replace(/^(@.*?\/)/, ""),
|
|
203
|
+
require: bin,
|
|
204
|
+
source
|
|
205
|
+
}
|
|
206
|
+
];
|
|
207
|
+
}
|
|
79
208
|
/**
|
|
80
|
-
* Entry-point resolution:
|
|
209
|
+
* Entry-point resolution invariants for non-standalone target (mostly libraries):
|
|
81
210
|
* Following the [package entry-point specification](https://nodejs.org/api/packages.html#package-entry-points),
|
|
82
211
|
* whenever an export object is defined, it take precedence over other classical entry-point fields
|
|
83
212
|
* (such as main, module, and types defined at the root package.json level).
|
|
84
|
-
*/ if (PKG.main
|
|
213
|
+
*/ if (PKG.main || PKG.module || PKG.types || !PKG.exports) {
|
|
85
214
|
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`)).");
|
|
86
215
|
}
|
|
87
216
|
const buildableExportFields = [
|
|
@@ -130,15 +259,15 @@ const getBuildableExports = ()=>{
|
|
|
130
259
|
}
|
|
131
260
|
return output;
|
|
132
261
|
};
|
|
133
|
-
const getPlugins = (
|
|
262
|
+
const getPlugins = (customPlugins, options)=>{
|
|
134
263
|
return [
|
|
135
|
-
externals({
|
|
264
|
+
!options.standalone && externals({
|
|
136
265
|
builtins: true,
|
|
137
266
|
deps: true,
|
|
138
267
|
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
268
|
+
* As they're not installed consumer side, `devDependencies` are declared as internal dependencies (via the `false` value)
|
|
269
|
+
* and bundled into the dist if and only if imported and not listed as `peerDependencies` (otherwise, they're considered external).
|
|
270
|
+
*/ devDeps: false,
|
|
142
271
|
optDeps: true,
|
|
143
272
|
peerDeps: true
|
|
144
273
|
}),
|
|
@@ -146,7 +275,7 @@ const getPlugins = (...customPlugins)=>{
|
|
|
146
275
|
url(),
|
|
147
276
|
json(),
|
|
148
277
|
...customPlugins
|
|
149
|
-
];
|
|
278
|
+
].filter(Boolean);
|
|
150
279
|
};
|
|
151
280
|
const createMainConfig = (entryPoints, options)=>{
|
|
152
281
|
const { minification, sourceMaps } = options;
|
|
@@ -158,6 +287,7 @@ const createMainConfig = (entryPoints, options)=>{
|
|
|
158
287
|
entryPoints.require && {
|
|
159
288
|
file: entryPoints.require,
|
|
160
289
|
format: "cjs",
|
|
290
|
+
inlineDynamicImports: Boolean(options.standalone),
|
|
161
291
|
sourcemap: sourceMaps
|
|
162
292
|
},
|
|
163
293
|
esmInput && {
|
|
@@ -169,13 +299,16 @@ const createMainConfig = (entryPoints, options)=>{
|
|
|
169
299
|
return {
|
|
170
300
|
input: entryPoints.source,
|
|
171
301
|
output,
|
|
172
|
-
plugins: getPlugins(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
302
|
+
plugins: getPlugins([
|
|
303
|
+
nodeResolve(),
|
|
304
|
+
swc({
|
|
305
|
+
minify: minification,
|
|
306
|
+
sourceMaps
|
|
307
|
+
})
|
|
308
|
+
], options)
|
|
176
309
|
};
|
|
177
310
|
};
|
|
178
|
-
const createTypesConfig = (entryPoints)=>{
|
|
311
|
+
const createTypesConfig = (entryPoints, options)=>{
|
|
179
312
|
return {
|
|
180
313
|
input: entryPoints.source,
|
|
181
314
|
output: [
|
|
@@ -183,52 +316,216 @@ const createTypesConfig = (entryPoints)=>{
|
|
|
183
316
|
file: entryPoints.types
|
|
184
317
|
}
|
|
185
318
|
],
|
|
186
|
-
plugins: getPlugins(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
319
|
+
plugins: getPlugins([
|
|
320
|
+
nodeResolve({
|
|
321
|
+
/**
|
|
322
|
+
* The `exports` conditional fields definition order is important in the `package.json file`.
|
|
323
|
+
* To be resolved first, `types` field must always come first in the package.json exports definition.
|
|
324
|
+
* @see https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#package-json-exports-imports-and-self-referencing.
|
|
325
|
+
*/ exportConditions: [
|
|
326
|
+
"types"
|
|
327
|
+
]
|
|
328
|
+
}),
|
|
329
|
+
dts({
|
|
330
|
+
compilerOptions: {
|
|
331
|
+
incremental: false
|
|
332
|
+
},
|
|
333
|
+
respectExternal: true
|
|
334
|
+
})
|
|
335
|
+
], options)
|
|
200
336
|
};
|
|
201
337
|
};
|
|
202
|
-
// eslint-disable-next-line import/no-default-export
|
|
203
|
-
createConfigurations();
|
|
204
338
|
|
|
205
|
-
const
|
|
339
|
+
const createWatchCommand = (program)=>{
|
|
340
|
+
return createCommand(program, {
|
|
341
|
+
name: "watch",
|
|
342
|
+
description: "Watch and rebuild on any code change (development mode)"
|
|
343
|
+
}).task({
|
|
344
|
+
handler (context) {
|
|
345
|
+
watch(createConfiguration({
|
|
346
|
+
minification: context.minification,
|
|
347
|
+
sourceMaps: context.sourceMaps,
|
|
348
|
+
standalone: false
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
};
|
|
206
353
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
354
|
+
const build = async (input)=>{
|
|
355
|
+
process.env.NODE_ENV ??= "production";
|
|
356
|
+
const { data: configurations } = input;
|
|
357
|
+
const output = [];
|
|
358
|
+
for (const config of configurations){
|
|
359
|
+
const initialTime = Date.now();
|
|
360
|
+
const bundle = await rollup({
|
|
361
|
+
...config,
|
|
362
|
+
onLog
|
|
363
|
+
});
|
|
364
|
+
if (config.output) {
|
|
365
|
+
const outputEntries = Array.isArray(config.output) ? config.output : [
|
|
366
|
+
config.output
|
|
367
|
+
];
|
|
368
|
+
const promises = [];
|
|
369
|
+
for (const outputEntry of outputEntries){
|
|
370
|
+
const outputFilePath = outputEntry.file ?? outputEntry.dir;
|
|
371
|
+
if (!outputFilePath) {
|
|
372
|
+
throw new Error("Misconfigured file entry point. Make sure to define an `import`, `require`, or `default` field.");
|
|
373
|
+
}
|
|
374
|
+
promises.push(new Promise((resolve, reject)=>{
|
|
375
|
+
bundle.write(outputEntry).then(()=>{
|
|
376
|
+
resolve({
|
|
377
|
+
elapsedTime: Date.now() - initialTime,
|
|
378
|
+
filePath: outputFilePath
|
|
379
|
+
});
|
|
380
|
+
}).catch((reason)=>{
|
|
381
|
+
reject(reason);
|
|
382
|
+
});
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
output.push(...await Promise.all(promises));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return output;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const TEMPORARY_PATH = resolveFromInternalDirectory("dist", "tmp");
|
|
392
|
+
const TEMPORARY_DOWNLOAD_PATH = join(TEMPORARY_PATH, "zip");
|
|
393
|
+
const TEMPORARY_RUNTIME_PATH = join(TEMPORARY_PATH, "runtime");
|
|
394
|
+
const createCompileCommand = (program)=>{
|
|
395
|
+
return program.command({
|
|
396
|
+
name: "compile",
|
|
397
|
+
description: "Compiles the source code into a self-contained executable"
|
|
213
398
|
}).option({
|
|
214
|
-
key: "
|
|
215
|
-
name:
|
|
216
|
-
|
|
217
|
-
|
|
399
|
+
key: "targetInput",
|
|
400
|
+
name: {
|
|
401
|
+
long: "target",
|
|
402
|
+
short: "t"
|
|
403
|
+
},
|
|
404
|
+
description: "Set a different cross-compilation target",
|
|
405
|
+
defaultValue: "local"
|
|
406
|
+
}).task({
|
|
407
|
+
key: "config",
|
|
408
|
+
label: "Create configuration",
|
|
409
|
+
handler () {
|
|
410
|
+
return createConfiguration({
|
|
411
|
+
minification: true,
|
|
412
|
+
sourceMaps: false,
|
|
413
|
+
standalone: true
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}).task({
|
|
417
|
+
key: "osType",
|
|
418
|
+
label ({ targetInput }) {
|
|
419
|
+
return `Get \`${targetInput}\` runtime`;
|
|
420
|
+
},
|
|
421
|
+
async handler ({ targetInput }) {
|
|
422
|
+
if (targetInput === "local") {
|
|
423
|
+
await copyFile(process.execPath, TEMPORARY_RUNTIME_PATH);
|
|
424
|
+
return getOsType(os.type());
|
|
425
|
+
}
|
|
426
|
+
const matchedRuntimeParts = matchRuntimeParts(targetInput);
|
|
427
|
+
if (!matchedRuntimeParts) {
|
|
428
|
+
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)`.");
|
|
429
|
+
}
|
|
430
|
+
const osType = getOsType(matchedRuntimeParts.os);
|
|
431
|
+
const extension = osType === "windows" ? "zip" : "tar.gz";
|
|
432
|
+
await download(`https://nodejs.org/download/release/${matchedRuntimeParts.version}/${targetInput}.${extension}`, TEMPORARY_DOWNLOAD_PATH);
|
|
433
|
+
await unzip({
|
|
434
|
+
path: TEMPORARY_DOWNLOAD_PATH,
|
|
435
|
+
targetedArchivePath: osType === "windows" ? join(targetInput, "node.exe") : join(targetInput, "bin", "node")
|
|
436
|
+
}, {
|
|
437
|
+
directoryPath: dirname(TEMPORARY_RUNTIME_PATH),
|
|
438
|
+
filename: basename(TEMPORARY_RUNTIME_PATH)
|
|
439
|
+
});
|
|
440
|
+
return osType;
|
|
441
|
+
}
|
|
442
|
+
}).task({
|
|
443
|
+
label: "Build",
|
|
444
|
+
async handler ({ config }) {
|
|
445
|
+
await build(config);
|
|
446
|
+
}
|
|
447
|
+
}).task({
|
|
448
|
+
label ({ config }) {
|
|
449
|
+
const binaries = config.metadata.map(({ bin })=>{
|
|
450
|
+
if (!bin) return undefined;
|
|
451
|
+
return `\`${bin}\``;
|
|
452
|
+
}).filter(Boolean).join(", ");
|
|
453
|
+
return `Compile ${binaries}`;
|
|
454
|
+
},
|
|
455
|
+
async handler ({ config, osType }) {
|
|
456
|
+
await Promise.all(config.metadata.map(async ({ bin, require })=>{
|
|
457
|
+
if (!require || !bin) return;
|
|
458
|
+
return compile({
|
|
459
|
+
bin,
|
|
460
|
+
input: require,
|
|
461
|
+
osType
|
|
462
|
+
});
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
218
465
|
});
|
|
219
466
|
};
|
|
467
|
+
const getOsType = (input)=>{
|
|
468
|
+
switch(input){
|
|
469
|
+
case "Windows_NT":
|
|
470
|
+
case "win":
|
|
471
|
+
return "windows";
|
|
472
|
+
case "Darwin":
|
|
473
|
+
case "darwin":
|
|
474
|
+
return "macos";
|
|
475
|
+
case "Linux":
|
|
476
|
+
case "linux":
|
|
477
|
+
return "linux";
|
|
478
|
+
default:
|
|
479
|
+
throw new Error(`Unsupported operating system \`${input}\``);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
const matchRuntimeParts = createRegExpMatcher(/^node-(?<version>v\d+\.\d+\.\d+)-(?<os>darwin|linux|win)-(?<architecture>arm64|x64|x86)$/);
|
|
483
|
+
const compile = async ({ bin, input, osType })=>{
|
|
484
|
+
const inputFileName = basename(input);
|
|
485
|
+
const inputDirectory = dirname(input);
|
|
486
|
+
const resolveFromInputDirectory = (...paths)=>{
|
|
487
|
+
return resolve(inputDirectory, ...paths);
|
|
488
|
+
};
|
|
489
|
+
const blobFileName = resolveFromInputDirectory(`${inputFileName}.blob`);
|
|
490
|
+
const executableFileName = resolveFromInputDirectory(`${bin}${osType === "windows" ? ".exe" : ""}`);
|
|
491
|
+
const seaConfigFileName = resolveFromInputDirectory(`${inputFileName}.sea-config.json`);
|
|
492
|
+
await writeFile(seaConfigFileName, JSON.stringify({
|
|
493
|
+
disableExperimentalSEAWarning: true,
|
|
494
|
+
main: input,
|
|
495
|
+
output: blobFileName,
|
|
496
|
+
useCodeCache: false,
|
|
497
|
+
useSnapshot: false
|
|
498
|
+
}));
|
|
499
|
+
await Promise.all([
|
|
500
|
+
helpers.exec(`node --experimental-sea-config ${seaConfigFileName}`),
|
|
501
|
+
copyFile(TEMPORARY_RUNTIME_PATH, executableFileName)
|
|
502
|
+
]);
|
|
503
|
+
if (osType === "macos") {
|
|
504
|
+
await helpers.exec(`codesign --remove-signature ${executableFileName}`);
|
|
505
|
+
}
|
|
506
|
+
await helpers.exec(`npx postject ${executableFileName} NODE_SEA_BLOB ${blobFileName} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ${osType === "macos" ? "--macho-segment-name NODE_SEA" : ""}`);
|
|
507
|
+
if (osType === "macos") {
|
|
508
|
+
await helpers.exec(`codesign --sign - ${executableFileName}`);
|
|
509
|
+
}
|
|
510
|
+
await Promise.all([
|
|
511
|
+
blobFileName,
|
|
512
|
+
seaConfigFileName,
|
|
513
|
+
TEMPORARY_PATH
|
|
514
|
+
].map(async (path)=>removePath(path)));
|
|
515
|
+
};
|
|
220
516
|
|
|
221
517
|
const createBuildCommand = (program)=>{
|
|
222
|
-
createCommand(program, {
|
|
518
|
+
return createCommand(program, {
|
|
223
519
|
name: "build",
|
|
224
520
|
description: "Build the source code (production mode)"
|
|
225
521
|
}).task({
|
|
226
522
|
key: "buildOutput",
|
|
227
523
|
label: "Bundle assets 📦",
|
|
228
524
|
async handler (context) {
|
|
229
|
-
return build(
|
|
525
|
+
return build(createConfiguration({
|
|
230
526
|
minification: context.minification,
|
|
231
|
-
sourceMaps: context.sourceMaps
|
|
527
|
+
sourceMaps: context.sourceMaps,
|
|
528
|
+
standalone: false
|
|
232
529
|
}));
|
|
233
530
|
}
|
|
234
531
|
}).task({
|
|
@@ -249,7 +546,7 @@ const createBuildCommand = (program)=>{
|
|
|
249
546
|
].map((message, index)=>{
|
|
250
547
|
return index === 0 ? message : ` ${message}`;
|
|
251
548
|
}).join("\n"), {
|
|
252
|
-
label: `${item.
|
|
549
|
+
label: `${item.filePath} (took ${item.elapsedTime}ms)`,
|
|
253
550
|
type: "information"
|
|
254
551
|
});
|
|
255
552
|
});
|
|
@@ -261,7 +558,7 @@ const createBuildCommand = (program)=>{
|
|
|
261
558
|
};
|
|
262
559
|
const computeBundleSize = async (buildOutput)=>{
|
|
263
560
|
const computeFileSize = async (buildItemOutput)=>{
|
|
264
|
-
const content = await readFile(buildItemOutput.
|
|
561
|
+
const content = await readFile(buildItemOutput.filePath);
|
|
265
562
|
const gzSize = await gzipSize(content);
|
|
266
563
|
return {
|
|
267
564
|
...buildItemOutput,
|
|
@@ -276,70 +573,14 @@ const formatSize = (bytes)=>{
|
|
|
276
573
|
return kiloBytes < 1 ? `${bytes} B` : `${kiloBytes.toFixed(2)} kB`;
|
|
277
574
|
};
|
|
278
575
|
|
|
279
|
-
const watch = (configurations)=>{
|
|
280
|
-
process.env.NODE_ENV ??= "development";
|
|
281
|
-
const watcher = watch$1(configurations.map((config)=>({
|
|
282
|
-
...config,
|
|
283
|
-
onLog
|
|
284
|
-
})));
|
|
285
|
-
let startDuration;
|
|
286
|
-
console.clear();
|
|
287
|
-
watcher.on("event", async (event)=>{
|
|
288
|
-
switch(event.code){
|
|
289
|
-
case "START":
|
|
290
|
-
{
|
|
291
|
-
startDuration = Date.now();
|
|
292
|
-
clearLog(`Build in progress...`, {
|
|
293
|
-
type: "information"
|
|
294
|
-
});
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
case "BUNDLE_END":
|
|
298
|
-
await event.result.close();
|
|
299
|
-
break;
|
|
300
|
-
case "END":
|
|
301
|
-
{
|
|
302
|
-
const duration = Date.now() - startDuration;
|
|
303
|
-
clearLog(`Build done in ${duration}ms (at ${new Date().toLocaleTimeString()})`, {
|
|
304
|
-
type: "success"
|
|
305
|
-
});
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
case "ERROR":
|
|
309
|
-
{
|
|
310
|
-
const { error } = event;
|
|
311
|
-
clearLog(`${String(error)}`, {
|
|
312
|
-
type: "error"
|
|
313
|
-
});
|
|
314
|
-
console.error("\n", error);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
};
|
|
320
|
-
const clearLog = (...input)=>{
|
|
321
|
-
console.clear();
|
|
322
|
-
helpers.message(...input);
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const createWatchCommand = (program)=>{
|
|
326
|
-
createCommand(program, {
|
|
327
|
-
name: "watch",
|
|
328
|
-
description: "Watch and rebuild on any code change (development mode)"
|
|
329
|
-
}).task({
|
|
330
|
-
handler (context) {
|
|
331
|
-
watch(createConfigurations({
|
|
332
|
-
minification: context.minification,
|
|
333
|
-
sourceMaps: context.sourceMaps
|
|
334
|
-
}));
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
};
|
|
338
|
-
|
|
339
576
|
const createProgram = (...commandBuilders)=>{
|
|
340
|
-
const program = termost(
|
|
577
|
+
const program = termost({
|
|
578
|
+
name,
|
|
579
|
+
description: "The zero-configuration transpiler and bundler for the web",
|
|
580
|
+
version
|
|
581
|
+
});
|
|
341
582
|
for (const commandBuilder of commandBuilders){
|
|
342
583
|
commandBuilder(program);
|
|
343
584
|
}
|
|
344
585
|
};
|
|
345
|
-
createProgram(createBuildCommand, createWatchCommand);
|
|
586
|
+
createProgram(createBuildCommand, createWatchCommand, createCompileCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickbundle",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "The zero-configuration transpiler and bundler for the web",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ayoub Adib",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"typescript": "
|
|
43
|
+
"typescript": "^4.7.0 || ^5.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"typescript": {
|
|
@@ -52,19 +52,21 @@
|
|
|
52
52
|
"@rollup/plugin-json": "^6.1.0",
|
|
53
53
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
54
54
|
"@rollup/plugin-url": "^8.0.2",
|
|
55
|
-
"@swc/core": "^1.
|
|
55
|
+
"@swc/core": "^1.9.1",
|
|
56
|
+
"decompress": "^4.2.1",
|
|
56
57
|
"gzip-size": "^7.0.0",
|
|
57
|
-
"rollup": "^4.24.
|
|
58
|
+
"rollup": "^4.24.4",
|
|
58
59
|
"rollup-plugin-dts": "^6.1.1",
|
|
59
60
|
"rollup-plugin-node-externals": "^7.1.3",
|
|
60
61
|
"rollup-plugin-swc3": "^0.12.1",
|
|
61
|
-
"termost": "^
|
|
62
|
+
"termost": "^1.2.0"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
|
-
"@types/
|
|
65
|
+
"@types/decompress": "4.2.7",
|
|
66
|
+
"@types/node": "22.9.0"
|
|
65
67
|
},
|
|
66
68
|
"scripts": {
|
|
67
|
-
"build": "rollup --config ./
|
|
68
|
-
"watch": "rollup --watch --config ./
|
|
69
|
+
"build": "rollup --config ./bundler.config.ts --configPlugin rollup-plugin-swc3",
|
|
70
|
+
"watch": "rollup --watch --config ./bundler.config.ts --configPlugin rollup-plugin-swc3"
|
|
69
71
|
}
|
|
70
72
|
}
|