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 +52 -28
- package/dist/bin/cli.js +39 -9
- package/dist/index.js +48 -21
- package/package.json +7 -2
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
|
-
|
|
28
|
+
### Configuration
|
|
29
|
+
|
|
30
|
+
Create entry files of your library and `package.json`.
|
|
29
31
|
|
|
30
32
|
```sh
|
|
31
|
-
|
|
33
|
+
cd ./coffee
|
|
34
|
+
mkdir src && touch ./src/index.ts && touch package.json
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
Add the exports in `package.json`.
|
|
35
38
|
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
mkdir src && touch ./src/index.ts
|
|
53
|
+
npm run build
|
|
41
54
|
```
|
|
42
55
|
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
`src/
|
|
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>
|
|
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
|
-
"
|
|
81
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
101
|
-
"types": "./dist/
|
|
102
|
-
"default": "./dist/
|
|
120
|
+
".": {
|
|
121
|
+
"types": "./dist/index.d.ts",
|
|
122
|
+
"default": "./dist/index.js"
|
|
103
123
|
},
|
|
104
|
-
"
|
|
105
|
-
"types": "./dist/
|
|
106
|
-
"default": "./dist/
|
|
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
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
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/
|
|
130
|
-
"default": "./dist/
|
|
152
|
+
"types": "./dist/index.d.ts",
|
|
153
|
+
"default": "./dist/index.js"
|
|
131
154
|
},
|
|
132
155
|
"require": {
|
|
133
|
-
"types": "./dist/
|
|
134
|
-
"default": "./dist/
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
+
"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": "^
|
|
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",
|