@hypernym/bundler 0.1.2 → 0.3.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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import process, { stdout, cwd } from 'node:process';
2
+ import process, { cwd } from 'node:process';
3
3
  import { createArgs } from '@hypernym/args';
4
- import { readFile, mkdir, writeFile, stat } from 'node:fs/promises';
4
+ import { readFile, stat } from 'node:fs/promises';
5
5
  import { resolve, parse } from 'node:path';
6
- import { exists } from '@hypernym/utils/node';
7
- import { dim, magenta, red, cyan, green } from '@hypernym/colors';
6
+ import { exists, writeFile } from '@hypernym/utils/fs';
7
+ import { cyan, dim, red, magenta, green } from '@hypernym/colors';
8
8
  import { build as build$1, transform } from 'esbuild';
9
9
  import { createSpinner } from '@hypernym/spinner';
10
10
  import { isObject } from '@hypernym/utils';
@@ -25,31 +25,28 @@ const externals = [
25
25
  ];
26
26
 
27
27
  const name = "bundler";
28
- const version = `0.1.2`;
28
+ const version = `0.3.0`;
29
29
 
30
30
  const cl = console.log;
31
- const log = (...args) => {
32
- let length = args.length + 2;
33
- const cols = stdout.columns || 80;
34
- const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
35
- for (const arg of args) {
36
- length = length + arg.replace(/\u001b\[.*?m/g, "").length;
37
- }
38
- const repeatLength = cols <= length + time.length ? 0 : cols - (length + time.length);
39
- return cl(...args, " ".repeat(repeatLength), dim(time));
40
- };
41
31
  const logger = {
32
+ info: (...args) => {
33
+ const time = `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`;
34
+ return cl(cyan(name), dim(time), ...args);
35
+ },
36
+ error: (...args) => {
37
+ const time = `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`;
38
+ return cl(red(name), dim(time), ...args);
39
+ },
42
40
  exit: (message) => {
43
- cl();
44
- log(dim(name), dim(version));
45
- log(magenta(name), message);
46
- cl();
41
+ const time = `[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`;
42
+ cl(magenta(name), dim(time), version);
43
+ cl(magenta(name), dim(time), message);
47
44
  return process.exit(1);
48
45
  }
49
46
  };
50
47
 
51
48
  function error(err) {
52
- log(red(name), "Something went wrong...");
49
+ logger.error("Something went wrong...");
53
50
  console.error(err);
54
51
  return process.exit(1);
55
52
  }
@@ -106,9 +103,7 @@ async function loadConfig(cwd, filePath, defaults) {
106
103
  packages: "external"
107
104
  });
108
105
  const code = result.outputFiles[0].text;
109
- const tempDir = resolve(cwd, "node_modules", ".hypernym", "bundler");
110
- const tempConfig = resolve(tempDir, "config.mjs");
111
- await mkdir(tempDir, { recursive: true });
106
+ const tempConfig = resolve(cwd, "node_modules/.hypernym/bundler/config.mjs");
112
107
  await writeFile(tempConfig, code, "utf-8");
113
108
  const content = await import(tempConfig);
114
109
  const config = {
@@ -201,8 +196,8 @@ async function build(cwd, options) {
201
196
  buildTime: 0,
202
197
  files: []
203
198
  };
204
- if (hooks?.["build:before"])
205
- await hooks["build:before"](options);
199
+ if (hooks?.["build:start"])
200
+ await hooks["build:start"](options, buildStats);
206
201
  if (options.entries) {
207
202
  start = Date.now();
208
203
  for (const entry of options.entries) {
@@ -217,13 +212,13 @@ async function build(cwd, options) {
217
212
  _format = "cjs";
218
213
  const output = entry.output || _output;
219
214
  const format = entry.format || _format;
220
- const defaultPlugins = [esbuild(plugins?.esbuild)];
215
+ const _plugins = [esbuild(plugins?.esbuild)];
221
216
  if (plugins?.json) {
222
217
  const jsonOptions = isObject(plugins.json) ? plugins.json : void 0;
223
- defaultPlugins.push(jsonPlugin(jsonOptions));
218
+ _plugins.push(jsonPlugin(jsonOptions));
224
219
  }
225
220
  if (plugins?.replace) {
226
- defaultPlugins.unshift(
221
+ _plugins.unshift(
227
222
  replacePlugin({
228
223
  true: true,
229
224
  ...plugins.replace
@@ -232,12 +227,20 @@ async function build(cwd, options) {
232
227
  }
233
228
  if (plugins?.resolve) {
234
229
  const resolveOptions = isObject(plugins.resolve) ? plugins.resolve : void 0;
235
- defaultPlugins.unshift(resolvePlugin(resolveOptions));
230
+ _plugins.unshift(resolvePlugin(resolveOptions));
231
+ }
232
+ if (hooks?.["rollup:plugins"]) {
233
+ hooks["rollup:plugins"](_plugins, {
234
+ ...entry,
235
+ input,
236
+ output,
237
+ format
238
+ });
236
239
  }
237
240
  const builder = await rollup({
238
241
  input: resolve(cwd, input),
239
242
  external: externals || options.externals,
240
- plugins: defaultPlugins,
243
+ plugins: _plugins,
241
244
  onLog: (level, log) => {
242
245
  if (logFilter(log))
243
246
  buildLogs.push({ level, log });
@@ -268,10 +271,19 @@ async function build(cwd, options) {
268
271
  _format = "cjs";
269
272
  const output = entry.output || _output;
270
273
  const format = entry.format || _format;
274
+ const _plugins = [dts(plugins?.dts)];
275
+ if (hooks?.["rollup:plugins"]) {
276
+ hooks["rollup:plugins"](_plugins, {
277
+ ...entry,
278
+ types,
279
+ output,
280
+ format
281
+ });
282
+ }
271
283
  const builder = await rollup({
272
284
  input: resolve(cwd, types),
273
285
  external: externals || options.externals,
274
- plugins: [dts(plugins?.dts)],
286
+ plugins: _plugins,
275
287
  onLog: (level, log) => {
276
288
  if (logFilter(log))
277
289
  buildLogs.push({ level, log });
@@ -296,33 +308,24 @@ async function build(cwd, options) {
296
308
  }
297
309
  buildStats.buildTime = Date.now() - start;
298
310
  }
299
- if (hooks?.["build:done"])
300
- await hooks["build:done"](options);
311
+ if (hooks?.["build:end"])
312
+ await hooks["build:end"](options, buildStats);
301
313
  return buildStats;
302
314
  }
303
315
 
304
316
  async function createBuilder(cwd, args, options) {
317
+ const { hooks } = options;
318
+ if (hooks?.["bundle:start"])
319
+ await hooks["bundle:start"](options);
320
+ logger.info(version);
321
+ logger.info(`Bundling started...`);
305
322
  const spinner = createSpinner();
306
- cl();
307
- log(dim(name), dim(version));
308
- log(cyan(name), `Bundling started...`);
309
323
  spinner.start({
310
324
  message: `Transforming modules ${green("for production...")}`
311
325
  });
312
- return await build(cwd, options).then((stats) => {
326
+ await build(cwd, options).then((stats) => {
313
327
  spinner.stop({
314
- message: `Modules transformation is ${green("done!")}`,
315
- template: (mark, message) => {
316
- const temp = `${mark}${message}`;
317
- const cols = stdout.columns || 80;
318
- const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
319
- const length = (
320
- // eslint-disable-next-line no-control-regex
321
- temp.replace(/\u001b\[.*?m/g, "").length + time.length + 1
322
- );
323
- const repeatLength = cols <= length ? 0 : cols - length;
324
- return temp + " ".repeat(repeatLength) + dim(time);
325
- }
328
+ message: `Modules transformation is ${green("done!")}`
326
329
  });
327
330
  const check = green("\u2714");
328
331
  const info = cyan("i");
@@ -336,9 +339,9 @@ async function createBuilder(cwd, args, options) {
336
339
  const longestSize = Math.max(
337
340
  ...stats.files.map((v) => formatBytes(v.size).length)
338
341
  );
339
- log(check, `Bundling completed in ${buildTime}`);
340
- log(check, `${modules} modules transformed. Total size is ${buildSize}`);
341
- log(info, "Individual stats per module");
342
+ cl(check, `Bundling completed in ${buildTime}`);
343
+ cl(check, `${modules} modules transformed. Total size is ${buildSize}`);
344
+ cl(info, "Individual stats per module");
342
345
  for (const file of stats.files) {
343
346
  let format = file.format;
344
347
  const base = parse(file.path).base;
@@ -352,11 +355,11 @@ async function createBuilder(cwd, args, options) {
352
355
  if (base.includes(".d."))
353
356
  format = "dts";
354
357
  if (file.logs) {
355
- for (const log2 of file.logs) {
356
- cl(magenta("!"), magenta(log2.log.message));
358
+ for (const log of file.logs) {
359
+ cl(magenta("!"), magenta(log.log.message));
357
360
  }
358
361
  }
359
- log(
362
+ cl(
360
363
  info,
361
364
  dim("\u251C\u2500"),
362
365
  dim(path) + cyan(base),
@@ -368,7 +371,7 @@ async function createBuilder(cwd, args, options) {
368
371
  dim(formatBytes(file.size).padStart(longestSize))
369
372
  );
370
373
  }
371
- log(
374
+ logger.info(
372
375
  check,
373
376
  `Bundle is now fully generated and ready ${green("for production!")}`
374
377
  );
@@ -377,10 +380,12 @@ async function createBuilder(cwd, args, options) {
377
380
  mark: red("\u2716"),
378
381
  message: `Modules transformation is ${red("interupted!")}`
379
382
  });
380
- log(red(name), "Something went wrong...");
383
+ logger.error("Something went wrong...");
381
384
  console.error(err);
382
385
  return process.exit(1);
383
386
  });
387
+ if (hooks?.["bundle:end"])
388
+ await hooks["bundle:end"](options);
384
389
  }
385
390
 
386
391
  async function main() {
@@ -1,4 +1,4 @@
1
- import { OutputOptions } from 'rollup';
1
+ import { OutputOptions, LogLevel, RollupLog, Plugin } from 'rollup';
2
2
  import { RollupReplaceOptions } from '@rollup/plugin-replace';
3
3
  import { RollupJsonOptions } from '@rollup/plugin-json';
4
4
  import { RollupNodeResolveOptions } from '@rollup/plugin-node-resolve';
@@ -13,7 +13,7 @@ interface BuildPlugins {
13
13
  replace?: RollupReplaceOptions;
14
14
  }
15
15
 
16
- interface Entry {
16
+ interface EntryBase {
17
17
  /**
18
18
  * Specifies the path of the transformed module.
19
19
  *
@@ -56,7 +56,7 @@ interface Entry {
56
56
  */
57
57
  logFilter?: string[];
58
58
  }
59
- interface EntryInput extends Entry {
59
+ interface EntryInput extends EntryBase {
60
60
  /**
61
61
  * Specifies the path of the module's build source.
62
62
  */
@@ -68,7 +68,7 @@ interface EntryInput extends Entry {
68
68
  */
69
69
  plugins?: BuildPlugins;
70
70
  }
71
- interface EntryTypes extends Entry {
71
+ interface EntryTypes extends EntryBase {
72
72
  /**
73
73
  * Specifies the path of the module's build source that contains only TS definitions.
74
74
  */
@@ -80,17 +80,156 @@ interface EntryTypes extends Entry {
80
80
  */
81
81
  plugins?: Pick<BuildPlugins, 'dts'>;
82
82
  }
83
- type EntriesOptions = EntryInput | EntryTypes;
83
+ type EntryOptions = EntryInput | EntryTypes;
84
84
 
85
- interface BuildHooks {
85
+ interface BuildLogs {
86
+ level: LogLevel;
87
+ log: RollupLog;
88
+ }
89
+ interface BuildStats {
90
+ /**
91
+ * The root path of the project.
92
+ */
93
+ cwd: string;
94
+ /**
95
+ * Final bundle size.
96
+ */
97
+ size: number;
98
+ /**
99
+ * Total bundle build time.
100
+ */
101
+ buildTime: number;
102
+ /**
103
+ * List of generated bundle modules.
104
+ */
105
+ files: {
106
+ /**
107
+ * Module output path.
108
+ */
109
+ path: string;
110
+ /**
111
+ * Module size.
112
+ */
113
+ size: number;
114
+ /**
115
+ * Build time of individual module.
116
+ */
117
+ buildTime: number;
118
+ /**
119
+ * Module format.
120
+ */
121
+ format: string;
122
+ /**
123
+ * List of warnings from build plugins.
124
+ */
125
+ logs: BuildLogs[];
126
+ }[];
127
+ }
128
+
129
+ interface HooksOptions {
86
130
  /**
87
131
  * Called just before bundling started.
132
+ *
133
+ * @example
134
+ *
135
+ * ```ts
136
+ * export default defineConfig({
137
+ * hooks: {
138
+ * 'bundle:start': async (options) => {
139
+ * // ...
140
+ * }
141
+ * }
142
+ * })
143
+ * ```
144
+ *
145
+ * @default undefined
88
146
  */
89
- 'build:before'?: (options?: Options) => void | Promise<void>;
147
+ 'bundle:start'?: (options?: Options) => void | Promise<void>;
148
+ /**
149
+ * Called just before building started.
150
+ *
151
+ * @example
152
+ *
153
+ * ```ts
154
+ * export default defineConfig({
155
+ * hooks: {
156
+ * 'build:start': async (options, buildStats) => {
157
+ * // ...
158
+ * }
159
+ * }
160
+ * })
161
+ * ```
162
+ *
163
+ * @default undefined
164
+ */
165
+ 'build:start'?: (options?: Options, buildStats?: BuildStats) => void | Promise<void>;
166
+ /**
167
+ * Called just before the initialization of the Rollup plugin.
168
+ *
169
+ * Provides the ability to add and manipulate custom plugins.
170
+ *
171
+ * @example
172
+ *
173
+ * ```ts
174
+ * import { plugin1, plugin2, plugin3 } from './plugins'
175
+ *
176
+ * export default defineConfig({
177
+ * hooks: {
178
+ * 'rollup:plugins': (plugins, entry) => {
179
+ * // adds a custom plugin before the default bundler plugins
180
+ * plugins.unshift(plugin1())
181
+ * // adds a custom plugin after the default bundler plugins
182
+ * plugins.push(plugin2())
183
+ * // adds a custom plugin for a specific entry only
184
+ * if (entry?.input?.includes('./src/index.ts')) {
185
+ * plugins.push(plugin3())
186
+ * }
187
+ * // returns the final list of plugins
188
+ * return plugins
189
+ * }
190
+ * }
191
+ * })
192
+ * ```
193
+ *
194
+ * @default undefined
195
+ */
196
+ 'rollup:plugins'?: (plugins: Plugin[], entry?: Partial<EntryInput> & Partial<Omit<EntryTypes, 'plugins'>>) => Plugin[];
197
+ /**
198
+ * Called right after building is complete.
199
+ *
200
+ * @example
201
+ *
202
+ * ```ts
203
+ * export default defineConfig({
204
+ * hooks: {
205
+ * 'build:end': async (options, buildStats) => {
206
+ * // ...
207
+ * }
208
+ * }
209
+ * })
210
+ * ```
211
+ *
212
+ * @default undefined
213
+ */
214
+ 'build:end'?: (options?: Options, buildStats?: BuildStats) => void | Promise<void>;
90
215
  /**
91
216
  * Called right after bundling is complete.
217
+ *
218
+ * @example
219
+ *
220
+ * ```ts
221
+ * export default defineConfig({
222
+ * hooks: {
223
+ * 'bundle:end': async (options) => {
224
+ * // ...
225
+ * }
226
+ * }
227
+ * })
228
+ * ```
229
+ *
230
+ * @default undefined
92
231
  */
93
- 'build:done'?: (options?: Options) => void | Promise<void>;
232
+ 'bundle:end'?: (options?: Options) => void | Promise<void>;
94
233
  }
95
234
 
96
235
  interface Options {
@@ -98,8 +237,20 @@ interface Options {
98
237
  * Specifies the bundle's entry points.
99
238
  *
100
239
  * It allows you to manually set all build entries and adjust options for each one individually.
240
+ *
241
+ * @example
242
+ *
243
+ * ```ts
244
+ * export default defineConfig({
245
+ * entries: [
246
+ * { input: './src/index.ts' }, // => './dist/index.mjs'
247
+ * { types: './src/types.ts' }, // => './dist/types.d.ts'
248
+ * // ...
249
+ * ]
250
+ * })
251
+ * ```
101
252
  */
102
- entries: EntriesOptions[];
253
+ entries: EntryOptions[];
103
254
  /**
104
255
  * Specifies the output directory for production bundle.
105
256
  *
@@ -118,13 +269,43 @@ interface Options {
118
269
  */
119
270
  externals?: (string | RegExp)[];
120
271
  /**
121
- * Provides a powerful hooking system to further expand build mode.
272
+ * Provides a powerful hooking system to further expand bundling mode.
273
+ *
274
+ * @example
275
+ *
276
+ * ```ts
277
+ * export default defineConfig({
278
+ * hooks: {
279
+ * 'build:end': async (options, buildStats) => {
280
+ * // ...
281
+ * }
282
+ * }
283
+ * })
284
+ * ```
122
285
  *
123
286
  * @default undefined
124
287
  */
125
- hooks?: BuildHooks;
288
+ hooks?: HooksOptions;
126
289
  }
127
290
 
291
+ /**
292
+ * List of global defaults for externals.
293
+ *
294
+ * @example
295
+ *
296
+ * ```ts
297
+ * import { externals } from '@hypernym/bundler'
298
+ *
299
+ * export default defineConfig({
300
+ * entries: [
301
+ * {
302
+ * input: './src/index.ts',
303
+ * externals: [...externals, 'id', /regexp/]
304
+ * },
305
+ * ]
306
+ * })
307
+ * ```
308
+ */
128
309
  declare const externals: RegExp[];
129
310
  declare function defineConfig(options: Options): Options;
130
311
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypernym/bundler",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "author": "Hypernym Studio",
5
5
  "description": "ESM & TS module bundler.",
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@
8
8
  "homepage": "https://github.com/hypernym-studio/bundler",
9
9
  "funding": "https://github.com/sponsors/ivodolenc",
10
10
  "type": "module",
11
+ "types": "./dist/types/index.d.ts",
11
12
  "exports": {
12
13
  ".": {
13
14
  "types": "./dist/types/index.d.ts",
@@ -55,22 +56,22 @@
55
56
  }
56
57
  },
57
58
  "dependencies": {
58
- "@hypernym/args": "^0.2.0",
59
- "@hypernym/colors": "^1.0.0",
60
- "@hypernym/spinner": "^0.1.0",
61
- "@hypernym/utils": "^2.0.2",
59
+ "@hypernym/args": "^0.2.1",
60
+ "@hypernym/colors": "^1.0.1",
61
+ "@hypernym/spinner": "^0.2.0",
62
+ "@hypernym/utils": "^2.1.0",
62
63
  "@rollup/plugin-json": "^6.0.1",
63
64
  "@rollup/plugin-node-resolve": "^15.2.3",
64
- "@rollup/plugin-replace": "^5.0.3",
65
+ "@rollup/plugin-replace": "^5.0.4",
65
66
  "esbuild": "^0.19.4",
66
- "rollup": "^4.0.2",
67
+ "rollup": "^4.1.4",
67
68
  "rollup-plugin-dts": "^6.1.0"
68
69
  },
69
70
  "devDependencies": {
70
- "@hypernym/eslint-config": "^2.0.1",
71
- "@hypernym/prettier-config": "^2.0.1",
71
+ "@hypernym/eslint-config": "^2.0.2",
72
+ "@hypernym/prettier-config": "^2.0.2",
72
73
  "@hypernym/tsconfig": "^1.1.0",
73
- "@types/node": "^20.8.4",
74
+ "@types/node": "^20.8.6",
74
75
  "eslint": "^8.51.0",
75
76
  "prettier": "^3.0.3",
76
77
  "tsx": "^3.13.0",