bunchee 5.6.1 → 6.0.0-beta.1

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
@@ -44,12 +44,12 @@ mkdir src && touch ./src/index.ts
44
44
 
45
45
  ```sh
46
46
  # Use bunchee to prepare package.json configuration
47
- npm exec bunchee --prepare
47
+ npm exec bunchee prepare
48
48
  # "If you're using other package manager such as pnpm"
49
- # pnpm bunchee --prepare
49
+ # pnpm bunchee prepare
50
50
 
51
51
  # "Or use with npx"
52
- # npx bunchee@latest --prepare
52
+ # npx bunchee@latest prepare
53
53
  ```
54
54
 
55
55
  Or you can checkout the following cases to configure your package.json.
@@ -252,63 +252,65 @@ Then when the library is integrated to an app such as Next.js, app bundler can t
252
252
  If you're using `"use client"` or `"use server"` in entry file, then it will be preserved on top and the dist file of that entry will become a client component.
253
253
  If you're using `"use client"` or `"use server"` in a file that used as a dependency for an entry, then that file containing directives be split into a separate chunk and hoist the directives to the top of the chunk.
254
254
 
255
- ### Shared Modules (Experimental)
255
+ ### Shared Modules
256
256
 
257
- There're always cases that you need to share code among bundles but they don't have to be a separate entry or exports. You want to have them bundled into a shared chunk and then use them in different bundles. You can use shared module convention `[name].[layer]-runtime.[ext]` to create shared modules bundles.
257
+ In some cases, you may need to share code across multiple bundles without promoting them to separate entries or exports. These modules should be bundled into shared chunks that can be reused by various bundles. By convention, files or directories prefixed with an underscore (`_<name>.<ext>` or `_<name>/index.<ext>`) are treated as **shared modules**. They're private and not exposed publicly as entry points or exports.
258
258
 
259
259
  <details>
260
260
  <summary>Shared Utils Example</summary>
261
261
 
262
262
  ```js
263
- // src/util.shared-runtime.js
263
+ // src/_util.js
264
264
  export function sharedUtil() {
265
265
  /* ... */
266
266
  }
267
267
  ```
268
268
 
269
- Then you can use them in different entry files:
269
+ You can then use them in different entry files:
270
270
 
271
271
  ```js
272
272
  // src/index.js
273
- import { sharedUtil } from './util.shared-runtime'
273
+ import { sharedUtil } from './_util'
274
274
  ```
275
275
 
276
276
  ```js
277
277
  // src/lite.js
278
- import { sharedUtil } from './util.shared-runtime'
278
+ import { sharedUtil } from './_util'
279
279
  ```
280
280
 
281
- `bunchee` will bundle the shared module into a separate **layer** which matches the file name convention, in the above case it's "shared", and that bundle will be referenced by the different entry bundles.
281
+ `bunchee` will bundle the shared module into a separate chunk, keeping it private and ensuring it's referenced by multiple entry bundles.
282
282
 
283
283
  </details>
284
284
 
285
- With multiple runtime bundles, such as having `default` and `react-server` together. They could have the modules that need to be shared and kept as only one instance among different runtime bundles. You can use the shared module convention to create shared modules bundles for different runtime bundles.
285
+ For scenarios involving multiple runtime bundles, such as `default` and `react-server`, modules that need to be shared and remain as a single instance across different runtime bundles can also follow this convention. The leading underscore (`_`) ensures that these modules are private to your application while facilitating reuse.
286
286
 
287
287
  <details>
288
288
  <summary>Shared Runtime Module Example</summary>
289
289
 
290
290
  ```js
291
291
  'use client'
292
- // src/app-context.shared-runtime.js
292
+ // src/_app-context.js
293
293
  export const AppContext = React.createContext(null)
294
294
  ```
295
295
 
296
- Then you can use them in different entry files:
296
+ These modules can be imported in various runtime entry files:
297
297
 
298
298
  ```js
299
299
  // src/index.js
300
- import { AppContext } from './app-context.shared-runtime'
300
+ import { AppContext } from './_app-context'
301
301
  ```
302
302
 
303
303
  ```js
304
304
  // src/index.react-server.js
305
- import { AppContext } from './app-context.shared-runtime'
305
+ import { AppContext } from './_app-context'
306
306
  ```
307
307
 
308
- `app-context.shared-runtime` will be bundled into a separate chunk that only has one instance and be shared among different runtime bundles.
308
+ The `_app-context` module will be bundled into a shared chunk that exists as a single instance across different runtime bundles.
309
309
 
310
310
  </details>
311
311
 
312
+ This convention keeps shared modules private while enabling efficient bundling and reuse across your codebase.
313
+
312
314
  ### CLI
313
315
 
314
316
  #### CLI Options
package/dist/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ var fsp = require('fs/promises');
8
8
  var require$$0 = require('tty');
9
9
  var index_js = require('../index.js');
10
10
  require('module');
11
+ var glob = require('glob');
11
12
  var prettyBytes = require('pretty-bytes');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -141,7 +142,7 @@ const logger = {
141
142
  console.log(...arg);
142
143
  },
143
144
  warn (...arg) {
144
- console.warn(color('yellow')('⚠️'), ...arg);
145
+ console.warn(color('yellow')('!'), ...arg);
145
146
  },
146
147
  error (...arg) {
147
148
  console.error(color('red')('⨯'), ...arg);
@@ -151,6 +152,11 @@ const logger = {
151
152
  }
152
153
  };
153
154
 
155
+ const { sep } = require('path');
156
+ function relativify(path) {
157
+ return path.startsWith('.') ? path : `.${sep}${path}`;
158
+ }
159
+
154
160
  function exit(err) {
155
161
  logger.error(err);
156
162
  process.exit(1);
@@ -175,8 +181,6 @@ function isTypescriptFile(filename) {
175
181
  function fileExists(filePath) {
176
182
  return fs__default.default.existsSync(filePath);
177
183
  }
178
- // 'index.server.js' -> 'index'
179
- const getFileBasename = (str)=>str.split('.')[0];
180
184
  const hasCjsExtension = (filename)=>path__default.default.extname(filename) === '.cjs';
181
185
  const getMainFieldExportType = (pkg)=>{
182
186
  const isEsmPkg = isESModulePackage(pkg.type);
@@ -203,6 +207,16 @@ function isBinExportPath(exportPath) {
203
207
  function isTypeFile(filename) {
204
208
  return filename.endsWith('.d.ts') || filename.endsWith('.d.mts') || filename.endsWith('.d.cts');
205
209
  }
210
+ // shared.ts -> ./shared
211
+ // shared.<export condition>.ts -> ./shared.<export condition>
212
+ // index.ts -> ./index
213
+ // index.development.ts -> ./index.development
214
+ // foo/index.ts -> ./foo
215
+ function sourceFilenameToExportFullPath(filename) {
216
+ const ext = path__default.default.extname(filename);
217
+ const exportPath = filename.slice(0, -ext.length);
218
+ return relativify(exportPath);
219
+ }
206
220
 
207
221
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
208
222
  // End of searching, export value is file path.
@@ -525,26 +539,13 @@ function lint$1(pkg) {
525
539
  }
526
540
  }
527
541
 
528
- var version = "5.6.1";
529
-
530
- function relativify(path) {
531
- return path.startsWith('.') ? path : `./${path}`;
532
- }
542
+ var version = "6.0.0-beta.1";
533
543
 
534
544
  async function writeDefaultTsconfig(tsConfigPath) {
535
545
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
536
546
  logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
537
547
  }
538
548
 
539
- // shared.ts -> ./shared
540
- // shared.<export condition>.ts -> ./shared
541
- // index.ts -> ./index
542
- // index.development.ts -> ./index.development
543
- function sourceFilenameToExportFullPath(filename) {
544
- const baseName = baseNameWithoutExtension(filename);
545
- let exportPath = baseName;
546
- return relativify(exportPath);
547
- }
548
549
  // ./index -> default
549
550
  // ./index.development -> development
550
551
  // ./index.react-server -> react-server
@@ -597,125 +598,98 @@ function normalizeExportPath(exportPath) {
597
598
  async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
598
599
  const isBinaryPath = isBinExportPath(originalSubpath);
599
600
  const subpath = originalSubpath.replace(BINARY_TAG, 'bin');
600
- const absoluteDirPath = path__default.default.join(sourceFolderPath, subpath);
601
- const isDirectory = fs__default.default.existsSync(absoluteDirPath) ? (await fsp__default.default.stat(absoluteDirPath)).isDirectory() : false;
602
- if (isDirectory) {
601
+ const absoluteDirPath = path.join(sourceFolderPath, subpath);
602
+ const dirName = path.dirname(subpath) // Get directory name regardless of file/directory
603
+ ;
604
+ const baseName = path.basename(subpath) // Get base name regardless of file/directory
605
+ ;
606
+ const dirPath = path.join(sourceFolderPath, dirName);
607
+ // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
608
+ const globalPatterns = [
609
+ `${baseName}.{${[
610
+ ...availableExtensions
611
+ ].join(',')}}`,
612
+ `${baseName}.{${[
613
+ ...runtimeExportConventions
614
+ ].join(',')}}.{${[
615
+ ...availableExtensions
616
+ ].join(',')}}`,
617
+ `${baseName}/index.{${[
618
+ ...availableExtensions
619
+ ].join(',')}}`,
620
+ `${baseName}/index.{${[
621
+ ...runtimeExportConventions
622
+ ].join(',')}}.{${[
623
+ ...availableExtensions
624
+ ].join(',')}}`
625
+ ];
626
+ const files = await glob.glob(globalPatterns, {
627
+ cwd: dirPath,
628
+ nodir: true,
629
+ ignore: '**/_*'
630
+ });
631
+ for (const file of files){
632
+ const ext = path.extname(file).slice(1);
633
+ if (!availableExtensions.has(ext) || isTestFile(file)) continue;
634
+ const sourceFileAbsolutePath = path.join(dirPath, file);
635
+ const exportPath = relativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath);
603
636
  if (isBinaryPath) {
604
- const binDirentList = await fsp__default.default.readdir(absoluteDirPath, {
605
- withFileTypes: true
606
- });
607
- for (const binDirent of binDirentList){
608
- if (binDirent.isFile()) {
609
- const binFileAbsolutePath = path__default.default.join(absoluteDirPath, binDirent.name);
610
- if (fs__default.default.existsSync(binFileAbsolutePath)) {
611
- bins.set(normalizeExportPath(originalSubpath), binFileAbsolutePath);
612
- }
613
- }
614
- }
637
+ bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
615
638
  } else {
616
- // Search folder/index.<ext> convention entries
617
- for (const extension of availableExtensions){
618
- const indexAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${extension}`);
619
- // Search folder/index.<special type>.<ext> convention entries
620
- for (const specialExportType of runtimeExportConventions){
621
- const indexSpecialAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${specialExportType}.${extension}`);
622
- if (fs__default.default.existsSync(indexSpecialAbsoluteFile)) {
623
- // Add special export path
624
- // { ./<export path>.<special cond>: { <special cond>: 'index.<special cond>.<ext>' } }
625
- const exportPath = relativify(subpath);
626
- const specialExportPath = exportPath + '.' + specialExportType;
627
- const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
628
- sourceFilesMap[specialExportType] = indexSpecialAbsoluteFile;
629
- exportsEntries.set(specialExportPath, sourceFilesMap);
630
- }
631
- }
632
- if (fs__default.default.existsSync(indexAbsoluteFile) && !isTestFile(indexAbsoluteFile)) {
633
- const exportPath = relativify(subpath);
634
- const sourceFilesMap = exportsEntries.get(exportPath) || {};
635
- const exportType = getExportTypeFromExportPath(exportPath);
636
- sourceFilesMap[exportType] = indexAbsoluteFile;
637
- exportsEntries.set(exportPath, sourceFilesMap);
638
- }
639
- }
640
- }
641
- } else {
642
- // subpath could be a file
643
- const dirName = path.dirname(subpath);
644
- const baseName = path.basename(subpath);
645
- // Read current file's directory
646
- const dirPath = path__default.default.join(sourceFolderPath, dirName);
647
- if (!fs__default.default.existsSync(dirPath)) {
648
- return;
649
- }
650
- const dirents = await fsp__default.default.readdir(dirPath, {
651
- withFileTypes: true
652
- });
653
- for (const dirent of dirents){
654
- // index.development.js -> index.development
655
- const direntBaseName = baseNameWithoutExtension(dirent.name);
656
- const ext = path.extname(dirent.name).slice(1);
657
- if (!dirent.isFile() || direntBaseName !== baseName || !availableExtensions.has(ext)) {
658
- continue;
659
- }
660
- if (isTestFile(dirent.name)) {
661
- continue;
662
- }
663
- const sourceFileAbsolutePath = path__default.default.join(dirPath, dirent.name);
664
- if (isBinaryPath) {
665
- bins.set(originalSubpath, sourceFileAbsolutePath);
666
- } else {
667
- let sourceFilesMap = exportsEntries.get(originalSubpath) || {};
668
- const exportType = getExportTypeFromExportPath(originalSubpath);
669
- sourceFilesMap[exportType] = sourceFileAbsolutePath;
670
- if (specialExportConventions.has(exportType)) {
671
- // e.g. ./foo/index.react-server -> ./foo/index
672
- const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
673
- const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
674
- sourceFilesMap = {
675
- ...fallbackSourceFilesMap,
676
- ...sourceFilesMap
677
- };
678
- }
679
- exportsEntries.set(originalSubpath, sourceFilesMap);
639
+ const parts = path.basename(file).split('.');
640
+ const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath);
641
+ const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches
642
+ ;
643
+ const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
644
+ sourceFilesMap[exportType] = sourceFileAbsolutePath;
645
+ if (specialExportConventions.has(exportType)) {
646
+ const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
647
+ const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
648
+ Object.assign(sourceFilesMap, fallbackSourceFilesMap);
680
649
  }
650
+ exportsEntries.set(specialExportPath, sourceFilesMap);
681
651
  }
682
652
  }
683
653
  }
684
- // For `prepare`
654
+
655
+ // For `prepare` command
685
656
  async function collectSourceEntries(sourceFolderPath) {
686
657
  const bins = new Map();
687
658
  const exportsEntries = new Map();
688
- if (!fs__default.default.existsSync(sourceFolderPath)) {
659
+ if (!fs.existsSync(sourceFolderPath)) {
689
660
  return {
690
661
  bins,
691
662
  exportsEntries
692
663
  };
693
664
  }
694
- const entryFileDirentList = await fsp__default.default.readdir(sourceFolderPath, {
695
- withFileTypes: true
665
+ // Match with global patterns
666
+ // bin/**/*.<ext>, bin/**/index.<ext>
667
+ const binPattern = `bin/**/*.{${[
668
+ ...availableExtensions
669
+ ].join(',')}}`;
670
+ const srcPattern = `**/*.{${[
671
+ ...availableExtensions
672
+ ].join(',')}}`;
673
+ const binMatches = await glob.glob(binPattern, {
674
+ cwd: sourceFolderPath,
675
+ nodir: true,
676
+ ignore: '**/_*'
696
677
  });
697
- // Collect source files for `exports` field
698
- for (const dirent of entryFileDirentList){
699
- if (getFileBasename(dirent.name) === 'bin') {
700
- continue;
701
- }
702
- const exportPath = sourceFilenameToExportFullPath(dirent.name);
703
- await collectSourceEntriesByExportPath(sourceFolderPath, exportPath, bins, exportsEntries);
678
+ const srcMatches = await glob.glob(srcPattern, {
679
+ cwd: sourceFolderPath,
680
+ nodir: true,
681
+ ignore: '**/_*'
682
+ });
683
+ for (const file of binMatches){
684
+ // convert relative path to export path
685
+ const exportPath = sourceFilenameToExportFullPath(file);
686
+ const binExportPath = exportPath.replace(/^\.[\//]bin/, BINARY_TAG);
687
+ await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
704
688
  }
705
- // Collect source files for `bin` field
706
- const binDirent = entryFileDirentList.find((dirent)=>getFileBasename(dirent.name) === 'bin');
707
- if (binDirent) {
708
- if (binDirent.isDirectory()) {
709
- const binDirentList = await fsp__default.default.readdir(path__default.default.join(sourceFolderPath, binDirent.name), {
710
- withFileTypes: true
711
- });
712
- for (const binDirent of binDirentList){
713
- const binExportPath = path.posix.join(BINARY_TAG, getFileBasename(binDirent.name));
714
- await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
715
- }
716
- } else {
717
- await collectSourceEntriesByExportPath(sourceFolderPath, BINARY_TAG, bins, exportsEntries);
718
- }
689
+ for (const file of srcMatches){
690
+ const binExportPath = file.replace(/^bin/, BINARY_TAG)// Remove index.<ext> to [^index].<ext> to build the export path
691
+ .replace(/(\/index)?\.[^/]+$/, '');
692
+ await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
719
693
  }
720
694
  return {
721
695
  bins,
@@ -955,6 +929,9 @@ function logOutputState(stats) {
955
929
  const helpMessage = `
956
930
  Usage: bunchee [options]
957
931
 
932
+ Commands:
933
+ prepare auto configure package.json exports for building
934
+
958
935
  Options:
959
936
  -v, --version output the version number
960
937
  -w, --watch watch src files changes
@@ -962,7 +939,7 @@ Options:
962
939
  -o, --output <file> specify output filename
963
940
  -f, --format <format> type of output (esm, amd, cjs, iife, umd, system), default: esm
964
941
  -h, --help output usage information
965
- --prepare auto configure package.json exports for building
942
+
966
943
  --external <mod> specify an external dependency, separate by comma
967
944
  --no-external do not bundle external dependencies
968
945
  --no-clean do not clean dist folder before building, default: false
@@ -986,7 +963,11 @@ async function lint(cwd) {
986
963
  await lint$1(await getPackageMeta(cwd));
987
964
  }
988
965
  async function parseCliArgs(argv) {
989
- const args = await yargs__default.default(helpers.hideBin(argv)).option('cwd', {
966
+ const args = await yargs__default.default(helpers.hideBin(argv)).option('watch', {
967
+ type: 'boolean',
968
+ alias: 'w',
969
+ description: 'watch src files changes'
970
+ }).option('cwd', {
990
971
  type: 'string',
991
972
  description: 'specify current working directory'
992
973
  }).option('dts', {
@@ -1008,10 +989,6 @@ async function parseCliArgs(argv) {
1008
989
  alias: 'f',
1009
990
  default: 'esm',
1010
991
  description: 'type of output (esm, amd, cjs, iife, umd, system)'
1011
- }).option('watch', {
1012
- type: 'boolean',
1013
- alias: 'w',
1014
- description: 'watch src files changes'
1015
992
  }).option('minify', {
1016
993
  type: 'boolean',
1017
994
  alias: 'm',
@@ -1039,22 +1016,23 @@ async function parseCliArgs(argv) {
1039
1016
  return typeof arg === 'string' || typeof arg === 'boolean' ? arg : undefined;
1040
1017
  },
1041
1018
  description: 'specify an external dependency, separate by comma'
1042
- }).option('prepare', {
1043
- type: 'boolean',
1044
- description: 'auto configure package.json exports for building'
1045
1019
  }).option('tsconfig', {
1046
1020
  type: 'string',
1047
1021
  description: 'path to tsconfig file'
1048
1022
  }).option('dts-bundle', {
1049
1023
  type: 'boolean',
1050
1024
  description: 'bundle type declaration files'
1025
+ }).command('prepare', 'auto configure package.json exports for building', ()=>{}, (argv)=>{
1026
+ return prepare(argv.cwd || process.cwd());
1027
+ }).command('lint', 'lint package.json', ()=>{}, (argv)=>{
1028
+ return lint(argv.cwd || process.cwd());
1051
1029
  }).version(version).help('help', 'output usage information').showHelpOnFail(true).parse();
1052
1030
  const source = args._[0];
1053
1031
  const parsedArgs = {
1054
1032
  source,
1055
1033
  format: args['format'],
1056
1034
  file: args['output'],
1057
- watch: args['watch'],
1035
+ watch: !!args['watch'],
1058
1036
  minify: args['minify'],
1059
1037
  sourcemap: !!args['sourcemap'],
1060
1038
  cwd: args['cwd'],
@@ -1067,7 +1045,6 @@ async function parseCliArgs(argv) {
1067
1045
  external: args['external'] === false ? null : args['external'],
1068
1046
  clean: args['clean'] !== false,
1069
1047
  env: args['env'],
1070
- prepare: !!args['prepare'],
1071
1048
  tsconfig: args['tsconfig']
1072
1049
  };
1073
1050
  return parsedArgs;
@@ -1094,11 +1071,8 @@ async function run(args) {
1094
1071
  clean,
1095
1072
  tsconfig
1096
1073
  };
1097
- if (args.prepare) {
1098
- return await prepare(cwd);
1099
- }
1100
1074
  const cliEntry = source ? path__default.default.resolve(cwd, source) : '';
1101
- // lint package
1075
+ // lint package by default
1102
1076
  await lint(cwd);
1103
1077
  const { default: ora } = await import('ora');
1104
1078
  const oraInstance = process.stdout.isTTY ? ora({
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var fsp = require('fs/promises');
4
4
  var fs = require('fs');
5
5
  var path = require('path');
6
6
  require('pretty-bytes');
7
+ var glob = require('glob');
7
8
  var require$$0 = require('tty');
8
9
  var module$1 = require('module');
9
10
  var rollup = require('rollup');
@@ -100,7 +101,7 @@ const logger = {
100
101
  console.log(...arg);
101
102
  },
102
103
  warn (...arg) {
103
- console.warn(color('yellow')('⚠️'), ...arg);
104
+ console.warn(color('yellow')('!'), ...arg);
104
105
  },
105
106
  error (...arg) {
106
107
  console.error(color('red')('⨯'), ...arg);
@@ -241,6 +242,11 @@ const DEFAULT_TS_CONFIG = {
241
242
  };
242
243
  const BINARY_TAG = '$binary';
243
244
 
245
+ const { sep } = require('path');
246
+ function relativify(path) {
247
+ return path.startsWith('.') ? path : `.${sep}${path}`;
248
+ }
249
+
244
250
  function exit(err) {
245
251
  logger.error(err);
246
252
  process.exit(1);
@@ -306,7 +312,7 @@ async function getSourcePathFromExportPath(cwd, exportPath, exportType) {
306
312
  // TODO: add unit test
307
313
  // Unlike path.basename, forcedly removing extension
308
314
  function filePathWithoutExtension(filePath) {
309
- if (!filePath) return;
315
+ if (!filePath) return '';
310
316
  const lastDotIndex = filePath.lastIndexOf('.');
311
317
  const lastSlashIndex = filePath.lastIndexOf('/');
312
318
  if (lastDotIndex !== -1 && lastDotIndex > lastSlashIndex) {
@@ -362,6 +368,16 @@ async function removeOutputDir(output, cwd) {
362
368
  function isBinExportPath(exportPath) {
363
369
  return exportPath === BINARY_TAG || exportPath.startsWith(BINARY_TAG + '/');
364
370
  }
371
+ // shared.ts -> ./shared
372
+ // shared.<export condition>.ts -> ./shared.<export condition>
373
+ // index.ts -> ./index
374
+ // index.development.ts -> ./index.development
375
+ // foo/index.ts -> ./foo
376
+ function sourceFilenameToExportFullPath(filename) {
377
+ const ext = path__default.default.extname(filename);
378
+ const exportPath = filename.slice(0, -ext.length);
379
+ return relativify(exportPath);
380
+ }
365
381
 
366
382
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
367
383
  // End of searching, export value is file path.
@@ -503,7 +519,8 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs, spec
503
519
  function isEsmExportName(name, ext) {
504
520
  return [
505
521
  'import',
506
- 'module'
522
+ 'module',
523
+ 'module-sync'
507
524
  ].includes(name) || ext === 'mjs';
508
525
  }
509
526
  function isCjsExportName(pkg, exportCondition, ext) {
@@ -564,20 +581,7 @@ function getExportTypeFromFile(filename, pkgType) {
564
581
  return exportType;
565
582
  }
566
583
 
567
- function relativify(path) {
568
- return path.startsWith('.') ? path : `./${path}`;
569
- }
570
-
571
- // shared.ts -> ./shared
572
- // shared.<export condition>.ts -> ./shared
573
- // index.ts -> ./index
574
- // index.development.ts -> ./index.development
575
- function sourceFilenameToExportFullPath(filename) {
576
- const baseName = baseNameWithoutExtension(filename);
577
- let exportPath = baseName;
578
- return relativify(exportPath);
579
- }
580
- async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, sourceFile) {
584
+ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, sourceFile) {
581
585
  const entries = {};
582
586
  if (sourceFile) {
583
587
  const defaultExport = parsedExportsInfo.get('./index')[0];
@@ -590,7 +594,7 @@ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, sourceFil
590
594
  };
591
595
  }
592
596
  // Find source files
593
- const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo);
597
+ const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo, pkg);
594
598
  // A mapping between each export path and its related special export conditions,
595
599
  // excluding the 'default' export condition.
596
600
  // { './index' => Set('development', 'edge-light') }
@@ -687,6 +691,10 @@ function getSpecialExportTypeFromComposedExportPath(composedExportType) {
687
691
  }
688
692
  return 'default';
689
693
  }
694
+ function getSpecialExportTypeFromSourcePath(sourcePath) {
695
+ const fileBaseName = baseNameWithoutExtension(sourcePath);
696
+ return getSpecialExportTypeFromComposedExportPath(fileBaseName);
697
+ }
690
698
  function getExportTypeFromExportTypesArray(types) {
691
699
  let exportType = 'default';
692
700
  new Set(types).forEach((value)=>{
@@ -735,87 +743,56 @@ function stripSpecialCondition(exportPath) {
735
743
  async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
736
744
  const isBinaryPath = isBinExportPath(originalSubpath);
737
745
  const subpath = originalSubpath.replace(BINARY_TAG, 'bin');
738
- const absoluteDirPath = path__default.default.join(sourceFolderPath, subpath);
739
- const isDirectory = fs__default.default.existsSync(absoluteDirPath) ? (await fsp__default.default.stat(absoluteDirPath)).isDirectory() : false;
740
- if (isDirectory) {
746
+ const absoluteDirPath = path.join(sourceFolderPath, subpath);
747
+ const dirName = path.dirname(subpath) // Get directory name regardless of file/directory
748
+ ;
749
+ const baseName = path.basename(subpath) // Get base name regardless of file/directory
750
+ ;
751
+ const dirPath = path.join(sourceFolderPath, dirName);
752
+ // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
753
+ const globalPatterns = [
754
+ `${baseName}.{${[
755
+ ...availableExtensions
756
+ ].join(',')}}`,
757
+ `${baseName}.{${[
758
+ ...runtimeExportConventions
759
+ ].join(',')}}.{${[
760
+ ...availableExtensions
761
+ ].join(',')}}`,
762
+ `${baseName}/index.{${[
763
+ ...availableExtensions
764
+ ].join(',')}}`,
765
+ `${baseName}/index.{${[
766
+ ...runtimeExportConventions
767
+ ].join(',')}}.{${[
768
+ ...availableExtensions
769
+ ].join(',')}}`
770
+ ];
771
+ const files = await glob.glob(globalPatterns, {
772
+ cwd: dirPath,
773
+ nodir: true,
774
+ ignore: '**/_*'
775
+ });
776
+ for (const file of files){
777
+ const ext = path.extname(file).slice(1);
778
+ if (!availableExtensions.has(ext) || isTestFile(file)) continue;
779
+ const sourceFileAbsolutePath = path.join(dirPath, file);
780
+ const exportPath = relativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath);
741
781
  if (isBinaryPath) {
742
- const binDirentList = await fsp__default.default.readdir(absoluteDirPath, {
743
- withFileTypes: true
744
- });
745
- for (const binDirent of binDirentList){
746
- if (binDirent.isFile()) {
747
- const binFileAbsolutePath = path__default.default.join(absoluteDirPath, binDirent.name);
748
- if (fs__default.default.existsSync(binFileAbsolutePath)) {
749
- bins.set(normalizeExportPath(originalSubpath), binFileAbsolutePath);
750
- }
751
- }
752
- }
782
+ bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
753
783
  } else {
754
- // Search folder/index.<ext> convention entries
755
- for (const extension of availableExtensions){
756
- const indexAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${extension}`);
757
- // Search folder/index.<special type>.<ext> convention entries
758
- for (const specialExportType of runtimeExportConventions){
759
- const indexSpecialAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${specialExportType}.${extension}`);
760
- if (fs__default.default.existsSync(indexSpecialAbsoluteFile)) {
761
- // Add special export path
762
- // { ./<export path>.<special cond>: { <special cond>: 'index.<special cond>.<ext>' } }
763
- const exportPath = relativify(subpath);
764
- const specialExportPath = exportPath + '.' + specialExportType;
765
- const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
766
- sourceFilesMap[specialExportType] = indexSpecialAbsoluteFile;
767
- exportsEntries.set(specialExportPath, sourceFilesMap);
768
- }
769
- }
770
- if (fs__default.default.existsSync(indexAbsoluteFile) && !isTestFile(indexAbsoluteFile)) {
771
- const exportPath = relativify(subpath);
772
- const sourceFilesMap = exportsEntries.get(exportPath) || {};
773
- const exportType = getExportTypeFromExportPath(exportPath);
774
- sourceFilesMap[exportType] = indexAbsoluteFile;
775
- exportsEntries.set(exportPath, sourceFilesMap);
776
- }
777
- }
778
- }
779
- } else {
780
- // subpath could be a file
781
- const dirName = path.dirname(subpath);
782
- const baseName = path.basename(subpath);
783
- // Read current file's directory
784
- const dirPath = path__default.default.join(sourceFolderPath, dirName);
785
- if (!fs__default.default.existsSync(dirPath)) {
786
- return;
787
- }
788
- const dirents = await fsp__default.default.readdir(dirPath, {
789
- withFileTypes: true
790
- });
791
- for (const dirent of dirents){
792
- // index.development.js -> index.development
793
- const direntBaseName = baseNameWithoutExtension(dirent.name);
794
- const ext = path.extname(dirent.name).slice(1);
795
- if (!dirent.isFile() || direntBaseName !== baseName || !availableExtensions.has(ext)) {
796
- continue;
797
- }
798
- if (isTestFile(dirent.name)) {
799
- continue;
800
- }
801
- const sourceFileAbsolutePath = path__default.default.join(dirPath, dirent.name);
802
- if (isBinaryPath) {
803
- bins.set(originalSubpath, sourceFileAbsolutePath);
804
- } else {
805
- let sourceFilesMap = exportsEntries.get(originalSubpath) || {};
806
- const exportType = getExportTypeFromExportPath(originalSubpath);
807
- sourceFilesMap[exportType] = sourceFileAbsolutePath;
808
- if (specialExportConventions.has(exportType)) {
809
- // e.g. ./foo/index.react-server -> ./foo/index
810
- const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
811
- const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
812
- sourceFilesMap = {
813
- ...fallbackSourceFilesMap,
814
- ...sourceFilesMap
815
- };
816
- }
817
- exportsEntries.set(originalSubpath, sourceFilesMap);
784
+ const parts = path.basename(file).split('.');
785
+ const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath);
786
+ const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches
787
+ ;
788
+ const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
789
+ sourceFilesMap[exportType] = sourceFileAbsolutePath;
790
+ if (specialExportConventions.has(exportType)) {
791
+ const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
792
+ const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
793
+ Object.assign(sourceFilesMap, fallbackSourceFilesMap);
818
794
  }
795
+ exportsEntries.set(specialExportPath, sourceFilesMap);
819
796
  }
820
797
  }
821
798
  }
@@ -830,7 +807,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
830
807
  * "react-server" => "source"
831
808
  * }
832
809
  * }
833
- */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo) {
810
+ */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo, pkg) {
834
811
  const bins = new Map();
835
812
  const exportsEntries = new Map();
836
813
  for (const [exportPath, exportInfo] of parsedExportsInfo.entries()){
@@ -846,6 +823,64 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
846
823
  await collectSourceEntriesByExportPath(sourceFolderPath, exportPath + '.' + specialCondition, bins, exportsEntries);
847
824
  }
848
825
  }
826
+ // Search private shared module files which are not in the parsedExportsInfo, but start with _.
827
+ // e.g. _utils.ts, _utils/index.ts
828
+ // e.g. _utils.development.ts, _utils/index.development.js
829
+ const privatePattern = [
830
+ `**/_*{,/index}.{${[
831
+ ...availableExtensions
832
+ ].join(',')}}`,
833
+ `**/_*{,/index}.{${[
834
+ ...runtimeExportConventions
835
+ ].join(',')}}.{${[
836
+ ...availableExtensions
837
+ ].join(',')}}`
838
+ ];
839
+ const privateFiles = await glob.glob(privatePattern, {
840
+ cwd: sourceFolderPath,
841
+ nodir: true
842
+ });
843
+ for (const file of privateFiles){
844
+ const sourceFileAbsolutePath = path.join(sourceFolderPath, file);
845
+ const exportPath = sourceFilenameToExportFullPath(file);
846
+ const isEsmPkg = isESModulePackage(pkg.type);
847
+ // const specialItems: [string, string][] = []
848
+ const specialExportType = getSpecialExportTypeFromSourcePath(file);
849
+ const normalizedExportPath = stripSpecialCondition(exportPath);
850
+ const isSpecialExport = specialExportType !== 'default';
851
+ // export type: default => ''
852
+ // export type: development => '.development'
853
+ const condPart = isSpecialExport ? specialExportType + '.' : '';
854
+ // Map private shared files to the dist directory
855
+ // e.g. ./_utils.ts -> ./dist/_utils.js
856
+ const privateExportInfo = [
857
+ [
858
+ relativify(path.join('./dist', exportPath + (isEsmPkg ? '.js' : '.mjs'))),
859
+ condPart + 'import'
860
+ ],
861
+ [
862
+ relativify(path.join('./dist', exportPath + (isEsmPkg ? '.cjs' : '.js'))),
863
+ condPart + 'require'
864
+ ]
865
+ ];
866
+ const exportsInfo = parsedExportsInfo.get(normalizedExportPath);
867
+ if (!exportsInfo) {
868
+ // Add private shared files to parsedExportsInfo
869
+ parsedExportsInfo.set(normalizedExportPath, privateExportInfo);
870
+ } else {
871
+ // Merge private shared files to the existing exportsInfo
872
+ exportsInfo.push(...privateExportInfo);
873
+ }
874
+ // Insert private shared modules into the entries
875
+ const entry = exportsEntries.get(exportPath);
876
+ if (!entry) {
877
+ exportsEntries.set(exportPath, {
878
+ [specialExportType]: sourceFileAbsolutePath
879
+ });
880
+ } else {
881
+ entry[specialExportType] = sourceFileAbsolutePath;
882
+ }
883
+ }
849
884
  return {
850
885
  bins,
851
886
  exportsEntries
@@ -882,9 +917,9 @@ function createOutputState({ entries }) {
882
917
  return {
883
918
  name: 'collect-sizes',
884
919
  writeBundle (options, bundle) {
885
- const dir = options.dir || path.posix.dirname(options.file);
920
+ const dir = options.dir || path__default.default.dirname(options.file);
886
921
  Object.entries(bundle).forEach(([fileName, chunk])=>{
887
- const filePath = path.posix.join(dir, fileName);
922
+ const filePath = path__default.default.join(dir, fileName);
888
923
  if (chunk.type !== 'chunk') {
889
924
  return;
890
925
  }
@@ -895,7 +930,7 @@ function createOutputState({ entries }) {
895
930
  const sourceFileName = chunk.facadeModuleId || '';
896
931
  const exportPath = removeScope(reversedMapping.get(sourceFileName) || '.');
897
932
  addSize({
898
- fileName: path__default.default.isAbsolute(cwd) ? path.posix.relative(cwd, filePath) : filePath,
933
+ fileName: path__default.default.relative(cwd, filePath).replace(path__default.default.sep, '/'),
899
934
  size,
900
935
  sourceFileName,
901
936
  exportPath
@@ -1021,7 +1056,7 @@ async function writeDefaultTsconfig(tsConfigPath) {
1021
1056
  const FILENAME_REGEX = /__filename/;
1022
1057
  const DIRNAME_REGEX = /__dirname/;
1023
1058
  // not char, or space before require(.resolve)?(
1024
- const GLOBAL_REQUIRE_REGEX = /[\W\s]require(\.resolve)?\(/;
1059
+ const GLOBAL_REQUIRE_REGEX = /(?:^|[^.\w'"`])\brequire(\.resolve)?\(\s*[\r\n]*(['"`])/;
1025
1060
  const PolyfillComment = '/** rollup-private-do-not-use-esm-shim-polyfill */';
1026
1061
  const createESMShim = ({ filename, dirname, globalRequire })=>{
1027
1062
  const useNodeUrl = filename || dirname;
@@ -1298,28 +1333,6 @@ function aliasEntries({ entry: sourceFilePath, conditionNames, entries, format,
1298
1333
  };
1299
1334
  }
1300
1335
 
1301
- function prependDirectives() {
1302
- return {
1303
- name: 'prependDirective',
1304
- transform: {
1305
- order: 'post',
1306
- handler (code, id) {
1307
- var _moduleInfo_meta;
1308
- const moduleInfo = this.getModuleInfo(id);
1309
- if (moduleInfo == null ? void 0 : (_moduleInfo_meta = moduleInfo.meta) == null ? void 0 : _moduleInfo_meta.preserveDirectives) {
1310
- const firstDirective = moduleInfo.meta.preserveDirectives.directives[0];
1311
- if (firstDirective) {
1312
- const directive = firstDirective.value;
1313
- const directiveCode = `'${directive}';`;
1314
- return directiveCode + '\n' + code;
1315
- }
1316
- }
1317
- return null;
1318
- }
1319
- }
1320
- };
1321
- }
1322
-
1323
1336
  const prependShebang = (entry)=>({
1324
1337
  name: 'prependShebang',
1325
1338
  transform: (code, id)=>{
@@ -1503,8 +1516,7 @@ async function buildInputConfig(entry, bundleConfig, exportCondition, buildConte
1503
1516
  }),
1504
1517
  commonjs__default.default({
1505
1518
  exclude: bundleConfig.external || null
1506
- }),
1507
- prependDirectives()
1519
+ })
1508
1520
  ]).filter(isNotNull);
1509
1521
  return {
1510
1522
  input: entry,
@@ -1539,19 +1551,6 @@ function getModuleLayer(moduleMeta) {
1539
1551
  const moduleLayer = directives[0];
1540
1552
  return moduleLayer;
1541
1553
  }
1542
- function getCustomModuleLayer(moduleId) {
1543
- const segments = path__default.default.basename(moduleId).split('.');
1544
- if (segments.length >= 2) {
1545
- const [layerSegment, ext] = segments.slice(-2);
1546
- const baseName = segments[0];
1547
- const match = layerSegment.match(/^(\w+)-runtime$/);
1548
- const layer = match && match[1];
1549
- if (availableExtensions.has(ext) && layer && layer.length > 0) {
1550
- return baseName + '-' + layer;
1551
- }
1552
- }
1553
- return undefined;
1554
- }
1555
1554
  // dependencyGraphMap: Map<subModuleId, Set<entryParentId>>
1556
1555
  function createSplitChunks(dependencyGraphMap, entryFiles) {
1557
1556
  // If there's existing chunk being splitted, and contains a layer { <id>: <chunkGroup> }
@@ -1564,15 +1563,6 @@ function createSplitChunks(dependencyGraphMap, entryFiles) {
1564
1563
  const { isEntry } = moduleInfo;
1565
1564
  const moduleMeta = moduleInfo.meta;
1566
1565
  const moduleLayer = getModuleLayer(moduleMeta);
1567
- if (!isEntry) {
1568
- const cachedCustomModuleLayer = splitChunksGroupMap.get(id);
1569
- if (cachedCustomModuleLayer) return cachedCustomModuleLayer;
1570
- const customModuleLayer = getCustomModuleLayer(id);
1571
- if (customModuleLayer) {
1572
- splitChunksGroupMap.set(id, customModuleLayer);
1573
- return customModuleLayer;
1574
- }
1575
- }
1576
1566
  // Collect the sub modules of the entry, if they're having layer, and the same layer with the entry, push them to the dependencyGraphMap.
1577
1567
  if (isEntry) {
1578
1568
  const subModuleIds = ctx.getModuleIds();
@@ -1892,7 +1882,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, ...options } = {}) {
1892
1882
  }
1893
1883
  }
1894
1884
  }
1895
- const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, inputFile);
1885
+ const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, inputFile);
1896
1886
  const hasTypeScriptFiles = Object.values(entries).some((entry)=>isTypescriptFile(entry.source));
1897
1887
  if (hasTypeScriptFiles && !hasTsConfig) {
1898
1888
  const tsConfigPath = path.resolve(cwd, 'tsconfig.json');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "5.6.1",
3
+ "version": "6.0.0-beta.1",
4
4
  "description": "zero config bundler for js/ts/jsx libraries",
5
5
  "bin": "./dist/bin/cli.js",
6
6
  "main": "./dist/index.js",
@@ -36,22 +36,23 @@
36
36
  },
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
- "@rollup/plugin-commonjs": "^28.0.0",
39
+ "@rollup/plugin-commonjs": "^28.0.1",
40
40
  "@rollup/plugin-json": "^6.1.0",
41
- "@rollup/plugin-node-resolve": "^15.2.3",
41
+ "@rollup/plugin-node-resolve": "^15.3.0",
42
42
  "@rollup/plugin-replace": "^6.0.1",
43
43
  "@rollup/plugin-wasm": "^6.2.2",
44
44
  "@rollup/pluginutils": "^5.1.0",
45
- "@swc/core": "^1.7.14",
45
+ "@swc/core": "^1.9.3",
46
46
  "@swc/helpers": "^0.5.11",
47
47
  "clean-css": "^5.3.3",
48
+ "glob": "^11.0.0",
48
49
  "magic-string": "^0.30.11",
49
50
  "ora": "^8.0.1",
50
51
  "pretty-bytes": "^5.6.0",
51
- "rollup": "^4.24.0",
52
+ "rollup": "^4.27.4",
52
53
  "rollup-plugin-dts": "^6.1.1",
53
54
  "rollup-plugin-swc3": "^0.11.1",
54
- "rollup-preserve-directives": "^1.1.2",
55
+ "rollup-preserve-directives": "^1.1.3",
55
56
  "tslib": "^2.7.0",
56
57
  "yargs": "^17.7.2"
57
58
  },
@@ -73,7 +74,7 @@
73
74
  "@swc/types": "^0.1.9",
74
75
  "@types/clean-css": "^4.2.11",
75
76
  "@types/jest": "29.0.0",
76
- "@types/node": "^20.4.1",
77
+ "@types/node": "^22.9.3",
77
78
  "@types/yargs": "^17.0.33",
78
79
  "bunchee": "link:./",
79
80
  "cross-env": "^7.0.3",
@@ -82,7 +83,7 @@
82
83
  "lint-staged": "^15.2.2",
83
84
  "picocolors": "^1.0.0",
84
85
  "prettier": "^3.0.0",
85
- "react": "^18.2.0",
86
+ "react": "^18.3.1",
86
87
  "typescript": "^5.6.2"
87
88
  },
88
89
  "lint-staged": {