bunchee 6.8.2 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,6 +94,21 @@ Here's a example of entry files and exports configuration:
94
94
  }
95
95
  ```
96
96
 
97
+ #### Wildcard Exports
98
+
99
+ `bunchee` supports wildcard patterns in the `exports` field to automatically generate exports for multiple files:
100
+
101
+ ```json5
102
+ {
103
+ "exports": {
104
+ ".": "./dist/index.js",
105
+ "./features/*": "./dist/features/*.js"
106
+ }
107
+ }
108
+ ```
109
+
110
+ This will automatically discover files in `src/features/` and generate exports like `./features/foo`, `./features/bar`, etc. The wildcard `*` is substituted in both the export path and output path.
111
+
97
112
  ### Output Formats
98
113
 
99
114
  **bunchee** detects the format of each entry-point based on export condition type or the file extension. It supports the following output formats:
@@ -215,6 +230,15 @@ This will match the `bin` field in package.json as:
215
230
 
216
231
  > Note: For multiple `bin` files, the filename should match the key name in the `bin` field.
217
232
 
233
+ ### Native Addon (.node) Support
234
+
235
+ `bunchee` supports bundling native Node.js addon files (`.node` binaries). When you import a `.node` file, it will be copied to the output directory and the import will be rewritten to load it at runtime.
236
+
237
+ ```js
238
+ // src/index.js
239
+ import addon from './native-addon.node'
240
+ ```
241
+
218
242
  ### Server Components
219
243
 
220
244
  **bunchee** supports building React Server Components and Server Actions with directives like `"use client"` or `"use server"`. It generates separate chunks for the server or client boundaries. When integrated to framework like Next.js, it can correctly handles the boundaries with the split chunks.
package/dist/bin/cli.js CHANGED
@@ -6,10 +6,10 @@ var perf_hooks = require('perf_hooks');
6
6
  var fs = require('fs');
7
7
  var fsp = require('fs/promises');
8
8
  var require$$0 = require('tty');
9
+ var tinyglobby = require('tinyglobby');
9
10
  var picomatch = require('picomatch');
10
11
  var index_js = require('../index.js');
11
12
  require('module');
12
- var tinyglobby = require('tinyglobby');
13
13
  var prettyBytes = require('pretty-bytes');
14
14
  var nanospinner = require('nanospinner');
15
15
 
@@ -274,7 +274,9 @@ const getMainFieldExportType = (pkg)=>{
274
274
  };
275
275
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
276
276
  function joinRelativePath(...segments) {
277
- let result = path__default.default.join(...segments);
277
+ // Normalize to forward slashes for cross-platform compatibility
278
+ // Export paths in package.json always use POSIX-style paths
279
+ let result = path__default.default.posix.join(...segments);
278
280
  // If the first segment starts with '.', ensure the result does too.
279
281
  if (segments[0] === '.' && !result.startsWith('.')) {
280
282
  result = './' + result;
@@ -312,6 +314,155 @@ function normalizePath(filePath) {
312
314
  return filePath.replace(/\\/g, '/');
313
315
  }
314
316
 
317
+ /**
318
+ * Check if an export key contains a wildcard pattern
319
+ */ function hasWildcardPattern(exportKey) {
320
+ return exportKey.includes('*');
321
+ }
322
+ /**
323
+ * Replace wildcard in output path with matched subpath
324
+ * Example: "./dist/features/*.js" with "foo" -> "./dist/features/foo.js"
325
+ */ function substituteWildcardInPath(outputPath, matchedSubpath) {
326
+ return outputPath.replace(/\*/g, matchedSubpath);
327
+ }
328
+ /**
329
+ * Expand a wildcard export pattern by finding matching source files
330
+ * Returns a map of concrete export paths to their matched subpaths
331
+ * Example: "./features/*" with files ["foo.ts", "bar.ts"] in src/features/
332
+ * -> { "./features/foo": "foo", "./features/bar": "bar" }
333
+ */ async function expandWildcardPattern(wildcardPattern, cwd) {
334
+ const expanded = new Map();
335
+ const sourceDir = path__default.default.join(cwd, SRC);
336
+ if (!fileExists(sourceDir)) {
337
+ return expanded;
338
+ }
339
+ // Convert wildcard pattern to glob pattern
340
+ // "./features/*" -> "features/*"
341
+ const cleanPattern = wildcardPattern.replace(/^\.\//, '');
342
+ // Extract the base path before the wildcard
343
+ // "features/*" -> "features"
344
+ const basePathParts = cleanPattern.split('*');
345
+ const basePath = basePathParts[0].replace(/\/$/, '');
346
+ // Build glob pattern to match files
347
+ // "features/*" -> "features/*.{js,ts,tsx,...}"
348
+ const extPattern = `{${[
349
+ ...availableExtensions
350
+ ].join(',')}}`;
351
+ const globPatterns = [
352
+ `${cleanPattern}.${extPattern}`,
353
+ `${cleanPattern}/index.${extPattern}`
354
+ ];
355
+ let matches = [];
356
+ try {
357
+ matches = await tinyglobby.glob(globPatterns, {
358
+ cwd: sourceDir,
359
+ ignore: [
360
+ PRIVATE_GLOB_PATTERN,
361
+ TESTS_GLOB_PATTERN
362
+ ],
363
+ expandDirectories: false
364
+ });
365
+ } catch (error) {
366
+ logger.warn(`Failed to expand wildcard pattern ${wildcardPattern}: ${error}`);
367
+ return expanded;
368
+ }
369
+ for (const match of matches){
370
+ // Extract the matched subpath
371
+ // "features/foo.ts" -> "foo"
372
+ // "features/bar/index.ts" -> "bar"
373
+ const relativePath = normalizePath(match);
374
+ const ext = path__default.default.extname(relativePath);
375
+ const withoutExt = relativePath.slice(0, -ext.length);
376
+ // Remove the base path to get just the matched part
377
+ // "features/foo" -> "foo" (when basePath is "features")
378
+ let matchedPart = withoutExt;
379
+ if (basePath && matchedPart.startsWith(basePath + '/')) {
380
+ matchedPart = matchedPart.slice(basePath.length + 1);
381
+ } else if (basePath && matchedPart === basePath) {
382
+ continue;
383
+ }
384
+ // Handle index files
385
+ let matchedSubpath;
386
+ if (matchedPart.endsWith('/index')) {
387
+ matchedSubpath = matchedPart.slice(0, -6); // Remove "/index"
388
+ // If there's still a path, take the last segment
389
+ const lastSlash = matchedSubpath.lastIndexOf('/');
390
+ matchedSubpath = lastSlash >= 0 ? matchedSubpath.slice(lastSlash + 1) : matchedSubpath;
391
+ } else {
392
+ // Take the first segment (what matches the *)
393
+ const firstSlash = matchedPart.indexOf('/');
394
+ matchedSubpath = firstSlash >= 0 ? matchedPart.slice(0, firstSlash) : matchedPart;
395
+ }
396
+ // Build the concrete export path
397
+ // "./features/*" + "foo" -> "./features/foo"
398
+ const concreteExportPath = basePath ? `./${basePath}/${matchedSubpath}` : `./${matchedSubpath}`;
399
+ expanded.set(concreteExportPath, matchedSubpath);
400
+ }
401
+ return expanded;
402
+ }
403
+
404
+ /**
405
+ * Process export value for wildcard patterns, substituting wildcards in output paths
406
+ */ async function processWildcardExportValue(exportValue, originalExportKey, currentPath, exportTypes, exportToDist, matchedSubpath) {
407
+ // End of searching, export value is file path.
408
+ // <export key>: <export value> (string)
409
+ if (typeof exportValue === 'string') {
410
+ const composedTypes = new Set(exportTypes);
411
+ const exportType = originalExportKey.startsWith('.') ? 'default' : originalExportKey;
412
+ composedTypes.add(exportType);
413
+ const exportInfo = exportToDist.get(mapExportFullPath(currentPath));
414
+ const exportCondition = Array.from(composedTypes).join('.');
415
+ // Substitute wildcard in output path
416
+ const substitutedPath = substituteWildcardInPath(exportValue, matchedSubpath);
417
+ if (!exportInfo) {
418
+ const outputConditionPair = [
419
+ substitutedPath,
420
+ exportCondition
421
+ ];
422
+ addToExportDistMap(exportToDist, currentPath, [
423
+ outputConditionPair
424
+ ]);
425
+ } else {
426
+ exportInfo.push([
427
+ substitutedPath,
428
+ exportCondition
429
+ ]);
430
+ }
431
+ return;
432
+ }
433
+ const exportKeys = Object.keys(exportValue);
434
+ for (const exportKey of exportKeys){
435
+ // Clone the set to avoid modifying the parent set
436
+ const childExports = new Set(exportTypes);
437
+ // Normalize child export value to a map
438
+ const childExportValue = exportValue[exportKey];
439
+ // Substitute wildcard in nested string values
440
+ let processedChildValue = childExportValue;
441
+ if (typeof childExportValue === 'string') {
442
+ processedChildValue = substituteWildcardInPath(childExportValue, matchedSubpath);
443
+ } else if (typeof childExportValue === 'object' && childExportValue !== null) {
444
+ // Recursively process nested objects
445
+ const processed = {};
446
+ for (const [key, value] of Object.entries(childExportValue)){
447
+ if (typeof value === 'string') {
448
+ processed[key] = substituteWildcardInPath(value, matchedSubpath);
449
+ } else if (value !== null && value !== undefined) {
450
+ processed[key] = value;
451
+ }
452
+ }
453
+ processedChildValue = processed;
454
+ }
455
+ // Visit export path: ./subpath, ./subpath2, ...
456
+ if (exportKey.startsWith('.')) {
457
+ const childPath = joinRelativePath(currentPath, exportKey);
458
+ await processWildcardExportValue(processedChildValue, exportKey, childPath, childExports, exportToDist, matchedSubpath);
459
+ } else {
460
+ // Visit export type: import, require, ...
461
+ childExports.add(exportKey);
462
+ await processWildcardExportValue(processedChildValue, exportKey, currentPath, childExports, exportToDist, matchedSubpath);
463
+ }
464
+ }
465
+ }
315
466
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
316
467
  // End of searching, export value is file path.
317
468
  // <export key>: <export value> (string)
@@ -373,7 +524,7 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
373
524
  * './index': { development: ..., default: ... }
374
525
  * './index.react-server': { development: ..., default: ... }
375
526
  * }
376
- */ function parseExports(pkg) {
527
+ */ async function parseExports(pkg, cwd) {
377
528
  var _pkg_exports;
378
529
  const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {};
379
530
  var _pkg_bin;
@@ -397,6 +548,17 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
397
548
  const exportValue = exportsField[exportKey];
398
549
  const exportTypes = new Set();
399
550
  const isExportPath = exportKey.startsWith('.');
551
+ // Handle wildcard patterns
552
+ if (isExportPath && hasWildcardPattern(exportKey) && cwd) {
553
+ // Expand wildcard pattern to concrete exports
554
+ const expanded = await expandWildcardPattern(exportKey, cwd);
555
+ for (const [concreteExportPath, matchedSubpath] of expanded){
556
+ const childPath = joinRelativePath(currentPath, concreteExportPath);
557
+ // Process the export value and substitute wildcards in output paths
558
+ await processWildcardExportValue(exportValue, exportKey, childPath, exportTypes, exportToDist, matchedSubpath);
559
+ }
560
+ continue;
561
+ }
400
562
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
401
563
  if (!isExportPath) {
402
564
  exportTypes.add(exportKey);
@@ -515,10 +677,11 @@ function validateFilesField(packageJson) {
515
677
  });
516
678
  return state;
517
679
  }
518
- function lint$1(pkg) {
680
+ async function lint$1(cwd) {
681
+ const pkg = await getPackageMeta(cwd);
519
682
  const { name, main, exports } = pkg;
520
683
  const isESM = isESModulePackage(pkg.type);
521
- const parsedExports = parseExports(pkg);
684
+ const parsedExports = await parseExports(pkg, cwd);
522
685
  if (!name) {
523
686
  logger.warn('Missing package name');
524
687
  }
@@ -693,7 +856,7 @@ function lint$1(pkg) {
693
856
  }
694
857
  }
695
858
 
696
- var version = "6.8.2";
859
+ var version = "6.9.0";
697
860
 
698
861
  async function writeDefaultTsconfig(tsConfigPath) {
699
862
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
@@ -1202,10 +1365,10 @@ function help() {
1202
1365
  }
1203
1366
  async function lint(cwd) {
1204
1367
  // Not package.json detected, skip package linting
1205
- if (!await hasPackageJson(cwd)) {
1368
+ if (!hasPackageJson(cwd)) {
1206
1369
  return;
1207
1370
  }
1208
- await lint$1(await getPackageMeta(cwd));
1371
+ await lint$1(cwd);
1209
1372
  }
1210
1373
  async function parseCliArgs(argv) {
1211
1374
  const args = await yargs__default.default(helpers.hideBin(argv)).option('watch', {
package/dist/index.js CHANGED
@@ -385,7 +385,9 @@ const getMainFieldExportType = (pkg)=>{
385
385
  };
386
386
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
387
387
  function joinRelativePath(...segments) {
388
- let result = path__default.default.join(...segments);
388
+ // Normalize to forward slashes for cross-platform compatibility
389
+ // Export paths in package.json always use POSIX-style paths
390
+ let result = path__default.default.posix.join(...segments);
389
391
  // If the first segment starts with '.', ensure the result does too.
390
392
  if (segments[0] === '.' && !result.startsWith('.')) {
391
393
  result = './' + result;
@@ -437,6 +439,155 @@ function normalizePath(filePath) {
437
439
  return filePath.replace(/\\/g, '/');
438
440
  }
439
441
 
442
+ /**
443
+ * Check if an export key contains a wildcard pattern
444
+ */ function hasWildcardPattern(exportKey) {
445
+ return exportKey.includes('*');
446
+ }
447
+ /**
448
+ * Replace wildcard in output path with matched subpath
449
+ * Example: "./dist/features/*.js" with "foo" -> "./dist/features/foo.js"
450
+ */ function substituteWildcardInPath(outputPath, matchedSubpath) {
451
+ return outputPath.replace(/\*/g, matchedSubpath);
452
+ }
453
+ /**
454
+ * Expand a wildcard export pattern by finding matching source files
455
+ * Returns a map of concrete export paths to their matched subpaths
456
+ * Example: "./features/*" with files ["foo.ts", "bar.ts"] in src/features/
457
+ * -> { "./features/foo": "foo", "./features/bar": "bar" }
458
+ */ async function expandWildcardPattern(wildcardPattern, cwd) {
459
+ const expanded = new Map();
460
+ const sourceDir = path__default.default.join(cwd, SRC);
461
+ if (!fileExists(sourceDir)) {
462
+ return expanded;
463
+ }
464
+ // Convert wildcard pattern to glob pattern
465
+ // "./features/*" -> "features/*"
466
+ const cleanPattern = wildcardPattern.replace(/^\.\//, '');
467
+ // Extract the base path before the wildcard
468
+ // "features/*" -> "features"
469
+ const basePathParts = cleanPattern.split('*');
470
+ const basePath = basePathParts[0].replace(/\/$/, '');
471
+ // Build glob pattern to match files
472
+ // "features/*" -> "features/*.{js,ts,tsx,...}"
473
+ const extPattern = `{${[
474
+ ...availableExtensions
475
+ ].join(',')}}`;
476
+ const globPatterns = [
477
+ `${cleanPattern}.${extPattern}`,
478
+ `${cleanPattern}/index.${extPattern}`
479
+ ];
480
+ let matches = [];
481
+ try {
482
+ matches = await tinyglobby.glob(globPatterns, {
483
+ cwd: sourceDir,
484
+ ignore: [
485
+ PRIVATE_GLOB_PATTERN,
486
+ TESTS_GLOB_PATTERN
487
+ ],
488
+ expandDirectories: false
489
+ });
490
+ } catch (error) {
491
+ logger.warn(`Failed to expand wildcard pattern ${wildcardPattern}: ${error}`);
492
+ return expanded;
493
+ }
494
+ for (const match of matches){
495
+ // Extract the matched subpath
496
+ // "features/foo.ts" -> "foo"
497
+ // "features/bar/index.ts" -> "bar"
498
+ const relativePath = normalizePath(match);
499
+ const ext = path__default.default.extname(relativePath);
500
+ const withoutExt = relativePath.slice(0, -ext.length);
501
+ // Remove the base path to get just the matched part
502
+ // "features/foo" -> "foo" (when basePath is "features")
503
+ let matchedPart = withoutExt;
504
+ if (basePath && matchedPart.startsWith(basePath + '/')) {
505
+ matchedPart = matchedPart.slice(basePath.length + 1);
506
+ } else if (basePath && matchedPart === basePath) {
507
+ continue;
508
+ }
509
+ // Handle index files
510
+ let matchedSubpath;
511
+ if (matchedPart.endsWith('/index')) {
512
+ matchedSubpath = matchedPart.slice(0, -6); // Remove "/index"
513
+ // If there's still a path, take the last segment
514
+ const lastSlash = matchedSubpath.lastIndexOf('/');
515
+ matchedSubpath = lastSlash >= 0 ? matchedSubpath.slice(lastSlash + 1) : matchedSubpath;
516
+ } else {
517
+ // Take the first segment (what matches the *)
518
+ const firstSlash = matchedPart.indexOf('/');
519
+ matchedSubpath = firstSlash >= 0 ? matchedPart.slice(0, firstSlash) : matchedPart;
520
+ }
521
+ // Build the concrete export path
522
+ // "./features/*" + "foo" -> "./features/foo"
523
+ const concreteExportPath = basePath ? `./${basePath}/${matchedSubpath}` : `./${matchedSubpath}`;
524
+ expanded.set(concreteExportPath, matchedSubpath);
525
+ }
526
+ return expanded;
527
+ }
528
+
529
+ /**
530
+ * Process export value for wildcard patterns, substituting wildcards in output paths
531
+ */ async function processWildcardExportValue(exportValue, originalExportKey, currentPath, exportTypes, exportToDist, matchedSubpath) {
532
+ // End of searching, export value is file path.
533
+ // <export key>: <export value> (string)
534
+ if (typeof exportValue === 'string') {
535
+ const composedTypes = new Set(exportTypes);
536
+ const exportType = originalExportKey.startsWith('.') ? 'default' : originalExportKey;
537
+ composedTypes.add(exportType);
538
+ const exportInfo = exportToDist.get(mapExportFullPath(currentPath));
539
+ const exportCondition = Array.from(composedTypes).join('.');
540
+ // Substitute wildcard in output path
541
+ const substitutedPath = substituteWildcardInPath(exportValue, matchedSubpath);
542
+ if (!exportInfo) {
543
+ const outputConditionPair = [
544
+ substitutedPath,
545
+ exportCondition
546
+ ];
547
+ addToExportDistMap(exportToDist, currentPath, [
548
+ outputConditionPair
549
+ ]);
550
+ } else {
551
+ exportInfo.push([
552
+ substitutedPath,
553
+ exportCondition
554
+ ]);
555
+ }
556
+ return;
557
+ }
558
+ const exportKeys = Object.keys(exportValue);
559
+ for (const exportKey of exportKeys){
560
+ // Clone the set to avoid modifying the parent set
561
+ const childExports = new Set(exportTypes);
562
+ // Normalize child export value to a map
563
+ const childExportValue = exportValue[exportKey];
564
+ // Substitute wildcard in nested string values
565
+ let processedChildValue = childExportValue;
566
+ if (typeof childExportValue === 'string') {
567
+ processedChildValue = substituteWildcardInPath(childExportValue, matchedSubpath);
568
+ } else if (typeof childExportValue === 'object' && childExportValue !== null) {
569
+ // Recursively process nested objects
570
+ const processed = {};
571
+ for (const [key, value] of Object.entries(childExportValue)){
572
+ if (typeof value === 'string') {
573
+ processed[key] = substituteWildcardInPath(value, matchedSubpath);
574
+ } else if (value !== null && value !== undefined) {
575
+ processed[key] = value;
576
+ }
577
+ }
578
+ processedChildValue = processed;
579
+ }
580
+ // Visit export path: ./subpath, ./subpath2, ...
581
+ if (exportKey.startsWith('.')) {
582
+ const childPath = joinRelativePath(currentPath, exportKey);
583
+ await processWildcardExportValue(processedChildValue, exportKey, childPath, childExports, exportToDist, matchedSubpath);
584
+ } else {
585
+ // Visit export type: import, require, ...
586
+ childExports.add(exportKey);
587
+ await processWildcardExportValue(processedChildValue, exportKey, currentPath, childExports, exportToDist, matchedSubpath);
588
+ }
589
+ }
590
+ }
440
591
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
441
592
  // End of searching, export value is file path.
442
593
  // <export key>: <export value> (string)
@@ -498,7 +649,7 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
498
649
  * './index': { development: ..., default: ... }
499
650
  * './index.react-server': { development: ..., default: ... }
500
651
  * }
501
- */ function parseExports(pkg) {
652
+ */ async function parseExports(pkg, cwd) {
502
653
  var _pkg_exports;
503
654
  const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {};
504
655
  var _pkg_bin;
@@ -522,6 +673,17 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
522
673
  const exportValue = exportsField[exportKey];
523
674
  const exportTypes = new Set();
524
675
  const isExportPath = exportKey.startsWith('.');
676
+ // Handle wildcard patterns
677
+ if (isExportPath && hasWildcardPattern(exportKey) && cwd) {
678
+ // Expand wildcard pattern to concrete exports
679
+ const expanded = await expandWildcardPattern(exportKey, cwd);
680
+ for (const [concreteExportPath, matchedSubpath] of expanded){
681
+ const childPath = joinRelativePath(currentPath, concreteExportPath);
682
+ // Process the export value and substitute wildcards in output paths
683
+ await processWildcardExportValue(exportValue, exportKey, childPath, exportTypes, exportToDist, matchedSubpath);
684
+ }
685
+ continue;
686
+ }
525
687
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
526
688
  if (!isExportPath) {
527
689
  exportTypes.add(exportKey);
@@ -2203,7 +2365,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, onSuccess, ...options } = {}) {
2203
2365
  assignDefault(options, 'minify', false);
2204
2366
  assignDefault(options, 'target', 'es2015');
2205
2367
  const pkg = await getPackageMeta(cwd);
2206
- const parsedExportsInfo = parseExports(pkg);
2368
+ const parsedExportsInfo = await parseExports(pkg, cwd);
2207
2369
  const isMultiEntries = hasMultiEntryExport(parsedExportsInfo);
2208
2370
  const hasBin = Boolean(pkg.bin);
2209
2371
  // Original input file path, client path might change later
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "6.8.2",
3
+ "version": "6.9.0",
4
4
  "description": "zero config bundler for js/ts/jsx libraries",
5
5
  "bin": "./dist/bin/cli.js",
6
6
  "main": "./dist/index.js",