bunchee 6.8.1 → 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.
@@ -317,19 +341,6 @@ bunchee --no-external
317
341
 
318
342
  This will include all dependencies within your output bundle.
319
343
 
320
- #### TypeScript-Go Compiler
321
-
322
- TypeScript-Go (`@typescript/native-preview`) is a high-performance, Go-based implementation of the TypeScript compiler that can significantly speed up type declaration file generation.
323
-
324
- To use TypeScript-Go for type generation, use the `--tsgo` flag:
325
-
326
- **Note**: This requires `@typescript/native-preview` to be installed as a dev dependency. If it's not installed, bunchee will exit with an error.
327
-
328
- ```sh
329
- pnpm add -D bunchee @typescript/native-preview
330
- bunchee --tsgo
331
- ```
332
-
333
344
  #### Build Successful Command
334
345
 
335
346
  A command to be executed after a build is successful can be specified using the `--success` option, which is useful for development watching mode:
@@ -378,14 +389,6 @@ Then use use the [exports field in package.json](https://nodejs.org/api/packages
378
389
 
379
390
  If you're building a TypeScript library, separate the types from the main entry file and specify the types path in package.json. Types exports need to stay on the top of each export with `types` condition, and you can use `default` condition for the JS bundle file.
380
391
 
381
- **bunchee** supports using the TypeScript-Go compiler (`@typescript/native-preview`) for faster type generation. To enable it, use the `--tsgo` flag:
382
-
383
- ```sh
384
- bunchee --tsgo
385
- ```
386
-
387
- Note: This requires `@typescript/native-preview` to be installed as a dev dependency. If it's not installed, bunchee will fall back to the regular TypeScript compiler with a warning.
388
-
389
392
  ```json5
390
393
  {
391
394
  "files": ["dist"],
@@ -566,7 +569,6 @@ await bundle(path.resolve('./src/index.ts'), {
566
569
  cwd: process.cwd(), // string
567
570
  clean: true, // boolean
568
571
  tsconfig: 'tsconfig.json', // string
569
- tsgo: false, // Boolean - use TypeScript-Go compiler for type generation
570
572
  })
571
573
  ```
572
574
 
package/dist/bin/cli.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  var path = require('path');
3
- var module$1 = require('module');
4
3
  var yargs = require('yargs');
5
4
  var helpers = require('yargs/helpers');
6
5
  var perf_hooks = require('perf_hooks');
7
6
  var fs = require('fs');
8
7
  var fsp = require('fs/promises');
9
8
  var require$$0 = require('tty');
9
+ var tinyglobby = require('tinyglobby');
10
10
  var picomatch = require('picomatch');
11
11
  var index_js = require('../index.js');
12
- var tinyglobby = require('tinyglobby');
12
+ require('module');
13
13
  var prettyBytes = require('pretty-bytes');
14
14
  var nanospinner = require('nanospinner');
15
15
 
@@ -146,6 +146,52 @@ const defaultColorFn = (text)=>text;
146
146
  function color(prefixColor) {
147
147
  return pc.isColorSupported ? pc[prefixColor] : defaultColorFn;
148
148
  }
149
+ let activeSpinner = null;
150
+ // Store original console methods
151
+ const originalConsole = {
152
+ log: console.log.bind(console),
153
+ warn: console.warn.bind(console),
154
+ error: console.error.bind(console),
155
+ info: console.info.bind(console)
156
+ };
157
+ function isSpinnerActive() {
158
+ if (!activeSpinner) return false;
159
+ const isSpinning = activeSpinner.isSpinning;
160
+ return typeof isSpinning === 'function' ? isSpinning() : isSpinning;
161
+ }
162
+ /**
163
+ * Wrap a console method to pause spinner before logging
164
+ */ function wrapConsoleMethod(original) {
165
+ return (...args)=>{
166
+ if (isSpinnerActive() && activeSpinner) {
167
+ activeSpinner.clear();
168
+ original(...args);
169
+ activeSpinner.start();
170
+ } else {
171
+ original(...args);
172
+ }
173
+ };
174
+ }
175
+ /**
176
+ * Register a spinner so that ALL console output automatically pauses it.
177
+ * This intercepts console.log/warn/error/info globally.
178
+ * Call with `null` to unregister and restore original console methods.
179
+ */ function setActiveSpinner(spinner) {
180
+ activeSpinner = spinner;
181
+ if (spinner) {
182
+ // Patch global console methods to pause spinner
183
+ console.log = wrapConsoleMethod(originalConsole.log);
184
+ console.warn = wrapConsoleMethod(originalConsole.warn);
185
+ console.error = wrapConsoleMethod(originalConsole.error);
186
+ console.info = wrapConsoleMethod(originalConsole.info);
187
+ } else {
188
+ // Restore original console methods
189
+ console.log = originalConsole.log;
190
+ console.warn = originalConsole.warn;
191
+ console.error = originalConsole.error;
192
+ console.info = originalConsole.info;
193
+ }
194
+ }
149
195
  const logger = {
150
196
  log (...arg) {
151
197
  console.log(...arg);
@@ -228,7 +274,9 @@ const getMainFieldExportType = (pkg)=>{
228
274
  };
229
275
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
230
276
  function joinRelativePath(...segments) {
231
- 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);
232
280
  // If the first segment starts with '.', ensure the result does too.
233
281
  if (segments[0] === '.' && !result.startsWith('.')) {
234
282
  result = './' + result;
@@ -266,6 +314,155 @@ function normalizePath(filePath) {
266
314
  return filePath.replace(/\\/g, '/');
267
315
  }
268
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
+ }
269
466
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
270
467
  // End of searching, export value is file path.
271
468
  // <export key>: <export value> (string)
@@ -327,7 +524,7 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
327
524
  * './index': { development: ..., default: ... }
328
525
  * './index.react-server': { development: ..., default: ... }
329
526
  * }
330
- */ function parseExports(pkg) {
527
+ */ async function parseExports(pkg, cwd) {
331
528
  var _pkg_exports;
332
529
  const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {};
333
530
  var _pkg_bin;
@@ -351,6 +548,17 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
351
548
  const exportValue = exportsField[exportKey];
352
549
  const exportTypes = new Set();
353
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
+ }
354
562
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
355
563
  if (!isExportPath) {
356
564
  exportTypes.add(exportKey);
@@ -469,10 +677,11 @@ function validateFilesField(packageJson) {
469
677
  });
470
678
  return state;
471
679
  }
472
- function lint$1(pkg) {
680
+ async function lint$1(cwd) {
681
+ const pkg = await getPackageMeta(cwd);
473
682
  const { name, main, exports } = pkg;
474
683
  const isESM = isESModulePackage(pkg.type);
475
- const parsedExports = parseExports(pkg);
684
+ const parsedExports = await parseExports(pkg, cwd);
476
685
  if (!name) {
477
686
  logger.warn('Missing package name');
478
687
  }
@@ -647,12 +856,11 @@ function lint$1(pkg) {
647
856
  }
648
857
  }
649
858
 
650
- var version = "6.8.1";
859
+ var version = "6.9.0";
651
860
 
652
- async function writeDefaultTsconfig(tsConfigPath, useTsGo) {
861
+ async function writeDefaultTsconfig(tsConfigPath) {
653
862
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
654
- const compilerName = 'TypeScript';
655
- logger.log(`Detected using ${compilerName} but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
863
+ logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
656
864
  }
657
865
 
658
866
  // ./index -> default
@@ -1151,17 +1359,16 @@ Options:
1151
1359
  --tsconfig path to tsconfig file, default: tsconfig.json
1152
1360
  --dts-bundle bundle type declaration files, default: false
1153
1361
  --success <cmd> run command after build success
1154
- --tsgo use TypeScript-Go compiler for type generation
1155
1362
  `;
1156
1363
  function help() {
1157
1364
  logger.log(helpMessage);
1158
1365
  }
1159
1366
  async function lint(cwd) {
1160
1367
  // Not package.json detected, skip package linting
1161
- if (!await hasPackageJson(cwd)) {
1368
+ if (!hasPackageJson(cwd)) {
1162
1369
  return;
1163
1370
  }
1164
- await lint$1(await getPackageMeta(cwd));
1371
+ await lint$1(cwd);
1165
1372
  }
1166
1373
  async function parseCliArgs(argv) {
1167
1374
  const args = await yargs__default.default(helpers.hideBin(argv)).option('watch', {
@@ -1229,9 +1436,6 @@ async function parseCliArgs(argv) {
1229
1436
  }).option('success', {
1230
1437
  type: 'string',
1231
1438
  description: 'run command after build success'
1232
- }).option('tsgo', {
1233
- type: 'boolean',
1234
- description: 'use TypeScript-Go compiler for type generation'
1235
1439
  }).command('prepare', 'auto configure package.json exports for building', (yargs)=>{
1236
1440
  return yargs.option('esm', {
1237
1441
  type: 'boolean',
@@ -1274,8 +1478,7 @@ async function parseCliArgs(argv) {
1274
1478
  clean: args['clean'] !== false,
1275
1479
  env: args['env'],
1276
1480
  tsconfig: args['tsconfig'],
1277
- onSuccess: args['success'],
1278
- tsgo: args['tsgo']
1481
+ onSuccess: args['success']
1279
1482
  };
1280
1483
  // When minify is enabled, sourcemap should be enabled by default, unless explicitly opted out
1281
1484
  if (parsedArgs.minify && typeof args['sourcemap'] === 'undefined') {
@@ -1304,20 +1507,9 @@ async function run(args) {
1304
1507
  env: (env == null ? void 0 : env.split(',')) || [],
1305
1508
  clean,
1306
1509
  tsconfig,
1307
- onSuccess,
1308
- tsgo: args.tsgo
1510
+ onSuccess
1309
1511
  };
1310
1512
  const cliEntry = source ? path__default.default.resolve(cwd, source) : '';
1311
- // Check if ts-go is available when requested (before any build operations)
1312
- if (args.tsgo) {
1313
- try {
1314
- require.resolve('@typescript/native-preview', {
1315
- paths: module$1.Module._nodeModulePaths(cwd)
1316
- });
1317
- } catch {
1318
- exit('--tsgo flag was specified but @typescript/native-preview is not installed. Please install it as a dev dependency: pnpm add -D @typescript/native-preview');
1319
- }
1320
- }
1321
1513
  // lint package by default
1322
1514
  await lint(cwd);
1323
1515
  const spinnerInstance = process.stdout.isTTY ? nanospinner.createSpinner('Building...\n\n', {
@@ -1329,6 +1521,8 @@ async function run(args) {
1329
1521
  success: ()=>{},
1330
1522
  isSpinning: false
1331
1523
  };
1524
+ // Register spinner with logger so logs automatically pause the spinner
1525
+ setActiveSpinner(spinnerInstance);
1332
1526
  const spinner = {
1333
1527
  start: startSpinner,
1334
1528
  stop: stopSpinner
@@ -1408,6 +1602,8 @@ async function run(args) {
1408
1602
  } else {
1409
1603
  spinner.stop(`bunchee ${version} build completed`);
1410
1604
  }
1605
+ // Unregister spinner from logger
1606
+ setActiveSpinner(null);
1411
1607
  }
1412
1608
  async function main() {
1413
1609
  let params, error;
package/dist/index.d.ts CHANGED
@@ -22,7 +22,6 @@ type BundleConfig = {
22
22
  clean?: boolean;
23
23
  tsconfig?: string;
24
24
  onSuccess?: string | (() => void | Promise<void>);
25
- tsgo?: boolean;
26
25
  _callbacks?: {
27
26
  onBuildStart?: (state: any) => void;
28
27
  onBuildEnd?: (assetJobs: any) => void;
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ var fsp = require('fs/promises');
4
4
  var fs = require('fs');
5
5
  var path = require('path');
6
6
  require('pretty-bytes');
7
- var tinyglobby = require('tinyglobby');
8
7
  var require$$0 = require('tty');
8
+ var tinyglobby = require('tinyglobby');
9
9
  var module$1 = require('module');
10
10
  var rollup = require('rollup');
11
11
  var pluginWasm = require('@rollup/plugin-wasm');
@@ -97,6 +97,13 @@ const defaultColorFn = (text)=>text;
97
97
  function color(prefixColor) {
98
98
  return pc.isColorSupported ? pc[prefixColor] : defaultColorFn;
99
99
  }
100
+ // Store original console methods
101
+ ({
102
+ log: console.log.bind(console),
103
+ warn: console.warn.bind(console),
104
+ error: console.error.bind(console),
105
+ info: console.info.bind(console)
106
+ });
100
107
  const logger = {
101
108
  log (...arg) {
102
109
  console.log(...arg);
@@ -299,7 +306,7 @@ function validateEntryFiles(entryFiles) {
299
306
 
300
307
  function exit(err) {
301
308
  logger.error(err);
302
- throw typeof err === 'string' ? new Error(err) : err;
309
+ throw new Error(err) ;
303
310
  }
304
311
  async function getPackageMeta(cwd) {
305
312
  const pkgFilePath = path__default.default.resolve(cwd, 'package.json');
@@ -378,7 +385,9 @@ const getMainFieldExportType = (pkg)=>{
378
385
  };
379
386
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
380
387
  function joinRelativePath(...segments) {
381
- 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);
382
391
  // If the first segment starts with '.', ensure the result does too.
383
392
  if (segments[0] === '.' && !result.startsWith('.')) {
384
393
  result = './' + result;
@@ -430,6 +439,155 @@ function normalizePath(filePath) {
430
439
  return filePath.replace(/\\/g, '/');
431
440
  }
432
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
+ }
433
591
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
434
592
  // End of searching, export value is file path.
435
593
  // <export key>: <export value> (string)
@@ -491,7 +649,7 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
491
649
  * './index': { development: ..., default: ... }
492
650
  * './index.react-server': { development: ..., default: ... }
493
651
  * }
494
- */ function parseExports(pkg) {
652
+ */ async function parseExports(pkg, cwd) {
495
653
  var _pkg_exports;
496
654
  const exportsField = (_pkg_exports = pkg.exports) != null ? _pkg_exports : {};
497
655
  var _pkg_bin;
@@ -515,6 +673,17 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs) {
515
673
  const exportValue = exportsField[exportKey];
516
674
  const exportTypes = new Set();
517
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
+ }
518
687
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
519
688
  if (!isExportPath) {
520
689
  exportTypes.add(exportKey);
@@ -1034,40 +1203,7 @@ const memoizeByKey = (fn)=>{
1034
1203
  const memoize = (fn)=>createMemoize(fn);
1035
1204
 
1036
1205
  let hasLoggedTsWarning = false;
1037
- let hasLoggedTsGoWarning = false;
1038
- function resolveTsGo(cwd) {
1039
- let tsgo;
1040
- const m = new module$1.Module('', undefined);
1041
- m.paths = module$1.Module._nodeModulePaths(cwd);
1042
- try {
1043
- // Bun does not yet support the `Module` class properly.
1044
- if (typeof (m == null ? void 0 : m.require) === 'undefined') {
1045
- const tsgoPath = require.resolve('@typescript/native-preview', {
1046
- paths: module$1.Module._nodeModulePaths(cwd)
1047
- });
1048
- tsgo = require(tsgoPath);
1049
- } else {
1050
- tsgo = m.require('@typescript/native-preview');
1051
- }
1052
- // ts-go exports the TypeScript API as default or named export
1053
- return tsgo.default || tsgo;
1054
- } catch (e) {
1055
- if (!hasLoggedTsGoWarning) {
1056
- hasLoggedTsGoWarning = true;
1057
- logger.warn('Could not load TypeScript-Go compiler. Make sure `@typescript/native-preview` is installed as a dev dependency.');
1058
- }
1059
- return null;
1060
- }
1061
- }
1062
- function resolveTypescript(cwd, useTsGo) {
1063
- if (useTsGo) {
1064
- const tsgo = resolveTsGo(cwd);
1065
- if (tsgo) {
1066
- return tsgo;
1067
- }
1068
- // Error if ts-go is requested but not available
1069
- exit('TypeScript-Go compiler not found. Please install @typescript/native-preview as a dev dependency: pnpm add -D @typescript/native-preview');
1070
- }
1206
+ function resolveTypescript(cwd) {
1071
1207
  let ts;
1072
1208
  const m = new module$1.Module('', undefined);
1073
1209
  m.paths = module$1.Module._nodeModulePaths(cwd);
@@ -1095,11 +1231,11 @@ const resolveTsConfigPath = memoize((cwd, tsconfigFileName = 'tsconfig.json')=>{
1095
1231
  tsConfigPath = path.resolve(cwd, tsconfigFileName);
1096
1232
  return fileExists(tsConfigPath) ? tsConfigPath : undefined;
1097
1233
  });
1098
- function resolveTsConfigHandler(cwd, tsConfigPath, useTsGo) {
1234
+ function resolveTsConfigHandler(cwd, tsConfigPath) {
1099
1235
  let tsCompilerOptions = {};
1100
1236
  if (tsConfigPath) {
1101
1237
  // Use the original ts handler to avoid memory leak
1102
- const ts = resolveTypescript(cwd, useTsGo);
1238
+ const ts = resolveTypescript(cwd);
1103
1239
  const basePath = tsConfigPath ? path.dirname(tsConfigPath) : cwd;
1104
1240
  const tsconfigJSON = ts.readConfigFile(tsConfigPath, ts.sys.readFile).config;
1105
1241
  tsCompilerOptions = ts.parseJsonConfigFileContent(tsconfigJSON, ts.sys, basePath).options;
@@ -1111,28 +1247,24 @@ function resolveTsConfigHandler(cwd, tsConfigPath, useTsGo) {
1111
1247
  tsConfigPath
1112
1248
  };
1113
1249
  }
1114
- // Note: We can't memoize resolveTsConfigHandler directly with useTsGo parameter
1115
- // because memoize doesn't handle optional parameters well. Instead, we'll create
1116
- // a wrapper that handles the memoization per useTsGo value.
1117
1250
  const resolveTsConfigCache = new Map();
1118
- function resolveTsConfig(cwd, tsConfigPath, useTsGo) {
1119
- const cacheKey = `${cwd}:${tsConfigPath || ''}:${useTsGo ? 'tsgo' : 'ts'}`;
1251
+ function resolveTsConfig(cwd, tsConfigPath) {
1252
+ const cacheKey = `${cwd}:${tsConfigPath || ''}`;
1120
1253
  if (resolveTsConfigCache.has(cacheKey)) {
1121
1254
  return resolveTsConfigCache.get(cacheKey);
1122
1255
  }
1123
- const result = resolveTsConfigHandler(cwd, tsConfigPath, useTsGo);
1256
+ const result = resolveTsConfigHandler(cwd, tsConfigPath);
1124
1257
  resolveTsConfigCache.set(cacheKey, result);
1125
1258
  return result;
1126
1259
  }
1127
- async function convertCompilerOptions(cwd, json, useTsGo) {
1260
+ async function convertCompilerOptions(cwd, json) {
1128
1261
  // Use the original ts handler to avoid memory leak
1129
- const ts = resolveTypescript(cwd, useTsGo);
1262
+ const ts = resolveTypescript(cwd);
1130
1263
  return ts.convertCompilerOptionsFromJson(json, './');
1131
1264
  }
1132
- async function writeDefaultTsconfig(tsConfigPath, useTsGo) {
1265
+ async function writeDefaultTsconfig(tsConfigPath) {
1133
1266
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
1134
- const compilerName = useTsGo ? 'TypeScript-Go' : 'TypeScript';
1135
- logger.log(`Detected using ${compilerName} but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
1267
+ logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
1136
1268
  }
1137
1269
 
1138
1270
  /**
@@ -1640,7 +1772,7 @@ const swcMinifyOptions = {
1640
1772
  toplevel: true
1641
1773
  }
1642
1774
  };
1643
- async function createDtsPlugin(tsCompilerOptions, tsConfigPath, respectExternal, cwd, useTsGo) {
1775
+ async function createDtsPlugin(tsCompilerOptions, tsConfigPath, respectExternal, cwd) {
1644
1776
  const enableIncrementalWithoutBuildInfo = (tsCompilerOptions == null ? void 0 : tsCompilerOptions.incremental) && !(tsCompilerOptions == null ? void 0 : tsCompilerOptions.tsBuildInfoFile);
1645
1777
  const incrementalOptions = enableIncrementalWithoutBuildInfo ? {
1646
1778
  incremental: false
@@ -1667,31 +1799,7 @@ async function createDtsPlugin(tsCompilerOptions, tsConfigPath, respectExternal,
1667
1799
  ...incrementalOptions,
1668
1800
  // error TS6379: Composite projects may not disable incremental compilation.
1669
1801
  ...compositeOptions
1670
- }, useTsGo);
1671
- // If useTsGo is enabled, we need to make ts-go available to rollup-plugin-dts
1672
- // rollup-plugin-dts uses require('typescript') internally, so we need to
1673
- // temporarily override the module cache to use ts-go
1674
- if (useTsGo) {
1675
- const tsgo = resolveTsGo(cwd);
1676
- if (tsgo) {
1677
- try {
1678
- // First, try to resolve typescript to get its path
1679
- const tsPath = require.resolve('typescript', {
1680
- paths: module$1.Module._nodeModulePaths(cwd)
1681
- });
1682
- // Make ts-go available as 'typescript' for rollup-plugin-dts
1683
- // This overrides the module cache so rollup-plugin-dts will use ts-go
1684
- require.cache[tsPath] = {
1685
- id: tsPath,
1686
- exports: tsgo,
1687
- loaded: true
1688
- };
1689
- } catch (e) {
1690
- // If typescript cannot be resolved, we can't override it
1691
- // rollup-plugin-dts will try to require it and may fail
1692
- }
1693
- }
1694
- }
1802
+ });
1695
1803
  const dtsPlugin = require('rollup-plugin-dts').default({
1696
1804
  tsconfig: tsConfigPath,
1697
1805
  compilerOptions: overrideResolvedTsOptions,
@@ -1785,8 +1893,8 @@ async function buildInputConfig(entry, bundleConfig, exportCondition, buildConte
1785
1893
  // Each process should be unique
1786
1894
  // Each package build should be unique
1787
1895
  // Composing above factors into a unique cache key to retrieve the memoized dts plugin with tsconfigs
1788
- const uniqueProcessId = 'dts-plugin:' + process.pid + tsConfigPath + (buildContext.useTsGo ? ':tsgo' : '');
1789
- const dtsPlugin = await memoizeDtsPluginByKey(uniqueProcessId)(tsCompilerOptions, tsConfigPath, bundleConfig.dts && bundleConfig.dts.respectExternal, cwd, buildContext.useTsGo);
1896
+ const uniqueProcessId = 'dts-plugin:' + process.pid + tsConfigPath;
1897
+ const dtsPlugin = await memoizeDtsPluginByKey(uniqueProcessId)(tsCompilerOptions, tsConfigPath, bundleConfig.dts && bundleConfig.dts.respectExternal, cwd);
1790
1898
  typesPlugins.push(dtsPlugin);
1791
1899
  }
1792
1900
  const plugins = (dts ? typesPlugins : [
@@ -2257,23 +2365,14 @@ async function bundle(cliEntryPath, { cwd: _cwd, onSuccess, ...options } = {}) {
2257
2365
  assignDefault(options, 'minify', false);
2258
2366
  assignDefault(options, 'target', 'es2015');
2259
2367
  const pkg = await getPackageMeta(cwd);
2260
- const parsedExportsInfo = parseExports(pkg);
2368
+ const parsedExportsInfo = await parseExports(pkg, cwd);
2261
2369
  const isMultiEntries = hasMultiEntryExport(parsedExportsInfo);
2262
2370
  const hasBin = Boolean(pkg.bin);
2263
2371
  // Original input file path, client path might change later
2264
2372
  const inputFile = cliEntryPath;
2265
2373
  const isFromCli = Boolean(cliEntryPath);
2266
- const useTsGo = options.tsgo === true;
2267
- // Check if ts-go is available when requested (before resolving tsconfig)
2268
- let tsgoInstance = null;
2269
- if (useTsGo) {
2270
- tsgoInstance = resolveTsGo(cwd);
2271
- if (!tsgoInstance) {
2272
- exit('--tsgo flag was specified but @typescript/native-preview is not installed. Please install it as a dev dependency: pnpm add -D @typescript/native-preview');
2273
- }
2274
- }
2275
2374
  const tsConfigPath = resolveTsConfigPath(cwd, options.tsconfig);
2276
- let tsConfig = resolveTsConfig(cwd, tsConfigPath, useTsGo);
2375
+ let tsConfig = resolveTsConfig(cwd, tsConfigPath);
2277
2376
  let hasTsConfig = Boolean(tsConfig == null ? void 0 : tsConfig.tsConfigPath);
2278
2377
  const defaultTsOptions = {
2279
2378
  tsConfigPath: tsConfig == null ? void 0 : tsConfig.tsConfigPath,
@@ -2343,7 +2442,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, onSuccess, ...options } = {}) {
2343
2442
  // Otherwise, use the existing one.
2344
2443
  const defaultTsConfigPath = path.resolve(cwd, 'tsconfig.json');
2345
2444
  if (!fileExists(defaultTsConfigPath)) {
2346
- await writeDefaultTsconfig(defaultTsConfigPath, useTsGo);
2445
+ await writeDefaultTsconfig(defaultTsConfigPath);
2347
2446
  }
2348
2447
  defaultTsOptions.tsConfigPath = defaultTsConfigPath;
2349
2448
  hasTsConfig = true;
@@ -2355,15 +2454,12 @@ async function bundle(cliEntryPath, { cwd: _cwd, onSuccess, ...options } = {}) {
2355
2454
  const outputState = createOutputState({
2356
2455
  entries
2357
2456
  });
2358
- // Use ts-go if it was successfully resolved earlier
2359
- const useTsGoInContext = Boolean(useTsGo && hasTsConfig && tsgoInstance);
2360
2457
  const buildContext = {
2361
2458
  entries,
2362
2459
  pkg,
2363
2460
  cwd,
2364
2461
  tsOptions: defaultTsOptions,
2365
2462
  useTypeScript: hasTsConfig,
2366
- useTsGo: useTsGoInContext,
2367
2463
  browserslistConfig,
2368
2464
  pluginContext: {
2369
2465
  outputState,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "6.8.1",
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",