quickbundle 2.6.0 → 2.7.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 +56 -23
- package/bin/index.mjs +1 -1
- package/dist/index.mjs +292 -165
- package/package.json +8 -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
|
+
- 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
|
+
- Zero configuration: define the build artifacts in your `package.json`, and you're all set!
|
|
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, 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.
|
|
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
|
|
|
@@ -142,7 +175,7 @@ Enabling source map generation is needed only if a build is [obfuscated (minifie
|
|
|
142
175
|
|
|
143
176
|
## 🤩 Used by
|
|
144
177
|
|
|
145
|
-
-
|
|
178
|
+
- [@adbayb/stack](https://github.com/adbayb/stack) My opinionated toolbox for JavaScript/TypeScript projects.
|
|
146
179
|
|
|
147
180
|
<br>
|
|
148
181
|
|
|
@@ -154,8 +187,8 @@ We're open to new contributions, you can find more details [here](./CONTRIBUTING
|
|
|
154
187
|
|
|
155
188
|
## 💙 Acknowledgements
|
|
156
189
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
190
|
+
- 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.
|
|
191
|
+
- 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
192
|
|
|
160
193
|
<br>
|
|
161
194
|
|
package/bin/index.mjs
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { helpers, termost } from 'termost';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
8
|
-
import url from '@rollup/plugin-url';
|
|
2
|
+
import { readFile as readFile$1, writeFile, rm } from 'node:fs/promises';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { watch as watch$1, rollup } from 'rollup';
|
|
5
|
+
import { join, basename, dirname, resolve } from 'node:path';
|
|
9
6
|
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
7
|
import { swc } from 'rollup-plugin-swc3';
|
|
14
|
-
import
|
|
8
|
+
import externals from 'rollup-plugin-node-externals';
|
|
9
|
+
import dts from 'rollup-plugin-dts';
|
|
10
|
+
import url from '@rollup/plugin-url';
|
|
11
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
12
|
+
import json from '@rollup/plugin-json';
|
|
13
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { gzipSize } from 'gzip-size';
|
|
16
|
+
|
|
17
|
+
var name = "quickbundle";
|
|
18
|
+
var version = "2.7.0";
|
|
19
|
+
|
|
20
|
+
const CWD = process.cwd();
|
|
21
|
+
|
|
22
|
+
const readFile = readFile$1;
|
|
23
|
+
const createCommand = (program, input)=>{
|
|
24
|
+
return program.command(input).option({
|
|
25
|
+
key: "minification",
|
|
26
|
+
name: "minification",
|
|
27
|
+
description: "Enable minification",
|
|
28
|
+
defaultValue: false
|
|
29
|
+
}).option({
|
|
30
|
+
key: "sourceMaps",
|
|
31
|
+
name: "source-maps",
|
|
32
|
+
description: "Enable source maps generation",
|
|
33
|
+
defaultValue: false
|
|
34
|
+
});
|
|
35
|
+
};
|
|
15
36
|
|
|
16
37
|
const onLog = (_, log)=>{
|
|
17
38
|
if (log.message.includes("Generated an empty chunk")) return;
|
|
@@ -20,68 +41,109 @@ const isRecord = (value)=>{
|
|
|
20
41
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
42
|
};
|
|
22
43
|
|
|
23
|
-
const
|
|
24
|
-
process.env.NODE_ENV ??= "
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const initialTime = Date.now();
|
|
28
|
-
const bundle = await rollup({
|
|
29
|
-
...config,
|
|
44
|
+
const watch = (input)=>{
|
|
45
|
+
process.env.NODE_ENV ??= "development";
|
|
46
|
+
const watcher = watch$1(input.data.map((configItem)=>({
|
|
47
|
+
...configItem,
|
|
30
48
|
onLog
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
})));
|
|
50
|
+
let startDuration;
|
|
51
|
+
console.clear();
|
|
52
|
+
watcher.on("event", async (event)=>{
|
|
53
|
+
switch(event.code){
|
|
54
|
+
case "START":
|
|
55
|
+
{
|
|
56
|
+
startDuration = Date.now();
|
|
57
|
+
clearLog(`Build in progress...`, {
|
|
58
|
+
type: "information"
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case "BUNDLE_END":
|
|
63
|
+
await event.result.close();
|
|
64
|
+
break;
|
|
65
|
+
case "END":
|
|
66
|
+
{
|
|
67
|
+
const duration = Date.now() - startDuration;
|
|
68
|
+
clearLog(`Build done in ${duration}ms (at ${new Date().toLocaleTimeString()})`, {
|
|
69
|
+
type: "success"
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
case "ERROR":
|
|
74
|
+
{
|
|
75
|
+
const { error } = event;
|
|
76
|
+
clearLog(error.message, {
|
|
77
|
+
type: "error"
|
|
78
|
+
});
|
|
79
|
+
console.error("\n", error);
|
|
80
|
+
return;
|
|
41
81
|
}
|
|
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
82
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const clearLog = (...input)=>{
|
|
86
|
+
console.clear();
|
|
87
|
+
helpers.message(...input);
|
|
55
88
|
};
|
|
56
|
-
|
|
57
|
-
const CWD = process.cwd();
|
|
58
89
|
|
|
59
90
|
const require = createRequire(import.meta.url);
|
|
60
91
|
const PKG = require(join(CWD, "./package.json"));
|
|
61
|
-
const
|
|
92
|
+
const createConfiguration = (options = {
|
|
62
93
|
minification: false,
|
|
63
|
-
sourceMaps: false
|
|
94
|
+
sourceMaps: false,
|
|
95
|
+
standalone: false
|
|
64
96
|
})=>{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
const buildableExports = getBuildableExports(options);
|
|
98
|
+
return {
|
|
99
|
+
data: buildableExports.flatMap((buildableExport)=>{
|
|
100
|
+
return [
|
|
101
|
+
buildableExport.source && createMainConfig({
|
|
102
|
+
...buildableExport,
|
|
103
|
+
source: buildableExport.source
|
|
104
|
+
}, options),
|
|
105
|
+
buildableExport.source && buildableExport.types && createTypesConfig({
|
|
106
|
+
source: buildableExport.source,
|
|
107
|
+
types: buildableExport.types
|
|
108
|
+
}, options)
|
|
109
|
+
].filter(Boolean);
|
|
110
|
+
}),
|
|
111
|
+
metadata: buildableExports
|
|
112
|
+
};
|
|
77
113
|
};
|
|
78
|
-
|
|
114
|
+
// eslint-disable-next-line sonarjs/cyclomatic-complexity
|
|
115
|
+
const getBuildableExports = ({ standalone })=>{
|
|
116
|
+
if (standalone) {
|
|
117
|
+
/**
|
|
118
|
+
* Entry-point resolution invariants for standalone target (mostly binaries).
|
|
119
|
+
*/ if (!PKG.source || !PKG.bin || !PKG.name) {
|
|
120
|
+
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.");
|
|
121
|
+
}
|
|
122
|
+
const bin = PKG.bin;
|
|
123
|
+
const name = PKG.name;
|
|
124
|
+
const source = PKG.source;
|
|
125
|
+
if (isRecord(bin)) {
|
|
126
|
+
return Object.entries(bin).map((data)=>({
|
|
127
|
+
bin: data[0],
|
|
128
|
+
require: data[1],
|
|
129
|
+
source
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
// 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.
|
|
135
|
+
bin: name.replace(/^(@.*?\/)/, ""),
|
|
136
|
+
require: bin,
|
|
137
|
+
source
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
}
|
|
79
141
|
/**
|
|
80
|
-
* Entry-point resolution:
|
|
142
|
+
* Entry-point resolution invariants for non-standalone target (mostly libraries):
|
|
81
143
|
* Following the [package entry-point specification](https://nodejs.org/api/packages.html#package-entry-points),
|
|
82
144
|
* whenever an export object is defined, it take precedence over other classical entry-point fields
|
|
83
145
|
* (such as main, module, and types defined at the root package.json level).
|
|
84
|
-
*/ if (PKG.main
|
|
146
|
+
*/ if (PKG.main || PKG.module || PKG.types || !PKG.exports) {
|
|
85
147
|
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
148
|
}
|
|
87
149
|
const buildableExportFields = [
|
|
@@ -130,15 +192,15 @@ const getBuildableExports = ()=>{
|
|
|
130
192
|
}
|
|
131
193
|
return output;
|
|
132
194
|
};
|
|
133
|
-
const getPlugins = (
|
|
195
|
+
const getPlugins = (customPlugins, options)=>{
|
|
134
196
|
return [
|
|
135
|
-
externals({
|
|
197
|
+
!options.standalone && externals({
|
|
136
198
|
builtins: true,
|
|
137
199
|
deps: true,
|
|
138
200
|
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
201
|
+
* As they're not installed consumer side, `devDependencies` are declared as internal dependencies (via the `false` value)
|
|
202
|
+
* and bundled into the dist if and only if imported and not listed as `peerDependencies` (otherwise, they're considered external).
|
|
203
|
+
*/ devDeps: false,
|
|
142
204
|
optDeps: true,
|
|
143
205
|
peerDeps: true
|
|
144
206
|
}),
|
|
@@ -146,7 +208,7 @@ const getPlugins = (...customPlugins)=>{
|
|
|
146
208
|
url(),
|
|
147
209
|
json(),
|
|
148
210
|
...customPlugins
|
|
149
|
-
];
|
|
211
|
+
].filter(Boolean);
|
|
150
212
|
};
|
|
151
213
|
const createMainConfig = (entryPoints, options)=>{
|
|
152
214
|
const { minification, sourceMaps } = options;
|
|
@@ -158,6 +220,7 @@ const createMainConfig = (entryPoints, options)=>{
|
|
|
158
220
|
entryPoints.require && {
|
|
159
221
|
file: entryPoints.require,
|
|
160
222
|
format: "cjs",
|
|
223
|
+
inlineDynamicImports: Boolean(options.standalone),
|
|
161
224
|
sourcemap: sourceMaps
|
|
162
225
|
},
|
|
163
226
|
esmInput && {
|
|
@@ -169,13 +232,16 @@ const createMainConfig = (entryPoints, options)=>{
|
|
|
169
232
|
return {
|
|
170
233
|
input: entryPoints.source,
|
|
171
234
|
output,
|
|
172
|
-
plugins: getPlugins(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
235
|
+
plugins: getPlugins([
|
|
236
|
+
nodeResolve(),
|
|
237
|
+
swc({
|
|
238
|
+
minify: minification,
|
|
239
|
+
sourceMaps
|
|
240
|
+
})
|
|
241
|
+
], options)
|
|
176
242
|
};
|
|
177
243
|
};
|
|
178
|
-
const createTypesConfig = (entryPoints)=>{
|
|
244
|
+
const createTypesConfig = (entryPoints, options)=>{
|
|
179
245
|
return {
|
|
180
246
|
input: entryPoints.source,
|
|
181
247
|
output: [
|
|
@@ -183,52 +249,169 @@ const createTypesConfig = (entryPoints)=>{
|
|
|
183
249
|
file: entryPoints.types
|
|
184
250
|
}
|
|
185
251
|
],
|
|
186
|
-
plugins: getPlugins(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
252
|
+
plugins: getPlugins([
|
|
253
|
+
nodeResolve({
|
|
254
|
+
/**
|
|
255
|
+
* The `exports` conditional fields definition order is important in the `package.json file`.
|
|
256
|
+
* To be resolved first, `types` field must always come first in the package.json exports definition.
|
|
257
|
+
* @see https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#package-json-exports-imports-and-self-referencing.
|
|
258
|
+
*/ exportConditions: [
|
|
259
|
+
"types"
|
|
260
|
+
]
|
|
261
|
+
}),
|
|
262
|
+
dts({
|
|
263
|
+
compilerOptions: {
|
|
264
|
+
incremental: false
|
|
265
|
+
},
|
|
266
|
+
respectExternal: true
|
|
267
|
+
})
|
|
268
|
+
], options)
|
|
200
269
|
};
|
|
201
270
|
};
|
|
202
|
-
// eslint-disable-next-line import/no-default-export
|
|
203
|
-
createConfigurations();
|
|
204
271
|
|
|
205
|
-
const
|
|
272
|
+
const createWatchCommand = (program)=>{
|
|
273
|
+
return createCommand(program, {
|
|
274
|
+
name: "watch",
|
|
275
|
+
description: "Watch and rebuild on any code change (development mode)"
|
|
276
|
+
}).task({
|
|
277
|
+
handler (context) {
|
|
278
|
+
watch(createConfiguration({
|
|
279
|
+
minification: context.minification,
|
|
280
|
+
sourceMaps: context.sourceMaps,
|
|
281
|
+
standalone: false
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
};
|
|
206
286
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
287
|
+
const build = async (input)=>{
|
|
288
|
+
process.env.NODE_ENV ??= "production";
|
|
289
|
+
const { data: configurations } = input;
|
|
290
|
+
const output = [];
|
|
291
|
+
for (const config of configurations){
|
|
292
|
+
const initialTime = Date.now();
|
|
293
|
+
const bundle = await rollup({
|
|
294
|
+
...config,
|
|
295
|
+
onLog
|
|
296
|
+
});
|
|
297
|
+
if (config.output) {
|
|
298
|
+
const outputEntries = Array.isArray(config.output) ? config.output : [
|
|
299
|
+
config.output
|
|
300
|
+
];
|
|
301
|
+
const promises = [];
|
|
302
|
+
for (const outputEntry of outputEntries){
|
|
303
|
+
const outputFilePath = outputEntry.file ?? outputEntry.dir;
|
|
304
|
+
if (!outputFilePath) {
|
|
305
|
+
throw new Error("Misconfigured file entry point. Make sure to define an `import`, `require`, or `default` field.");
|
|
306
|
+
}
|
|
307
|
+
promises.push(new Promise((resolve, reject)=>{
|
|
308
|
+
bundle.write(outputEntry).then(()=>{
|
|
309
|
+
resolve({
|
|
310
|
+
elapsedTime: Date.now() - initialTime,
|
|
311
|
+
filePath: outputFilePath
|
|
312
|
+
});
|
|
313
|
+
}).catch((reason)=>{
|
|
314
|
+
reject(reason);
|
|
315
|
+
});
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
output.push(...await Promise.all(promises));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return output;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const createCompileCommand = (program)=>{
|
|
325
|
+
return program.command({
|
|
326
|
+
name: "compile",
|
|
327
|
+
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
|
+
}
|
|
335
|
+
}).task({
|
|
336
|
+
key: "config",
|
|
337
|
+
label: "Create configuration",
|
|
338
|
+
handler () {
|
|
339
|
+
return createConfiguration({
|
|
340
|
+
minification: true,
|
|
341
|
+
sourceMaps: false,
|
|
342
|
+
standalone: true
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}).task({
|
|
346
|
+
label: "Build",
|
|
347
|
+
async handler ({ config }) {
|
|
348
|
+
await build(config);
|
|
349
|
+
}
|
|
350
|
+
}).task({
|
|
351
|
+
label ({ config }) {
|
|
352
|
+
const binaries = config.metadata.map(({ bin })=>{
|
|
353
|
+
if (!bin) return undefined;
|
|
354
|
+
return `\`${bin}\``;
|
|
355
|
+
}).filter(Boolean).join(", ");
|
|
356
|
+
return `Compile ${binaries}`;
|
|
357
|
+
},
|
|
358
|
+
async handler ({ config, osType }) {
|
|
359
|
+
await Promise.all(config.metadata.map(async ({ bin, require })=>{
|
|
360
|
+
if (!require || !bin) return;
|
|
361
|
+
return compile({
|
|
362
|
+
bin,
|
|
363
|
+
input: require,
|
|
364
|
+
osType
|
|
365
|
+
});
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
218
368
|
});
|
|
219
369
|
};
|
|
370
|
+
const compile = async ({ bin, input, osType })=>{
|
|
371
|
+
const inputFileName = basename(input);
|
|
372
|
+
const inputDirectory = dirname(input);
|
|
373
|
+
const resolveFromInputDirectory = (...paths)=>{
|
|
374
|
+
return resolve(inputDirectory, ...paths);
|
|
375
|
+
};
|
|
376
|
+
const blobFileName = resolveFromInputDirectory(`${inputFileName}.blob`);
|
|
377
|
+
const executableFileName = resolveFromInputDirectory(`${bin}${osType === "windows" ? ".exe" : ""}`);
|
|
378
|
+
const seaConfigFileName = resolveFromInputDirectory(`${inputFileName}.sea-config.json`);
|
|
379
|
+
await writeFile(seaConfigFileName, JSON.stringify({
|
|
380
|
+
disableExperimentalSEAWarning: true,
|
|
381
|
+
main: input,
|
|
382
|
+
output: blobFileName,
|
|
383
|
+
useCodeCache: false,
|
|
384
|
+
useSnapshot: false
|
|
385
|
+
}), "utf-8");
|
|
386
|
+
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)));
|
|
390
|
+
if (osType === "macos") {
|
|
391
|
+
await helpers.exec(`codesign --remove-signature ${executableFileName}`);
|
|
392
|
+
}
|
|
393
|
+
await helpers.exec(`npx postject ${executableFileName} NODE_SEA_BLOB ${blobFileName} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ${osType === "macos" ? "--macho-segment-name NODE_SEA" : ""}`);
|
|
394
|
+
if (osType === "macos") {
|
|
395
|
+
await helpers.exec(`codesign --sign - ${executableFileName}`);
|
|
396
|
+
}
|
|
397
|
+
await Promise.all([
|
|
398
|
+
blobFileName,
|
|
399
|
+
seaConfigFileName
|
|
400
|
+
].map(async (file)=>rm(file)));
|
|
401
|
+
};
|
|
220
402
|
|
|
221
403
|
const createBuildCommand = (program)=>{
|
|
222
|
-
createCommand(program, {
|
|
404
|
+
return createCommand(program, {
|
|
223
405
|
name: "build",
|
|
224
406
|
description: "Build the source code (production mode)"
|
|
225
407
|
}).task({
|
|
226
408
|
key: "buildOutput",
|
|
227
409
|
label: "Bundle assets 📦",
|
|
228
410
|
async handler (context) {
|
|
229
|
-
return build(
|
|
411
|
+
return build(createConfiguration({
|
|
230
412
|
minification: context.minification,
|
|
231
|
-
sourceMaps: context.sourceMaps
|
|
413
|
+
sourceMaps: context.sourceMaps,
|
|
414
|
+
standalone: false
|
|
232
415
|
}));
|
|
233
416
|
}
|
|
234
417
|
}).task({
|
|
@@ -249,7 +432,7 @@ const createBuildCommand = (program)=>{
|
|
|
249
432
|
].map((message, index)=>{
|
|
250
433
|
return index === 0 ? message : ` ${message}`;
|
|
251
434
|
}).join("\n"), {
|
|
252
|
-
label: `${item.
|
|
435
|
+
label: `${item.filePath} (took ${item.elapsedTime}ms)`,
|
|
253
436
|
type: "information"
|
|
254
437
|
});
|
|
255
438
|
});
|
|
@@ -261,7 +444,7 @@ const createBuildCommand = (program)=>{
|
|
|
261
444
|
};
|
|
262
445
|
const computeBundleSize = async (buildOutput)=>{
|
|
263
446
|
const computeFileSize = async (buildItemOutput)=>{
|
|
264
|
-
const content = await readFile(buildItemOutput.
|
|
447
|
+
const content = await readFile(buildItemOutput.filePath);
|
|
265
448
|
const gzSize = await gzipSize(content);
|
|
266
449
|
return {
|
|
267
450
|
...buildItemOutput,
|
|
@@ -276,70 +459,14 @@ const formatSize = (bytes)=>{
|
|
|
276
459
|
return kiloBytes < 1 ? `${bytes} B` : `${kiloBytes.toFixed(2)} kB`;
|
|
277
460
|
};
|
|
278
461
|
|
|
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
462
|
const createProgram = (...commandBuilders)=>{
|
|
340
|
-
const program = termost(
|
|
463
|
+
const program = termost({
|
|
464
|
+
name,
|
|
465
|
+
description: "The zero-configuration transpiler and bundler for the web",
|
|
466
|
+
version
|
|
467
|
+
});
|
|
341
468
|
for (const commandBuilder of commandBuilders){
|
|
342
469
|
commandBuilder(program);
|
|
343
470
|
}
|
|
344
471
|
};
|
|
345
|
-
createProgram(createBuildCommand, createWatchCommand);
|
|
472
|
+
createProgram(createBuildCommand, createWatchCommand, createCompileCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickbundle",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.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,19 @@
|
|
|
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
56
|
"gzip-size": "^7.0.0",
|
|
57
|
-
"rollup": "^4.24.
|
|
57
|
+
"rollup": "^4.24.4",
|
|
58
58
|
"rollup-plugin-dts": "^6.1.1",
|
|
59
59
|
"rollup-plugin-node-externals": "^7.1.3",
|
|
60
60
|
"rollup-plugin-swc3": "^0.12.1",
|
|
61
|
-
"termost": "^
|
|
61
|
+
"termost": "^1.2.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@types/node": "22.
|
|
64
|
+
"@types/node": "22.9.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
|
-
"build": "rollup --config ./
|
|
68
|
-
"watch": "rollup --watch --config ./
|
|
67
|
+
"build": "rollup --config ./bundler.config.ts --configPlugin rollup-plugin-swc3",
|
|
68
|
+
"watch": "rollup --watch --config ./bundler.config.ts --configPlugin rollup-plugin-swc3"
|
|
69
69
|
}
|
|
70
70
|
}
|