bunchee 5.6.0 → 6.0.0-beta.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,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.0";
529
-
530
- function relativify(path) {
531
- return path.startsWith('.') ? path : `./${path}`;
532
- }
542
+ var version = "6.0.0-beta.0";
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);
@@ -142,6 +143,67 @@ const runtimeExportConventions = new Set([
142
143
  // Browser only
143
144
  'browser'
144
145
  ]);
146
+ const runtimeExportConventionsFallback = new Map([
147
+ // ESM only runtime
148
+ [
149
+ 'workerd',
150
+ [
151
+ 'import',
152
+ 'default'
153
+ ]
154
+ ],
155
+ [
156
+ 'edge-light',
157
+ [
158
+ 'import',
159
+ 'default'
160
+ ]
161
+ ],
162
+ [
163
+ 'browser',
164
+ [
165
+ 'import',
166
+ 'default'
167
+ ]
168
+ ],
169
+ // it could be CJS or ESM
170
+ [
171
+ 'electron',
172
+ [
173
+ 'default'
174
+ ]
175
+ ],
176
+ [
177
+ 'react-server',
178
+ [
179
+ 'default'
180
+ ]
181
+ ],
182
+ [
183
+ 'react-native',
184
+ [
185
+ 'default'
186
+ ]
187
+ ],
188
+ [
189
+ 'node',
190
+ [
191
+ 'default'
192
+ ]
193
+ ],
194
+ [
195
+ 'deno',
196
+ [
197
+ 'default'
198
+ ]
199
+ ],
200
+ [
201
+ 'bun',
202
+ [
203
+ 'default'
204
+ ]
205
+ ]
206
+ ]);
145
207
  const optimizeConventions = new Set([
146
208
  'development',
147
209
  'production'
@@ -180,6 +242,11 @@ const DEFAULT_TS_CONFIG = {
180
242
  };
181
243
  const BINARY_TAG = '$binary';
182
244
 
245
+ const { sep } = require('path');
246
+ function relativify(path) {
247
+ return path.startsWith('.') ? path : `.${sep}${path}`;
248
+ }
249
+
183
250
  function exit(err) {
184
251
  logger.error(err);
185
252
  process.exit(1);
@@ -245,7 +312,7 @@ async function getSourcePathFromExportPath(cwd, exportPath, exportType) {
245
312
  // TODO: add unit test
246
313
  // Unlike path.basename, forcedly removing extension
247
314
  function filePathWithoutExtension(filePath) {
248
- if (!filePath) return;
315
+ if (!filePath) return '';
249
316
  const lastDotIndex = filePath.lastIndexOf('.');
250
317
  const lastSlashIndex = filePath.lastIndexOf('/');
251
318
  if (lastDotIndex !== -1 && lastDotIndex > lastSlashIndex) {
@@ -301,6 +368,16 @@ async function removeOutputDir(output, cwd) {
301
368
  function isBinExportPath(exportPath) {
302
369
  return exportPath === BINARY_TAG || exportPath.startsWith(BINARY_TAG + '/');
303
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
+ }
304
381
 
305
382
  function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exportToDist) {
306
383
  // End of searching, export value is file path.
@@ -442,7 +519,8 @@ function addToExportDistMap(exportToDist, exportPath, outputConditionPairs, spec
442
519
  function isEsmExportName(name, ext) {
443
520
  return [
444
521
  'import',
445
- 'module'
522
+ 'module',
523
+ 'module-sync'
446
524
  ].includes(name) || ext === 'mjs';
447
525
  }
448
526
  function isCjsExportName(pkg, exportCondition, ext) {
@@ -503,20 +581,7 @@ function getExportTypeFromFile(filename, pkgType) {
503
581
  return exportType;
504
582
  }
505
583
 
506
- function relativify(path) {
507
- return path.startsWith('.') ? path : `./${path}`;
508
- }
509
-
510
- // shared.ts -> ./shared
511
- // shared.<export condition>.ts -> ./shared
512
- // index.ts -> ./index
513
- // index.development.ts -> ./index.development
514
- function sourceFilenameToExportFullPath(filename) {
515
- const baseName = baseNameWithoutExtension(filename);
516
- let exportPath = baseName;
517
- return relativify(exportPath);
518
- }
519
- async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, sourceFile) {
584
+ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, sourceFile) {
520
585
  const entries = {};
521
586
  if (sourceFile) {
522
587
  const defaultExport = parsedExportsInfo.get('./index')[0];
@@ -529,7 +594,7 @@ async function collectEntriesFromParsedExports(cwd, parsedExportsInfo, sourceFil
529
594
  };
530
595
  }
531
596
  // Find source files
532
- const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo);
597
+ const { bins, exportsEntries } = await collectSourceEntriesFromExportPaths(path.join(cwd, SRC), parsedExportsInfo, pkg);
533
598
  // A mapping between each export path and its related special export conditions,
534
599
  // excluding the 'default' export condition.
535
600
  // { './index' => Set('development', 'edge-light') }
@@ -626,6 +691,10 @@ function getSpecialExportTypeFromComposedExportPath(composedExportType) {
626
691
  }
627
692
  return 'default';
628
693
  }
694
+ function getSpecialExportTypeFromSourcePath(sourcePath) {
695
+ const fileBaseName = baseNameWithoutExtension(sourcePath);
696
+ return getSpecialExportTypeFromComposedExportPath(fileBaseName);
697
+ }
629
698
  function getExportTypeFromExportTypesArray(types) {
630
699
  let exportType = 'default';
631
700
  new Set(types).forEach((value)=>{
@@ -674,87 +743,56 @@ function stripSpecialCondition(exportPath) {
674
743
  async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpath, bins, exportsEntries) {
675
744
  const isBinaryPath = isBinExportPath(originalSubpath);
676
745
  const subpath = originalSubpath.replace(BINARY_TAG, 'bin');
677
- const absoluteDirPath = path__default.default.join(sourceFolderPath, subpath);
678
- const isDirectory = fs__default.default.existsSync(absoluteDirPath) ? (await fsp__default.default.stat(absoluteDirPath)).isDirectory() : false;
679
- 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);
680
781
  if (isBinaryPath) {
681
- const binDirentList = await fsp__default.default.readdir(absoluteDirPath, {
682
- withFileTypes: true
683
- });
684
- for (const binDirent of binDirentList){
685
- if (binDirent.isFile()) {
686
- const binFileAbsolutePath = path__default.default.join(absoluteDirPath, binDirent.name);
687
- if (fs__default.default.existsSync(binFileAbsolutePath)) {
688
- bins.set(normalizeExportPath(originalSubpath), binFileAbsolutePath);
689
- }
690
- }
691
- }
782
+ bins.set(normalizeExportPath(originalSubpath), sourceFileAbsolutePath);
692
783
  } else {
693
- // Search folder/index.<ext> convention entries
694
- for (const extension of availableExtensions){
695
- const indexAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${extension}`);
696
- // Search folder/index.<special type>.<ext> convention entries
697
- for (const specialExportType of runtimeExportConventions){
698
- const indexSpecialAbsoluteFile = path__default.default.join(absoluteDirPath, `index.${specialExportType}.${extension}`);
699
- if (fs__default.default.existsSync(indexSpecialAbsoluteFile)) {
700
- // Add special export path
701
- // { ./<export path>.<special cond>: { <special cond>: 'index.<special cond>.<ext>' } }
702
- const exportPath = relativify(subpath);
703
- const specialExportPath = exportPath + '.' + specialExportType;
704
- const sourceFilesMap = exportsEntries.get(specialExportPath) || {};
705
- sourceFilesMap[specialExportType] = indexSpecialAbsoluteFile;
706
- exportsEntries.set(specialExportPath, sourceFilesMap);
707
- }
708
- }
709
- if (fs__default.default.existsSync(indexAbsoluteFile) && !isTestFile(indexAbsoluteFile)) {
710
- const exportPath = relativify(subpath);
711
- const sourceFilesMap = exportsEntries.get(exportPath) || {};
712
- const exportType = getExportTypeFromExportPath(exportPath);
713
- sourceFilesMap[exportType] = indexAbsoluteFile;
714
- exportsEntries.set(exportPath, sourceFilesMap);
715
- }
716
- }
717
- }
718
- } else {
719
- // subpath could be a file
720
- const dirName = path.dirname(subpath);
721
- const baseName = path.basename(subpath);
722
- // Read current file's directory
723
- const dirPath = path__default.default.join(sourceFolderPath, dirName);
724
- if (!fs__default.default.existsSync(dirPath)) {
725
- return;
726
- }
727
- const dirents = await fsp__default.default.readdir(dirPath, {
728
- withFileTypes: true
729
- });
730
- for (const dirent of dirents){
731
- // index.development.js -> index.development
732
- const direntBaseName = baseNameWithoutExtension(dirent.name);
733
- const ext = path.extname(dirent.name).slice(1);
734
- if (!dirent.isFile() || direntBaseName !== baseName || !availableExtensions.has(ext)) {
735
- continue;
736
- }
737
- if (isTestFile(dirent.name)) {
738
- continue;
739
- }
740
- const sourceFileAbsolutePath = path__default.default.join(dirPath, dirent.name);
741
- if (isBinaryPath) {
742
- bins.set(originalSubpath, sourceFileAbsolutePath);
743
- } else {
744
- let sourceFilesMap = exportsEntries.get(originalSubpath) || {};
745
- const exportType = getExportTypeFromExportPath(originalSubpath);
746
- sourceFilesMap[exportType] = sourceFileAbsolutePath;
747
- if (specialExportConventions.has(exportType)) {
748
- // e.g. ./foo/index.react-server -> ./foo/index
749
- const fallbackExportPath = sourceFilenameToExportFullPath(originalSubpath);
750
- const fallbackSourceFilesMap = exportsEntries.get(fallbackExportPath) || {};
751
- sourceFilesMap = {
752
- ...fallbackSourceFilesMap,
753
- ...sourceFilesMap
754
- };
755
- }
756
- 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);
757
794
  }
795
+ exportsEntries.set(specialExportPath, sourceFilesMap);
758
796
  }
759
797
  }
760
798
  }
@@ -769,7 +807,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
769
807
  * "react-server" => "source"
770
808
  * }
771
809
  * }
772
- */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo) {
810
+ */ async function collectSourceEntriesFromExportPaths(sourceFolderPath, parsedExportsInfo, pkg) {
773
811
  const bins = new Map();
774
812
  const exportsEntries = new Map();
775
813
  for (const [exportPath, exportInfo] of parsedExportsInfo.entries()){
@@ -785,6 +823,64 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
785
823
  await collectSourceEntriesByExportPath(sourceFolderPath, exportPath + '.' + specialCondition, bins, exportsEntries);
786
824
  }
787
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
+ }
788
884
  return {
789
885
  bins,
790
886
  exportsEntries
@@ -821,9 +917,9 @@ function createOutputState({ entries }) {
821
917
  return {
822
918
  name: 'collect-sizes',
823
919
  writeBundle (options, bundle) {
824
- const dir = options.dir || path.posix.dirname(options.file);
920
+ const dir = options.dir || path__default.default.dirname(options.file);
825
921
  Object.entries(bundle).forEach(([fileName, chunk])=>{
826
- const filePath = path.posix.join(dir, fileName);
922
+ const filePath = path__default.default.join(dir, fileName);
827
923
  if (chunk.type !== 'chunk') {
828
924
  return;
829
925
  }
@@ -834,7 +930,7 @@ function createOutputState({ entries }) {
834
930
  const sourceFileName = chunk.facadeModuleId || '';
835
931
  const exportPath = removeScope(reversedMapping.get(sourceFileName) || '.');
836
932
  addSize({
837
- 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, '/'),
838
934
  size,
839
935
  sourceFileName,
840
936
  exportPath
@@ -960,7 +1056,7 @@ async function writeDefaultTsconfig(tsConfigPath) {
960
1056
  const FILENAME_REGEX = /__filename/;
961
1057
  const DIRNAME_REGEX = /__dirname/;
962
1058
  // not char, or space before require(.resolve)?(
963
- const GLOBAL_REQUIRE_REGEX = /[\W\s]require(\.resolve)?\(/;
1059
+ const GLOBAL_REQUIRE_REGEX = /(?:^|[^.\w'"`])\brequire(\.resolve)?\(\s*[\r\n]*(['"`])/;
964
1060
  const PolyfillComment = '/** rollup-private-do-not-use-esm-shim-polyfill */';
965
1061
  const createESMShim = ({ filename, dirname, globalRequire })=>{
966
1062
  const useNodeUrl = filename || dirname;
@@ -1153,7 +1249,17 @@ function findJsBundlePathCallback({ format, bundlePath, conditionNames }, specia
1153
1249
  const hasFormatCond = conditionNames.has('import') || conditionNames.has('require');
1154
1250
  const isMatchedFormat = hasFormatCond ? conditionNames.has(formatCond) : true;
1155
1251
  const isMatchedConditionWithFormat = conditionNames.has(specialCondition) || !conditionNames.has('default') && hasNoSpecialCondition(conditionNames);
1156
- return isMatchedConditionWithFormat && !isTypesCondName && hasBundle && isMatchedFormat;
1252
+ const match = isMatchedConditionWithFormat && !isTypesCondName && hasBundle && isMatchedFormat;
1253
+ if (!match) {
1254
+ const fallback = runtimeExportConventionsFallback.get(specialCondition);
1255
+ if (!fallback) {
1256
+ return false;
1257
+ } else {
1258
+ return fallback.some((name)=>conditionNames.has(name));
1259
+ }
1260
+ } else {
1261
+ return match;
1262
+ }
1157
1263
  }
1158
1264
  function findTypesFileCallback({ format, bundlePath, conditionNames }) {
1159
1265
  const hasCondition = bundlePath != null;
@@ -1468,19 +1574,6 @@ function getModuleLayer(moduleMeta) {
1468
1574
  const moduleLayer = directives[0];
1469
1575
  return moduleLayer;
1470
1576
  }
1471
- function getCustomModuleLayer(moduleId) {
1472
- const segments = path__default.default.basename(moduleId).split('.');
1473
- if (segments.length >= 2) {
1474
- const [layerSegment, ext] = segments.slice(-2);
1475
- const baseName = segments[0];
1476
- const match = layerSegment.match(/^(\w+)-runtime$/);
1477
- const layer = match && match[1];
1478
- if (availableExtensions.has(ext) && layer && layer.length > 0) {
1479
- return baseName + '-' + layer;
1480
- }
1481
- }
1482
- return undefined;
1483
- }
1484
1577
  // dependencyGraphMap: Map<subModuleId, Set<entryParentId>>
1485
1578
  function createSplitChunks(dependencyGraphMap, entryFiles) {
1486
1579
  // If there's existing chunk being splitted, and contains a layer { <id>: <chunkGroup> }
@@ -1493,15 +1586,6 @@ function createSplitChunks(dependencyGraphMap, entryFiles) {
1493
1586
  const { isEntry } = moduleInfo;
1494
1587
  const moduleMeta = moduleInfo.meta;
1495
1588
  const moduleLayer = getModuleLayer(moduleMeta);
1496
- if (!isEntry) {
1497
- const cachedCustomModuleLayer = splitChunksGroupMap.get(id);
1498
- if (cachedCustomModuleLayer) return cachedCustomModuleLayer;
1499
- const customModuleLayer = getCustomModuleLayer(id);
1500
- if (customModuleLayer) {
1501
- splitChunksGroupMap.set(id, customModuleLayer);
1502
- return customModuleLayer;
1503
- }
1504
- }
1505
1589
  // Collect the sub modules of the entry, if they're having layer, and the same layer with the entry, push them to the dependencyGraphMap.
1506
1590
  if (isEntry) {
1507
1591
  const subModuleIds = ctx.getModuleIds();
@@ -1821,7 +1905,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, ...options } = {}) {
1821
1905
  }
1822
1906
  }
1823
1907
  }
1824
- const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, inputFile);
1908
+ const entries = await collectEntriesFromParsedExports(cwd, parsedExportsInfo, pkg, inputFile);
1825
1909
  const hasTypeScriptFiles = Object.values(entries).some((entry)=>isTypescriptFile(entry.source));
1826
1910
  if (hasTypeScriptFiles && !hasTsConfig) {
1827
1911
  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.0",
3
+ "version": "6.0.0-beta.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": {