bunchee 5.6.1 → 6.0.0-rc.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
@@ -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,15 @@ 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
212
+ // index.ts -> ./index
213
+ // index.development.ts -> ./index.development
214
+ function sourceFilenameToExportFullPath(filename) {
215
+ const baseName = baseNameWithoutExtension(filename);
216
+ let exportPath = baseName;
217
+ return relativify(exportPath);
218
+ }
206
219
 
207
220
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
208
221
  // End of searching, export value is file path.
@@ -525,26 +538,13 @@ function lint$1(pkg) {
525
538
  }
526
539
  }
527
540
 
528
- var version = "5.6.1";
529
-
530
- function relativify(path) {
531
- return path.startsWith('.') ? path : `./${path}`;
532
- }
541
+ var version = "6.0.0-rc.0";
533
542
 
534
543
  async function writeDefaultTsconfig(tsConfigPath) {
535
544
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
536
545
  logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
537
546
  }
538
547
 
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
548
  // ./index -> default
549
549
  // ./index.development -> development
550
550
  // ./index.react-server -> react-server
@@ -597,125 +597,97 @@ function normalizeExportPath(exportPath) {
597
597
  async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
598
598
  const isBinaryPath = isBinExportPath(originalSubpath);
599
599
  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) {
600
+ const absoluteDirPath = path.join(sourceFolderPath, subpath);
601
+ const dirName = path.dirname(subpath) // Get directory name regardless of file/directory
602
+ ;
603
+ const baseName = path.basename(subpath) // Get base name regardless of file/directory
604
+ ;
605
+ const dirPath = path.join(sourceFolderPath, dirName);
606
+ // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
607
+ const globalPatterns = [
608
+ `${baseName}.{${[
609
+ ...availableExtensions
610
+ ].join(',')}}`,
611
+ `${baseName}.{${[
612
+ ...runtimeExportConventions
613
+ ].join(',')}}.{${[
614
+ ...availableExtensions
615
+ ].join(',')}}`,
616
+ `${baseName}/index.{${[
617
+ ...availableExtensions
618
+ ].join(',')}}`,
619
+ `${baseName}/index.{${[
620
+ ...runtimeExportConventions
621
+ ].join(',')}}.{${[
622
+ ...availableExtensions
623
+ ].join(',')}}`
624
+ ];
625
+ const files = await glob.glob(globalPatterns, {
626
+ cwd: dirPath,
627
+ nodir: true,
628
+ ignore: '**/_*'
629
+ });
630
+ for (const file of files){
631
+ const ext = path.extname(file).slice(1);
632
+ if (!availableExtensions.has(ext) || isTestFile(file)) continue;
633
+ const sourceFileAbsolutePath = path.join(dirPath, file);
634
+ const exportPath = relativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath);
603
635
  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
- }
636
+ bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
615
637
  } 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);
638
+ const parts = path.basename(file).split('.');
639
+ const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath);
640
+ const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches
641
+ ;
642
+ const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
643
+ sourceFilesMap[exportType] = sourceFileAbsolutePath;
644
+ if (specialExportConventions.has(exportType)) {
645
+ const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
646
+ const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
647
+ Object.assign(sourceFilesMap, fallbackSourceFilesMap);
680
648
  }
649
+ exportsEntries.set(specialExportPath, sourceFilesMap);
681
650
  }
682
651
  }
683
652
  }
684
- // For `prepare`
653
+
654
+ // For `prepare` command
685
655
  async function collectSourceEntries(sourceFolderPath) {
686
656
  const bins = new Map();
687
657
  const exportsEntries = new Map();
688
- if (!fs__default.default.existsSync(sourceFolderPath)) {
658
+ if (!fs.existsSync(sourceFolderPath)) {
689
659
  return {
690
660
  bins,
691
661
  exportsEntries
692
662
  };
693
663
  }
694
- const entryFileDirentList = await fsp__default.default.readdir(sourceFolderPath, {
695
- withFileTypes: true
664
+ // Match with global patterns
665
+ // bin/**/*.<ext>, bin/**/index.<ext>
666
+ const binPattern = `bin/**/*.{${[
667
+ ...availableExtensions
668
+ ].join(',')}}`;
669
+ const srcPattern = `**/*.{${[
670
+ ...availableExtensions
671
+ ].join(',')}}`;
672
+ const binMatches = await glob.glob(binPattern, {
673
+ cwd: sourceFolderPath,
674
+ nodir: true,
675
+ ignore: '**/_*'
696
676
  });
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);
677
+ const srcMatches = await glob.glob(srcPattern, {
678
+ cwd: sourceFolderPath,
679
+ nodir: true,
680
+ ignore: '**/_*'
681
+ });
682
+ for (const file of binMatches){
683
+ // convert relative path to export path
684
+ const exportPath = sourceFilenameToExportFullPath(file);
703
685
  await collectSourceEntriesByExportPath(sourceFolderPath, exportPath, bins, exportsEntries);
704
686
  }
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
- }
687
+ for (const file of srcMatches){
688
+ const binExportPath = file.replace(/^bin/, BINARY_TAG)// Remove index.<ext> to [^index].<ext> to build the export path
689
+ .replace(/(\/index)?\.[^/]+$/, '');
690
+ await collectSourceEntriesByExportPath(sourceFolderPath, binExportPath, bins, exportsEntries);
719
691
  }
720
692
  return {
721
693
  bins,
@@ -955,6 +927,9 @@ function logOutputState(stats) {
955
927
  const helpMessage = `
956
928
  Usage: bunchee [options]
957
929
 
930
+ Commands:
931
+ prepare auto configure package.json exports for building
932
+
958
933
  Options:
959
934
  -v, --version output the version number
960
935
  -w, --watch watch src files changes
@@ -962,7 +937,7 @@ Options:
962
937
  -o, --output <file> specify output filename
963
938
  -f, --format <format> type of output (esm, amd, cjs, iife, umd, system), default: esm
964
939
  -h, --help output usage information
965
- --prepare auto configure package.json exports for building
940
+
966
941
  --external <mod> specify an external dependency, separate by comma
967
942
  --no-external do not bundle external dependencies
968
943
  --no-clean do not clean dist folder before building, default: false
@@ -986,7 +961,11 @@ async function lint(cwd) {
986
961
  await lint$1(await getPackageMeta(cwd));
987
962
  }
988
963
  async function parseCliArgs(argv) {
989
- const args = await yargs__default.default(helpers.hideBin(argv)).option('cwd', {
964
+ const args = await yargs__default.default(helpers.hideBin(argv)).option('watch', {
965
+ type: 'boolean',
966
+ alias: 'w',
967
+ description: 'watch src files changes'
968
+ }).option('cwd', {
990
969
  type: 'string',
991
970
  description: 'specify current working directory'
992
971
  }).option('dts', {
@@ -1008,10 +987,6 @@ async function parseCliArgs(argv) {
1008
987
  alias: 'f',
1009
988
  default: 'esm',
1010
989
  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
990
  }).option('minify', {
1016
991
  type: 'boolean',
1017
992
  alias: 'm',
@@ -1039,22 +1014,23 @@ async function parseCliArgs(argv) {
1039
1014
  return typeof arg === 'string' || typeof arg === 'boolean' ? arg : undefined;
1040
1015
  },
1041
1016
  description: 'specify an external dependency, separate by comma'
1042
- }).option('prepare', {
1043
- type: 'boolean',
1044
- description: 'auto configure package.json exports for building'
1045
1017
  }).option('tsconfig', {
1046
1018
  type: 'string',
1047
1019
  description: 'path to tsconfig file'
1048
1020
  }).option('dts-bundle', {
1049
1021
  type: 'boolean',
1050
1022
  description: 'bundle type declaration files'
1023
+ }).command('prepare', 'auto configure package.json exports for building', ()=>{}, (argv)=>{
1024
+ return prepare(argv.cwd || process.cwd());
1025
+ }).command('lint', 'lint package.json', ()=>{}, (argv)=>{
1026
+ return lint(argv.cwd || process.cwd());
1051
1027
  }).version(version).help('help', 'output usage information').showHelpOnFail(true).parse();
1052
1028
  const source = args._[0];
1053
1029
  const parsedArgs = {
1054
1030
  source,
1055
1031
  format: args['format'],
1056
1032
  file: args['output'],
1057
- watch: args['watch'],
1033
+ watch: !!args['watch'],
1058
1034
  minify: args['minify'],
1059
1035
  sourcemap: !!args['sourcemap'],
1060
1036
  cwd: args['cwd'],
@@ -1067,7 +1043,6 @@ async function parseCliArgs(argv) {
1067
1043
  external: args['external'] === false ? null : args['external'],
1068
1044
  clean: args['clean'] !== false,
1069
1045
  env: args['env'],
1070
- prepare: !!args['prepare'],
1071
1046
  tsconfig: args['tsconfig']
1072
1047
  };
1073
1048
  return parsedArgs;
@@ -1094,11 +1069,8 @@ async function run(args) {
1094
1069
  clean,
1095
1070
  tsconfig
1096
1071
  };
1097
- if (args.prepare) {
1098
- return await prepare(cwd);
1099
- }
1100
1072
  const cliEntry = source ? path__default.default.resolve(cwd, source) : '';
1101
- // lint package
1073
+ // lint package by default
1102
1074
  await lint(cwd);
1103
1075
  const { default: ora } = await import('ora');
1104
1076
  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,15 @@ 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
373
+ // index.ts -> ./index
374
+ // index.development.ts -> ./index.development
375
+ function sourceFilenameToExportFullPath(filename) {
376
+ const baseName = baseNameWithoutExtension(filename);
377
+ let exportPath = baseName;
378
+ return relativify(exportPath);
379
+ }
365
380
 
366
381
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
367
382
  // End of searching, export value is file path.
@@ -503,7 +518,8 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs, spec
503
518
  function isEsmExportName(name, ext) {
504
519
  return [
505
520
  'import',
506
- 'module'
521
+ 'module',
522
+ 'module-sync'
507
523
  ].includes(name) || ext === 'mjs';
508
524
  }
509
525
  function isCjsExportName(pkg, exportCondition, ext) {
@@ -564,20 +580,7 @@ function getExportTypeFromFile(filename, pkgType) {
564
580
  return exportType;
565
581
  }
566
582
 
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) {
583
+ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, sourceFile) {
581
584
  const entries = {};
582
585
  if (sourceFile) {
583
586
  const defaultExport = parsedExportsInfo.get('./index')[0];
@@ -590,7 +593,7 @@ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, sourceFil
590
593
  };
591
594
  }
592
595
  // Find source files
593
- const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo);
596
+ const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo, pkg);
594
597
  // A mapping between each export path and its related special export conditions,
595
598
  // excluding the 'default' export condition.
596
599
  // { './index' => Set('development', 'edge-light') }
@@ -735,87 +738,56 @@ function stripSpecialCondition(exportPath) {
735
738
  async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
736
739
  const isBinaryPath = isBinExportPath(originalSubpath);
737
740
  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) {
741
+ const absoluteDirPath = path.join(sourceFolderPath, subpath);
742
+ const dirName = path.dirname(subpath) // Get directory name regardless of file/directory
743
+ ;
744
+ const baseName = path.basename(subpath) // Get base name regardless of file/directory
745
+ ;
746
+ const dirPath = path.join(sourceFolderPath, dirName);
747
+ // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
748
+ const globalPatterns = [
749
+ `${baseName}.{${[
750
+ ...availableExtensions
751
+ ].join(',')}}`,
752
+ `${baseName}.{${[
753
+ ...runtimeExportConventions
754
+ ].join(',')}}.{${[
755
+ ...availableExtensions
756
+ ].join(',')}}`,
757
+ `${baseName}/index.{${[
758
+ ...availableExtensions
759
+ ].join(',')}}`,
760
+ `${baseName}/index.{${[
761
+ ...runtimeExportConventions
762
+ ].join(',')}}.{${[
763
+ ...availableExtensions
764
+ ].join(',')}}`
765
+ ];
766
+ const files = await glob.glob(globalPatterns, {
767
+ cwd: dirPath,
768
+ nodir: true,
769
+ ignore: '**/_*'
770
+ });
771
+ for (const file of files){
772
+ const ext = path.extname(file).slice(1);
773
+ if (!availableExtensions.has(ext) || isTestFile(file)) continue;
774
+ const sourceFileAbsolutePath = path.join(dirPath, file);
775
+ const exportPath = relativify(fs.existsSync(absoluteDirPath) && (await fsp__default.default.stat(absoluteDirPath)).isDirectory() ? subpath : originalSubpath);
741
776
  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
- }
777
+ bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
753
778
  } 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);
779
+ const parts = path.basename(file).split('.');
780
+ const exportType = parts.length > 2 ? parts[1] : getExportTypeFromExportPath(exportPath);
781
+ const specialExportPath = exportType !== 'index' && parts.length > 2 ? exportPath + '.' + exportType : exportPath // Adjust for direct file matches
782
+ ;
783
+ const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
784
+ sourceFilesMap[exportType] = sourceFileAbsolutePath;
785
+ if (specialExportConventions.has(exportType)) {
786
+ const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
787
+ const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
788
+ Object.assign(sourceFilesMap, fallbackSourceFilesMap);
818
789
  }
790
+ exportsEntries.set(specialExportPath, sourceFilesMap);
819
791
  }
820
792
  }
821
793
  }
@@ -830,7 +802,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
830
802
  * "react-server" => "source"
831
803
  * }
832
804
  * }
833
- */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo) {
805
+ */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo, pkg) {
834
806
  const bins = new Map();
835
807
  const exportsEntries = new Map();
836
808
  for (const [exportPath, exportInfo] of parsedExportsInfo.entries()){
@@ -846,6 +818,39 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
846
818
  await collectSourceEntriesByExportPath(sourceFolderPath, exportPath + '.' + specialCondition, bins, exportsEntries);
847
819
  }
848
820
  }
821
+ // Search private shared module files which are not in the parsedExportsInfo, but start with _.
822
+ // e.g. _utils.ts, _utils/index.ts
823
+ const privatePattern = `**/_*{,/index}.{${[
824
+ ...availableExtensions
825
+ ].join(',')}}`;
826
+ const privateFiles = await glob.glob(privatePattern, {
827
+ cwd: sourceFolderPath,
828
+ nodir: true
829
+ });
830
+ for (const file of privateFiles){
831
+ const sourceFileAbsolutePath = path.join(sourceFolderPath, file);
832
+ const relativeFilePath = relativify(file);
833
+ const exportPath = filePathWithoutExtension(relativeFilePath);
834
+ // exportsEntries.set(specialExportPath, sourceFilesMap)
835
+ const isEsmPkg = isESModulePackage(pkg.type);
836
+ // Add private shared files to parsedExportsInfo
837
+ parsedExportsInfo.set(exportPath, [
838
+ // Map private shared files to the dist directory
839
+ // e.g. ./_utils.ts -> ./dist/_utils.js
840
+ [
841
+ relativify(path.join('./dist', exportPath + (isEsmPkg ? '.js' : '.mjs'))),
842
+ 'import'
843
+ ],
844
+ [
845
+ relativify(path.join('./dist', exportPath + (isEsmPkg ? '.cjs' : '.js'))),
846
+ 'require'
847
+ ]
848
+ ]);
849
+ // Insert private shared modules into the entries
850
+ exportsEntries.set(exportPath, {
851
+ default: sourceFileAbsolutePath
852
+ });
853
+ }
849
854
  return {
850
855
  bins,
851
856
  exportsEntries
@@ -882,9 +887,9 @@ function createOutputState({ entries }) {
882
887
  return {
883
888
  name: 'collect-sizes',
884
889
  writeBundle (options, bundle) {
885
- const dir = options.dir || path.posix.dirname(options.file);
890
+ const dir = options.dir || path__default.default.dirname(options.file);
886
891
  Object.entries(bundle).forEach(([fileName, chunk])=>{
887
- const filePath = path.posix.join(dir, fileName);
892
+ const filePath = path__default.default.join(dir, fileName);
888
893
  if (chunk.type !== 'chunk') {
889
894
  return;
890
895
  }
@@ -895,7 +900,7 @@ function createOutputState({ entries }) {
895
900
  const sourceFileName = chunk.facadeModuleId || '';
896
901
  const exportPath = removeScope(reversedMapping.get(sourceFileName) || '.');
897
902
  addSize({
898
- fileName: path__default.default.isAbsolute(cwd) ? path.posix.relative(cwd, filePath) : filePath,
903
+ fileName: path__default.default.relative(cwd, filePath).replace(path__default.default.sep, '/'),
899
904
  size,
900
905
  sourceFileName,
901
906
  exportPath
@@ -1021,7 +1026,7 @@ async function writeDefaultTsconfig(tsConfigPath) {
1021
1026
  const FILENAME_REGEX = /__filename/;
1022
1027
  const DIRNAME_REGEX = /__dirname/;
1023
1028
  // not char, or space before require(.resolve)?(
1024
- const GLOBAL_REQUIRE_REGEX = /[\W\s]require(\.resolve)?\(/;
1029
+ const GLOBAL_REQUIRE_REGEX = /(?:^|[^.\w'"`])\brequire(\.resolve)?\(\s*[\r\n]*(['"`])/;
1025
1030
  const PolyfillComment = '/** rollup-private-do-not-use-esm-shim-polyfill */';
1026
1031
  const createESMShim = ({ filename, dirname, globalRequire })=>{
1027
1032
  const useNodeUrl = filename || dirname;
@@ -1539,19 +1544,6 @@ function getModuleLayer(moduleMeta) {
1539
1544
  const moduleLayer = directives[0];
1540
1545
  return moduleLayer;
1541
1546
  }
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
1547
  // dependencyGraphMap: Map<subModuleId, Set<entryParentId>>
1556
1548
  function createSplitChunks(dependencyGraphMap, entryFiles) {
1557
1549
  // If there's existing chunk being splitted, and contains a layer { <id>: <chunkGroup> }
@@ -1564,15 +1556,6 @@ function createSplitChunks(dependencyGraphMap, entryFiles) {
1564
1556
  const { isEntry } = moduleInfo;
1565
1557
  const moduleMeta = moduleInfo.meta;
1566
1558
  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
1559
  // 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
1560
  if (isEntry) {
1578
1561
  const subModuleIds = ctx.getModuleIds();
@@ -1892,7 +1875,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, ...options } = {}) {
1892
1875
  }
1893
1876
  }
1894
1877
  }
1895
- const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, inputFile);
1878
+ const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, inputFile);
1896
1879
  const hasTypeScriptFiles = Object.values(entries).some((entry)=>isTypescriptFile(entry.source));
1897
1880
  if (hasTypeScriptFiles && !hasTsConfig) {
1898
1881
  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-rc.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",
@@ -36,19 +36,20 @@
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
55
  "rollup-preserve-directives": "^1.1.2",
@@ -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": {