bunchee 6.0.3 → 6.1.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
@@ -22,28 +22,45 @@ It uses the standard exports configuration in `package.json` as the only source
22
22
  ### Installation
23
23
 
24
24
  ```sh
25
- npm install --save-dev bunchee
25
+ npm install --save-dev bunchee typescript
26
26
  ```
27
27
 
28
- If you're using TypeScript
28
+ ### Configuration
29
+
30
+ Create entry files of your library and `package.json`.
29
31
 
30
32
  ```sh
31
- npm install --save-dev bunchee typescript
33
+ cd ./coffee
34
+ mkdir src && touch ./src/index.ts && touch package.json
32
35
  ```
33
36
 
34
- ### Configuration
37
+ Add the exports in `package.json`.
35
38
 
36
- Create your library entry file and package.json.
39
+ ```json
40
+ {
41
+ "name": "coffee",
42
+ "type": "module",
43
+ "main": "./dist/index.js",
44
+ "scripts": {
45
+ "build": "bunchee"
46
+ }
47
+ }
48
+ ```
49
+
50
+ #### Build
37
51
 
38
52
  ```sh
39
- cd ./my-lib
40
- mkdir src && touch ./src/index.ts
53
+ npm run build
41
54
  ```
42
55
 
43
- #### Build
56
+ Then files in `src` folders will be treated as entry files and match the export names in package.json.
57
+ Simply like Node.js module resolution, each export name will match the file in `src/` directory.
58
+
59
+ For example:
44
60
 
45
- Then files in `src` folders will be treated as entry files and match the export names in package.json. For example:
46
- `src/index.ts` will match the exports name `"."` or the only main export.
61
+ - `src/index.ts` will match the exports name `"."` or the only main export.
62
+ - `src/lite.ts` will match the exports name `"./lite"`.
63
+ - `src/react/index.ts` will match the exports name `"./react"`.
47
64
 
48
65
  Now just run `npm run build` (or `pnpm build` / `yarn build`) if you're using these package managers, `bunchee` will find the entry files and build them.
49
66
  The output format will based on the exports condition and also the file extension. Given an example:
@@ -69,16 +86,17 @@ npm exec bunchee prepare
69
86
  Or you can checkout the following cases to configure your package.json.
70
87
 
71
88
  <details>
72
- <summary> JavaScript</summary>
89
+ <summary>JavaScript ESModule</summary>
73
90
 
74
91
  Then use use the [exports field in package.json](https://nodejs.org/api/packages.html#exports-sugar) to configure different conditions and leverage the same functionality as other bundlers, such as webpack. The exports field allows you to define multiple conditions.
75
92
 
76
93
  ```json
77
94
  {
78
95
  "files": ["dist"],
96
+ "type": "module",
79
97
  "exports": {
80
- "import": "./dist/es/index.mjs",
81
- "require": "./dist/cjs/index.js"
98
+ ".": "./dist/es/index.js",
99
+ "./react": "./dist/es/react.js"
82
100
  },
83
101
  "scripts": {
84
102
  "build": "bunchee"
@@ -91,19 +109,21 @@ Then use use the [exports field in package.json](https://nodejs.org/api/packages
91
109
  <details>
92
110
  <summary>TypeScript</summary>
93
111
 
94
- If you're build a TypeScript library, separate the types from the main entry file and specify the types path in package.json. When you're using `.mjs` or `.cjs` extensions with TypeScript and modern module resolution (above node16), TypeScript will require specific type declaration files like `.d.mts` or `.d.cts` to match the extension. `bunchee` can automatically generate them to match the types to match the condition and extensions. One example is to configure your exports like this in package.json:
112
+ If you're build a TypeScript library, separate the types from the main entry file and specify the types path in package.json. Types exports need to stay on the top of each export with `types` condition, and you can use `default` condition for the JS bundle file.
95
113
 
96
114
  ```json
97
115
  {
98
116
  "files": ["dist"],
117
+ "type": "module",
118
+ "main": "./dist/index.js",
99
119
  "exports": {
100
- "import": {
101
- "types": "./dist/es/index.d.mts",
102
- "default": "./dist/es/index.mjs"
120
+ ".": {
121
+ "types": "./dist/index.d.ts",
122
+ "default": "./dist/index.js"
103
123
  },
104
- "require": {
105
- "types": "./dist/cjs/index.d.ts",
106
- "default": "./dist/cjs/index.js"
124
+ "./react": {
125
+ "types": "./dist/react/index.d.ts",
126
+ "default": "./dist/react/index.js"
107
127
  }
108
128
  },
109
129
  "scripts": {
@@ -118,20 +138,23 @@ If you're build a TypeScript library, separate the types from the main entry fil
118
138
  <summary>Hybrid (CJS & ESM) Module Resolution with TypeScript</summary>
119
139
  If you're using TypeScript with Node 10 and Node 16 module resolution, you can use the `types` field in package.json to specify the types path. Then `bunchee` will generate the types file with the same extension as the main entry file.
120
140
 
141
+ _NOTE_: When you're using `.mjs` or `.cjs` extensions with TypeScript and modern module resolution (above node16), TypeScript will require specific type declaration files like `.d.mts` or `.d.cts` to match the extension. `bunchee` can automatically generate them to match the types to match the condition and extensions.
142
+
121
143
  ```json
122
144
  {
123
145
  "files": ["dist"],
124
- "main": "./dist/cjs/index.js",
125
- "module": "./dist/es/index.mjs",
126
- "types": "./dist/cjs/index.d.ts",
146
+ "type": "module",
147
+ "main": "./dist/index.js",
148
+ "module": "./dist/index.js",
149
+ "types": "./dist/index.d.ts",
127
150
  "exports": {
128
151
  "import": {
129
- "types": "./dist/es/index.d.ts",
130
- "default": "./dist/es/index.js"
152
+ "types": "./dist/index.d.ts",
153
+ "default": "./dist/index.js"
131
154
  },
132
155
  "require": {
133
- "types": "./dist/cjs/index.d.cts",
134
- "default": "./dist/cjs/index.cjs"
156
+ "types": "./dist/index.d.cts",
157
+ "default": "./dist/index.cjs"
135
158
  }
136
159
  },
137
160
  "scripts": {
@@ -176,10 +199,11 @@ Assuming you have default export package as `"."` and subpath export `"./lite"`
176
199
  "scripts": {
177
200
  "build": "bunchee"
178
201
  },
202
+ "type": "module",
179
203
  "exports": {
180
204
  "./lite": "./dist/lite.js",
181
205
  ".": {
182
- "import": "./dist/index.mjs",
206
+ "import": "./dist/index.js",
183
207
  "require": "./dist/index.cjs"
184
208
  }
185
209
  }
package/dist/bin/cli.js CHANGED
@@ -74,6 +74,7 @@ const DEFAULT_TS_CONFIG = {
74
74
  }
75
75
  };
76
76
  const BINARY_TAG = '$binary';
77
+ const PRIVATE_GLOB_PATTERN = '**/_*/**';
77
78
 
78
79
  function getDefaultExportFromCjs (x) {
79
80
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -158,6 +159,36 @@ function posixRelativify(path) {
158
159
  return path.startsWith('.') ? path : `./${path}`;
159
160
  }
160
161
 
162
+ // Example: ./src/util/foo.development.ts -> foo.development
163
+ // Example: ./src/util/foo.react-server.ts -> foo.react-server
164
+ const baseNameWithoutExtension = (filePath)=>{
165
+ return path__default.default.basename(filePath, path__default.default.extname(filePath));
166
+ };
167
+ function validateEntryFiles(entryFiles) {
168
+ const fileBasePaths = new Set();
169
+ const duplicatePaths = new Set();
170
+ for (const filePath of entryFiles){
171
+ // Check if there are multiple files with the same base name
172
+ const filePathWithoutExt = filePath.slice(0, -path__default.default.extname(filePath).length).replace(/\\/g, '/');
173
+ const segments = filePathWithoutExt.split('/');
174
+ const lastSegment = segments.pop() || '';
175
+ if (lastSegment !== 'index' && lastSegment !== '') {
176
+ segments.push(lastSegment);
177
+ }
178
+ const fileBasePath = segments.join('/');
179
+ if (fileBasePaths.has(fileBasePath)) {
180
+ duplicatePaths.add(// Add a dot if the base name is empty, 'foo' -> './foo', '' -> '.'
181
+ './' + filePath.replace(/\\/g, '/'));
182
+ }
183
+ fileBasePaths.add(fileBasePath);
184
+ }
185
+ if (duplicatePaths.size > 0) {
186
+ throw new Error(`Conflicted entry files found for entries: ${[
187
+ ...duplicatePaths
188
+ ].join(', ')}`);
189
+ }
190
+ }
191
+
161
192
  function exit(err) {
162
193
  logger.error(err);
163
194
  process.exit(1);
@@ -188,8 +219,6 @@ const getMainFieldExportType = (pkg)=>{
188
219
  const mainExportType = isEsmPkg && pkg.main ? hasCjsExtension(pkg.main) ? 'require' : 'import' : 'require';
189
220
  return mainExportType;
190
221
  };
191
- // TODO: add unit test
192
- const baseNameWithoutExtension = (filename)=>path__default.default.basename(filename, path__default.default.extname(filename));
193
222
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
194
223
  function joinRelativePath(...segments) {
195
224
  let result = path__default.default.join(...segments);
@@ -612,7 +641,7 @@ function lint$1(pkg) {
612
641
  }
613
642
  }
614
643
 
615
- var version = "6.0.3";
644
+ var version = "6.1.0";
616
645
 
617
646
  async function writeDefaultTsconfig(tsConfigPath) {
618
647
  await fs.promises.writeFile(tsConfigPath, JSON.stringify(DEFAULT_TS_CONFIG, null, 2), 'utf-8');
@@ -678,7 +707,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
678
707
  ;
679
708
  const dirPath = path__default.default.join(sourceFolderPath, dirName);
680
709
  // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
681
- const globalPatterns = [
710
+ const entryFilesPatterns = [
682
711
  `${baseName}.{${[
683
712
  ...availableExtensions
684
713
  ].join(',')}}`,
@@ -696,12 +725,13 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
696
725
  ...availableExtensions
697
726
  ].join(',')}}`
698
727
  ];
699
- const files = await glob.glob(globalPatterns, {
728
+ const entryFiles = await glob.glob(entryFilesPatterns, {
700
729
  cwd: dirPath,
701
730
  nodir: true,
702
- ignore: '**/_*'
731
+ ignore: PRIVATE_GLOB_PATTERN
703
732
  });
704
- for (const file of files){
733
+ validateEntryFiles(entryFiles);
734
+ for (const file of entryFiles){
705
735
  const ext = path__default.default.extname(file).slice(1);
706
736
  if (!availableExtensions.has(ext) || isTestFile(file)) continue;
707
737
  const sourceFileAbsolutePath = path__default.default.join(dirPath, file);
@@ -746,12 +776,12 @@ async function collectSourceEntries(sourceFolderPath) {
746
776
  const binMatches = await glob.glob(binPattern, {
747
777
  cwd: sourceFolderPath,
748
778
  nodir: true,
749
- ignore: '**/_*'
779
+ ignore: PRIVATE_GLOB_PATTERN
750
780
  });
751
781
  const srcMatches = await glob.glob(srcPattern, {
752
782
  cwd: sourceFolderPath,
753
783
  nodir: true,
754
- ignore: '**/_*'
784
+ ignore: PRIVATE_GLOB_PATTERN
755
785
  });
756
786
  for (const file of binMatches){
757
787
  // convert relative path to export path
package/dist/index.js CHANGED
@@ -241,11 +241,42 @@ const DEFAULT_TS_CONFIG = {
241
241
  }
242
242
  };
243
243
  const BINARY_TAG = '$binary';
244
+ const PRIVATE_GLOB_PATTERN = '**/_*/**';
244
245
 
245
246
  function posixRelativify(path) {
246
247
  return path.startsWith('.') ? path : `./${path}`;
247
248
  }
248
249
 
250
+ // Example: ./src/util/foo.development.ts -> foo.development
251
+ // Example: ./src/util/foo.react-server.ts -> foo.react-server
252
+ const baseNameWithoutExtension = (filePath)=>{
253
+ return path__default.default.basename(filePath, path__default.default.extname(filePath));
254
+ };
255
+ function validateEntryFiles(entryFiles) {
256
+ const fileBasePaths = new Set();
257
+ const duplicatePaths = new Set();
258
+ for (const filePath of entryFiles){
259
+ // Check if there are multiple files with the same base name
260
+ const filePathWithoutExt = filePath.slice(0, -path__default.default.extname(filePath).length).replace(/\\/g, '/');
261
+ const segments = filePathWithoutExt.split('/');
262
+ const lastSegment = segments.pop() || '';
263
+ if (lastSegment !== 'index' && lastSegment !== '') {
264
+ segments.push(lastSegment);
265
+ }
266
+ const fileBasePath = segments.join('/');
267
+ if (fileBasePaths.has(fileBasePath)) {
268
+ duplicatePaths.add(// Add a dot if the base name is empty, 'foo' -> './foo', '' -> '.'
269
+ './' + filePath.replace(/\\/g, '/'));
270
+ }
271
+ fileBasePaths.add(fileBasePath);
272
+ }
273
+ if (duplicatePaths.size > 0) {
274
+ throw new Error(`Conflicted entry files found for entries: ${[
275
+ ...duplicatePaths
276
+ ].join(', ')}`);
277
+ }
278
+ }
279
+
249
280
  function exit(err) {
250
281
  logger.error(err);
251
282
  process.exit(1);
@@ -325,8 +356,6 @@ const getMainFieldExportType = (pkg)=>{
325
356
  const mainExportType = isEsmPkg && pkg.main ? hasCjsExtension(pkg.main) ? 'require' : 'import' : 'require';
326
357
  return mainExportType;
327
358
  };
328
- // TODO: add unit test
329
- const baseNameWithoutExtension = (filename)=>path__default.default.basename(filename, path__default.default.extname(filename));
330
359
  const isTestFile = (filename)=>/\.(test|spec)$/.test(baseNameWithoutExtension(filename));
331
360
  function joinRelativePath(...segments) {
332
361
  let result = path__default.default.join(...segments);
@@ -752,7 +781,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
752
781
  ;
753
782
  const dirPath = path__default.default.join(sourceFolderPath, dirName);
754
783
  // Match <name>{,/index}.{<ext>,<runtime>.<ext>}
755
- const globalPatterns = [
784
+ const entryFilesPatterns = [
756
785
  `${baseName}.{${[
757
786
  ...availableExtensions
758
787
  ].join(',')}}`,
@@ -770,12 +799,13 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
770
799
  ...availableExtensions
771
800
  ].join(',')}}`
772
801
  ];
773
- const files = await glob.glob(globalPatterns, {
802
+ const entryFiles = await glob.glob(entryFilesPatterns, {
774
803
  cwd: dirPath,
775
804
  nodir: true,
776
- ignore: '**/_*'
805
+ ignore: PRIVATE_GLOB_PATTERN
777
806
  });
778
- for (const file of files){
807
+ validateEntryFiles(entryFiles);
808
+ for (const file of entryFiles){
779
809
  const ext = path__default.default.extname(file).slice(1);
780
810
  if (!availableExtensions.has(ext) || isTestFile(file)) continue;
781
811
  const sourceFileAbsolutePath = path__default.default.join(dirPath, file);
@@ -826,18 +856,16 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
826
856
  }
827
857
  }
828
858
  // Search private shared module files which are not in the parsedExportsInfo, but start with _.
829
- // e.g. _utils.ts, _utils/index.ts
830
- // e.g. _utils.development.ts, _utils/index.development.js
831
- const privatePattern = [
832
- `**/_*{,/index}.{${[
833
- ...availableExtensions
834
- ].join(',')}}`,
835
- `**/_*{,/index}.{${[
836
- ...runtimeExportConventions
837
- ].join(',')}}.{${[
838
- ...availableExtensions
839
- ].join(',')}}`
840
- ];
859
+ // Leading underscore: e.g. _utils.ts, _utils/index.ts
860
+ // Segment contains leading underscore: e.g. a/_b/_c.ts, a/b/_c/index.ts
861
+ // Contains special suffix: e.g. _utils.development.ts, _utils/index.development.js
862
+ const suffixPattern = [
863
+ ...runtimeExportConventions
864
+ ].join(',');
865
+ const extPattern = [
866
+ ...availableExtensions
867
+ ].join(',');
868
+ const privatePattern = `**/_*{,/*}{,{.${suffixPattern}}}.{${extPattern}}`;
841
869
  const privateFiles = await glob.glob(privatePattern, {
842
870
  cwd: sourceFolderPath,
843
871
  nodir: true
@@ -855,6 +883,7 @@ async function collectSourceEntriesByExportPath(sourceFolderPath, originalSubpat
855
883
  const condPart = isSpecialExport ? specialExportType + '.' : '';
856
884
  // Map private shared files to the dist directory
857
885
  // e.g. ./_utils.ts -> ./dist/_utils.js
886
+ // TODO: improve the logic to only generate the required files, not all possible files
858
887
  const privateExportInfo = [
859
888
  [
860
889
  posixRelativify(path.posix.join('./dist', exportPath + (isEsmPkg ? '.js' : '.mjs'))),
@@ -1526,9 +1555,7 @@ async function buildInputConfig(entry, bundleConfig, exportCondition, buildConte
1526
1555
  return externals.some((name)=>id === name || id.startsWith(name + '/'));
1527
1556
  },
1528
1557
  plugins,
1529
- treeshake: {
1530
- propertyReadSideEffects: false
1531
- },
1558
+ treeshake: 'recommended',
1532
1559
  onwarn (warning, warn) {
1533
1560
  const code = warning.code || '';
1534
1561
  // Some may not have types, like CLI binary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "6.0.3",
3
+ "version": "6.1.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",
@@ -77,15 +77,18 @@
77
77
  "@types/jest": "29.0.0",
78
78
  "@types/node": "^22.9.3",
79
79
  "@types/picomatch": "^3.0.1",
80
+ "@types/react": "19.0.1",
80
81
  "@types/yargs": "^17.0.33",
81
82
  "bunchee": "link:./",
82
83
  "cross-env": "^7.0.3",
83
84
  "husky": "^9.0.11",
84
85
  "jest": "29.0.1",
85
86
  "lint-staged": "^15.2.2",
87
+ "next": "^15.0.4",
86
88
  "picocolors": "^1.0.0",
87
89
  "prettier": "^3.0.0",
88
- "react": "^18.3.1",
90
+ "react": "^19.0.0",
91
+ "react-dom": "^19.0.0",
89
92
  "typescript": "^5.6.2"
90
93
  },
91
94
  "lint-staged": {
@@ -114,6 +117,8 @@
114
117
  "test:ci": "pnpm test -- --ci",
115
118
  "test:update": "TEST_UPDATE_SNAPSHOT=1 pnpm test",
116
119
  "test:post": "cross-env POST_BUILD=1 pnpm jest test/compile.test.ts test/integration.test.ts",
120
+ "docs:dev": "next dev docs",
121
+ "docs:build": "next build docs",
117
122
  "clean": "rm -rf ./dist",
118
123
  "typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit",
119
124
  "prepare-release": "pnpm clean && pnpm build",