bunchee 4.2.11 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,39 +35,96 @@ npm install --save-dev bunchee typescript
35
35
 
36
36
  Create your library entry file and package.json.
37
37
  ```sh
38
- cd ./my-lib && mkdir src
39
- touch ./src/index.ts
40
- touch package.json
38
+ cd ./my-lib
39
+ mkdir src && touch ./src/index.ts
41
40
  ```
42
41
 
42
+ #### Prepare
43
+
44
+ ```sh
45
+ # Use bunchee to prepare package.json configuration
46
+ npm bunchee --prepare
47
+ # "If you're using other package manager such as pnpm"
48
+ # pnpm bunchee --prepare
49
+
50
+ # "Or use with npx"
51
+ # npx bunchee@latest --prepare
52
+ ```
53
+
54
+ Or you can checkout the following cases to configure your package.json.
55
+
56
+ <details>
57
+ <summary> JavaScript</summary>
58
+
43
59
  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.
44
60
  ```json
45
61
  {
46
62
  "files": ["dist"],
47
63
  "exports": {
48
- "import": "./dist/index.mjs",
49
- "require": "./dist/index.cjs"
64
+ "import": "./dist/es/index.mjs",
65
+ "require": "./dist/cjs/index.cjs"
50
66
  },
51
67
  "scripts": {
52
68
  "build": "bunchee"
53
69
  }
54
70
  }
55
71
  ```
72
+ </details>
73
+
74
+ <details>
75
+ <summary>TypeScript</summary>
76
+
77
+ 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:
56
78
 
57
- If you want to use ESM package, change the `type` field in package.json to `module`, `bunchee` will change the output format to ESM.
58
79
  ```json
59
80
  {
60
- "type": "module",
61
81
  "files": ["dist"],
62
82
  "exports": {
63
- "import": "./dist/index.mjs",
64
- "require": "./dist/index.cjs"
83
+ "import": {
84
+ "types": "./dist/es/index.d.mts",
85
+ "default": "./dist/es/index.mjs"
86
+ },
87
+ "require": {
88
+ "types": "./dist/cjs/index.d.cts",
89
+ "default": "./dist/cjs/index.cjs"
90
+ }
65
91
  },
66
92
  "scripts": {
67
93
  "build": "bunchee"
68
94
  }
69
95
  }
70
96
  ```
97
+ </details>
98
+
99
+
100
+ <details>
101
+ <summary>Hybrid (CJS & ESM) Module Resolution with TypeScript</summary>
102
+ 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.
103
+
104
+ ```json
105
+ {
106
+ "files": ["dist"],
107
+ "main": "./dist/cjs/index.cjs",
108
+ "module": "./dist/es/index.mjs",
109
+ "types": "./dist/cjs/index.d.ts",
110
+ "exports": {
111
+ "import": {
112
+ "types": "./dist/es/index.d.mts",
113
+ "default": "./dist/es/index.mjs"
114
+ },
115
+ "require": {
116
+ "types": "./dist/cjs/index.d.cts",
117
+ "default": "./dist/cjs/index.cjs"
118
+ }
119
+ },
120
+ "scripts": {
121
+ "build": "bunchee"
122
+ }
123
+ }
124
+ ```
125
+ </details>
126
+
127
+ #### Build
71
128
 
72
129
  Then files in `src` folders will be treated as entry files and match the export names in package.json. For example:
73
130
  `src/index.ts` will match the exports name `"."` or the only main export.
@@ -78,6 +135,9 @@ The output format will based on the exports condition and also the file extensio
78
135
  - It's CommonJS for `require` and ESM for `import` based on the exports condition.
79
136
  - It's CommonJS for `.js` and ESM for `.mjs` based on the extension regardless the exports condition. Then for export condition like "node" you could choose the format with your extension.
80
137
 
138
+ > [!NOTE]
139
+ > All the `dependencies` and `peerDependencies` will be marked as external automatically and not included in the bundle. If you want to include them in the bundle, you can use the `--no-external` option.
140
+
81
141
  ## Usage
82
142
 
83
143
  ### File Conventions
@@ -122,28 +182,6 @@ Then you need to add two entry files `index.ts` and `lite.ts` in project root di
122
182
 
123
183
  It will also look up for `index.<ext>` file under the directory having the name of the export path. For example, if you have `"./lite": "./dist/lite.js"` in exports field, then it will look up for `./lite/index.js` as the entry file as well.
124
184
 
125
- ### TypeScript Declaration
126
-
127
- 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.
128
- `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:
129
-
130
- ```json
131
- {
132
- "exports": {
133
- ".": {
134
- "import": {
135
- "types": "./dist/index.d.ts",
136
- "default": "./dist/index.mjs"
137
- },
138
- "require": {
139
- "types": "./dist/index.d.ts",
140
- "default": "./dist/index.js"
141
- }
142
- }
143
- }
144
- }
145
- ```
146
-
147
185
  ### Multiple Runtime
148
186
 
149
187
  For exports condition like `react-native`, `react-server` and `edge-light` as they're special platforms, they could have different exports or different code conditions. In this case bunchee provides an override input source file convention if you want to build them as different code bundle.
@@ -242,13 +280,14 @@ bunchee ./src/index.js --runtime node --target es2019
242
280
 
243
281
  #### Specifying extra external dependencies
244
282
 
245
- If you want to mark specific dependencies as external and not include them in the bundle, use the `--external` option followed by a comma-separated list of dependency names:
283
+ By default, `bunchee` will mark all the `dependencies` and `peerDependencies` as externals so you don't need to pass them as CLI args.
284
+ But if there's any dependency that used but not in the dependency list and you want to mark as external, you can use the `--external` option to specify them.
246
285
 
247
286
  ```sh
248
- bunchee --external=dependency1,dependency2,dependency3
287
+ bunchee --external=dep1,dep2,dep3
249
288
  ```
250
289
 
251
- Replace `dependency1`, `dependency2`, and `dependency3` with the names of the dependencies you want to exclude from the bundle.
290
+ Replace `dep1`, `dep2`, and `dep3` with the names of the dependencies you want to exclude from the bundle.
252
291
 
253
292
  #### Bundling everything without external dependencies
254
293
 
@@ -322,7 +361,6 @@ This will match the export names `"foo"` and `"bar"` and will be treated as the
322
361
  {
323
362
  "exports": {
324
363
  ".": {
325
- "types": "./dist/index.d.ts",
326
364
  "import": "./dist/index.js"
327
365
  },
328
366
  "./foo": {
@@ -380,26 +418,6 @@ output
380
418
  export default "hello world"
381
419
  ```
382
420
 
383
- ### TypeScript
384
-
385
- By default bunchee includes Typescript v3.9.x inside as a dependency. If you want to use your own version, just install typescript as another dev dependency then bunchee will automatically pick it.
386
-
387
- ```sh
388
- npm i -D bunchee typescript
389
- ```
390
-
391
- Create `tsconfig.json` to specify any compiler options for TypeScript.
392
-
393
- This library requires at least TypeScript 4.1.x.
394
-
395
- Adding `"types"` or `"typing"` field in your package.json, types will be generated with that path.
396
-
397
- ```json
398
- {
399
- "types": "dist/types/index.d.ts"
400
- }
401
- ```
402
-
403
421
  ### Node.js API
404
422
 
405
423
  ```ts
package/dist/bin/cli.js CHANGED
@@ -1,16 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
  var path = require('path');
3
3
  var arg = require('arg');
4
- var fs = require('fs/promises');
4
+ var fsp = require('fs/promises');
5
5
  var require$$0 = require('tty');
6
6
  var bunchee = require('bunchee');
7
+ var fs = require('fs');
7
8
 
8
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
10
 
10
11
  var path__default = /*#__PURE__*/_interopDefault(path);
11
12
  var arg__default = /*#__PURE__*/_interopDefault(arg);
12
- var fs__default = /*#__PURE__*/_interopDefault(fs);
13
+ var fsp__default = /*#__PURE__*/_interopDefault(fsp);
13
14
  var require$$0__default = /*#__PURE__*/_interopDefault(require$$0);
15
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
16
+
17
+ const availableExtensions = [
18
+ 'js',
19
+ 'cjs',
20
+ 'mjs',
21
+ 'jsx',
22
+ 'ts',
23
+ 'tsx',
24
+ 'cts',
25
+ 'mts'
26
+ ];
27
+ const SRC = 'src';
28
+ const dtsExtensionsMap = {
29
+ js: 'd.ts',
30
+ cjs: 'd.cts',
31
+ mjs: 'd.mts'
32
+ };
33
+ const tsExtensions = [
34
+ 'ts',
35
+ 'tsx',
36
+ 'cts',
37
+ 'mts'
38
+ ];
14
39
 
15
40
  function getDefaultExportFromCjs (x) {
16
41
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -64,25 +89,29 @@ picocolors.exports.createColors = createColors;
64
89
  var picocolorsExports = picocolors.exports;
65
90
  var pc = /*@__PURE__*/ getDefaultExportFromCjs(picocolorsExports);
66
91
 
92
+ const defaultColorFn = (text)=>text;
93
+ function color(prefixColor) {
94
+ return pc.isColorSupported ? pc[prefixColor] : defaultColorFn;
95
+ }
67
96
  const logger = {
68
97
  log (...arg) {
69
- console.log(' ', ...arg);
98
+ console.log(...arg);
70
99
  },
71
100
  warn (...arg) {
72
- console.warn(' ⚠️', ...arg);
101
+ console.warn(color('yellow')('⚠️'), ...arg);
73
102
  },
74
103
  error (...arg) {
75
- console.error(' ⨯', ...arg);
104
+ console.error(color('red')('⨯'), ...arg);
76
105
  },
77
106
  info (...arg) {
78
- console.log(' ✓', ...arg);
107
+ console.log(color('green')('✓'), ...arg);
79
108
  }
80
109
  };
81
110
  function paint(prefix, prefixColor, ...arg) {
82
111
  if (pc.isColorSupported) {
83
- console.log(' ' + pc[prefixColor](prefix), ...arg);
112
+ console.log(pc[prefixColor](prefix), ...arg);
84
113
  } else {
85
- console.log(' ' + prefix, ...arg);
114
+ console.log(prefix, ...arg);
86
115
  }
87
116
  }
88
117
 
@@ -97,15 +126,19 @@ async function getPackageMeta(cwd) {
97
126
  const pkgFilePath = path__default.default.resolve(cwd, 'package.json');
98
127
  let targetPackageJson = {};
99
128
  try {
100
- targetPackageJson = JSON.parse(await fs__default.default.readFile(pkgFilePath, {
129
+ targetPackageJson = JSON.parse(await fsp__default.default.readFile(pkgFilePath, {
101
130
  encoding: 'utf-8'
102
131
  }));
103
132
  } catch (_) {}
104
133
  return targetPackageJson;
105
134
  }
135
+ function isTypescriptFile(filename) {
136
+ const ext = path__default.default.extname(filename).slice(1);
137
+ return tsExtensions.includes(ext);
138
+ }
106
139
  async function fileExists(filePath) {
107
140
  try {
108
- await fs__default.default.access(filePath);
141
+ await fsp__default.default.access(filePath);
109
142
  return true;
110
143
  } catch (err) {
111
144
  if (err.code === 'ENOENT') {
@@ -114,8 +147,211 @@ async function fileExists(filePath) {
114
147
  throw err;
115
148
  }
116
149
  }
150
+ const hasAvailableExtension = (filename)=>availableExtensions.includes(path__default.default.extname(filename).slice(1));
151
+ const baseNameWithoutExtension = (filename)=>path__default.default.basename(filename, path__default.default.extname(filename));
152
+
153
+ var version = "4.3.1";
117
154
 
118
- var version = "4.2.11";
155
+ function relativify(path) {
156
+ return path.startsWith('.') ? path : `./${path}`;
157
+ }
158
+
159
+ const DIST = 'dist';
160
+ // Output with posix style in package.json
161
+ function getDistPath(...subPaths) {
162
+ return `./${DIST}/${subPaths.join('/')}`;
163
+ }
164
+ const normalizeBaseNameToExportName = (baseName)=>{
165
+ return /^index(\.|$)/.test(baseName) ? '.' : relativify(baseName);
166
+ };
167
+ function createExportCondition(exportName, sourceFile, moduleType) {
168
+ const isTsSourceFile = isTypescriptFile(sourceFile);
169
+ let cjsExtension = 'js';
170
+ if (moduleType === 'module') {
171
+ cjsExtension = 'cjs';
172
+ }
173
+ if (isTsSourceFile) {
174
+ return {
175
+ import: {
176
+ types: getDistPath('es', `${exportName}.d.mts`),
177
+ default: getDistPath('es', `${exportName}.mjs`)
178
+ },
179
+ require: {
180
+ types: getDistPath('cjs', `${exportName}.${dtsExtensionsMap[cjsExtension]}`),
181
+ default: getDistPath('cjs', `${exportName}.${cjsExtension}`)
182
+ }
183
+ };
184
+ }
185
+ return {
186
+ import: getDistPath(`${exportName}.mjs`),
187
+ require: getDistPath(`${exportName}.${cjsExtension}`)
188
+ };
189
+ }
190
+ async function collectSourceEntries(sourceFolderPath) {
191
+ const bins = new Map();
192
+ const exportsEntries = new Map();
193
+ const entryFileDirentList = await fsp__default.default.readdir(sourceFolderPath, {
194
+ withFileTypes: true
195
+ });
196
+ for (const dirent of entryFileDirentList){
197
+ if (dirent.isDirectory()) {
198
+ if (dirent.name === 'bin') {
199
+ const binDirentList = await fsp__default.default.readdir(path__default.default.join(sourceFolderPath, dirent.name), {
200
+ withFileTypes: true
201
+ });
202
+ for (const binDirent of binDirentList){
203
+ if (binDirent.isFile()) {
204
+ const binFile = path__default.default.join(sourceFolderPath, dirent.name, binDirent.name);
205
+ const binName = baseNameWithoutExtension(binDirent.name);
206
+ if (fs__default.default.existsSync(binFile)) {
207
+ bins.set(binName, binDirent.name);
208
+ }
209
+ }
210
+ }
211
+ } else {
212
+ // Search folder/<index>.<ext> convention entries
213
+ const extensions = availableExtensions;
214
+ for (const extension of extensions){
215
+ const indexFile = path__default.default.join(sourceFolderPath, dirent.name, `index.${extension}`);
216
+ if (fs__default.default.existsSync(indexFile)) {
217
+ exportsEntries.set(dirent.name, indexFile);
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ } else if (dirent.isFile()) {
223
+ const isAvailableExtension = availableExtensions.includes(path__default.default.extname(dirent.name).slice(1));
224
+ if (isAvailableExtension) {
225
+ const baseName = baseNameWithoutExtension(dirent.name);
226
+ const isBinFile = baseName === 'bin';
227
+ if (isBinFile) {
228
+ bins.set('.', dirent.name);
229
+ } else {
230
+ if (hasAvailableExtension(dirent.name)) {
231
+ exportsEntries.set(baseName, dirent.name);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ return {
238
+ bins,
239
+ exportsEntries
240
+ };
241
+ }
242
+ function createExportConditionPair(exportName, sourceFile, moduleType) {
243
+ // <exportName>.<specialCondition>
244
+ let specialCondition;
245
+ let exportCondName;
246
+ if (exportName.indexOf('.') > 0) {
247
+ const [originExportName, specialConditionName] = exportName.split('.');
248
+ specialCondition = {
249
+ [specialConditionName]: getDistPath('es', `${originExportName}-${specialConditionName}.mjs`)
250
+ };
251
+ exportCondName = normalizeBaseNameToExportName(originExportName);
252
+ return [
253
+ exportCondName,
254
+ specialCondition
255
+ ];
256
+ }
257
+ exportCondName = normalizeBaseNameToExportName(exportName);
258
+ const exportCond = createExportCondition(exportName, sourceFile, moduleType);
259
+ return [
260
+ exportCondName,
261
+ exportCond
262
+ ];
263
+ }
264
+ async function prepare(cwd) {
265
+ const sourceFolder = path__default.default.resolve(cwd, SRC);
266
+ if (!fs__default.default.existsSync(sourceFolder)) {
267
+ logger.error(`Source folder ${sourceFolder} does not exist. Cannot proceed to configure \`exports\` field.`);
268
+ process.exit(1);
269
+ }
270
+ let hasPackageJson = false;
271
+ const pkgJsonPath = path__default.default.join(cwd, 'package.json');
272
+ let pkgJson = {};
273
+ if (fs__default.default.existsSync(pkgJsonPath)) {
274
+ hasPackageJson = true;
275
+ const pkgJsonString = await fsp__default.default.readFile(pkgJsonPath, 'utf-8');
276
+ pkgJson = JSON.parse(pkgJsonString);
277
+ }
278
+ // configure `files` field with `dist`
279
+ const files = pkgJson.files || [];
280
+ if (!files.includes(DIST)) {
281
+ files.push(DIST);
282
+ }
283
+ pkgJson.files = files;
284
+ let isUsingTs = false;
285
+ // Collect bins and exports entries
286
+ const { bins, exportsEntries } = await collectSourceEntries(sourceFolder);
287
+ const tsconfigPath = path__default.default.join(cwd, 'tsconfig.json');
288
+ if (!fs__default.default.existsSync(tsconfigPath)) {
289
+ const sourceFiles = [
290
+ ...exportsEntries.values()
291
+ ].concat([
292
+ ...bins.values()
293
+ ]);
294
+ const hasTypeScriptFiles = sourceFiles.some((filename)=>isTypescriptFile(filename));
295
+ if (hasTypeScriptFiles) {
296
+ isUsingTs = true;
297
+ await fsp__default.default.writeFile(tsconfigPath, '{}', 'utf-8');
298
+ logger.log(`Detected using TypeScript but tsconfig.json is missing, created a ${pc.blue('tsconfig.json')} for you.`);
299
+ }
300
+ }
301
+ // Configure as ESM package by default if there's no package.json
302
+ if (!hasPackageJson) {
303
+ pkgJson.type = 'module';
304
+ }
305
+ if (bins.size > 0) {
306
+ logger.log('Discovered binaries entries:');
307
+ const maxLengthOfBinName = Math.max(...Array.from(bins.keys()).map((binName)=>normalizeBaseNameToExportName(binName).length));
308
+ for (const [binName, binFile] of bins.entries()){
309
+ const spaces = ' '.repeat(Math.max(maxLengthOfBinName - normalizeBaseNameToExportName(binName).length, 0));
310
+ logger.log(` ${normalizeBaseNameToExportName(binName)}${spaces}: ${binFile}`);
311
+ }
312
+ if (bins.size === 1 && bins.has('.')) {
313
+ pkgJson.bin = getDistPath('bin', 'index.js');
314
+ } else {
315
+ pkgJson.bin = {};
316
+ for (const [binName] of bins.entries()){
317
+ pkgJson.bin[binName === '.' ? pkgJson.name : binName] = getDistPath('bin', binName + '.js');
318
+ }
319
+ }
320
+ }
321
+ if (exportsEntries.size > 0) {
322
+ logger.log('Discovered exports entries:');
323
+ const maxLengthOfExportName = Math.max(...Array.from(exportsEntries.keys()).map((exportName)=>normalizeBaseNameToExportName(exportName).length));
324
+ for (const [exportName, exportFile] of exportsEntries.entries()){
325
+ const spaces = ' '.repeat(Math.max(maxLengthOfExportName - normalizeBaseNameToExportName(exportName).length, 0));
326
+ logger.log(` ${normalizeBaseNameToExportName(exportName)}${spaces}: ${exportFile}`);
327
+ }
328
+ const pkgExports = {};
329
+ for (const [exportName, sourceFile] of exportsEntries.entries()){
330
+ const [key, value] = createExportConditionPair(exportName, sourceFile, pkgJson.type);
331
+ pkgExports[key] = {
332
+ ...value,
333
+ ...pkgExports[key]
334
+ };
335
+ }
336
+ // Configure node10 module resolution
337
+ if (exportsEntries.has('index')) {
338
+ const isESM = pkgJson.type === 'module';
339
+ const mainExport = pkgExports['.'];
340
+ const mainCondition = isESM ? 'import' : 'require';
341
+ pkgJson.main = isUsingTs ? mainExport[mainCondition].default : mainExport[mainCondition];
342
+ pkgJson.module = isUsingTs ? mainExport.import.default : mainExport.import;
343
+ if (isUsingTs) {
344
+ pkgJson.types = isESM ? mainExport.import.types : mainExport.require.types;
345
+ }
346
+ }
347
+ // Assign the properties by order: files, main, module, types, exports
348
+ if (Object.keys(pkgExports).length > 0) {
349
+ pkgJson.exports = pkgExports;
350
+ }
351
+ }
352
+ await fsp__default.default.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
353
+ logger.info('Configured `exports` in package.json');
354
+ }
119
355
 
120
356
  const helpMessage = `
121
357
  Usage: bunchee [options]
@@ -127,6 +363,7 @@ Options:
127
363
  -o, --output <file> specify output filename
128
364
  -f, --format <format> type of output (esm, amd, cjs, iife, umd, system), default: esm
129
365
  -h, --help output usage information
366
+ --prepare auto configure package.json exports for building
130
367
  --external <mod> specify an external dependency, separate by comma
131
368
  --no-external do not bundle external dependencies
132
369
  --target <target> js features target: swc target es versions. default: es2015
@@ -172,6 +409,7 @@ function parseCliArgs(argv) {
172
409
  '--env': String,
173
410
  '--external': String,
174
411
  '--no-external': Boolean,
412
+ '--prepare': Boolean,
175
413
  '-h': '--help',
176
414
  '-v': '--version',
177
415
  '-w': '--watch',
@@ -197,7 +435,8 @@ function parseCliArgs(argv) {
197
435
  runtime: args['--runtime'],
198
436
  target: args['--target'],
199
437
  external: !!args['--no-external'] ? null : args['--external'],
200
- env: args['--env']
438
+ env: args['--env'],
439
+ prepare: !!args['--prepare']
201
440
  };
202
441
  return parsedArgs;
203
442
  }
@@ -225,6 +464,9 @@ async function run(args) {
225
464
  if (args.help) {
226
465
  return help();
227
466
  }
467
+ if (args.prepare) {
468
+ return await prepare(cwd);
469
+ }
228
470
  const entry = source ? path__default.default.resolve(cwd, source) : '';
229
471
  try {
230
472
  await bunchee.bundle(entry, bundleConfig);
@@ -241,7 +483,7 @@ async function run(args) {
241
483
  return;
242
484
  }
243
485
  // build mode
244
- console.log();
486
+ logger.log();
245
487
  paint('✓', 'green', `bunchee ${version} build completed`);
246
488
  await lintPackage(cwd);
247
489
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { JscTarget } from '@swc/types';
2
2
  import { OutputOptions } from 'rollup';
3
3
 
4
+ type ExportCondition = string | {
5
+ [key: string]: ExportCondition | string;
6
+ };
4
7
  type BundleConfig = {
5
8
  file?: string;
6
9
  cwd?: string;
@@ -13,6 +16,20 @@ type BundleConfig = {
13
16
  env?: string[];
14
17
  dts?: boolean;
15
18
  runtime?: string;
19
+ pkg?: PackageMetadata;
20
+ };
21
+ type PackageMetadata = {
22
+ name?: string;
23
+ main?: string;
24
+ bin?: string | Record<string, string>;
25
+ module?: string;
26
+ type?: 'commonjs' | 'module';
27
+ dependencies?: Record<string, string>;
28
+ peerDependencies?: Record<string, string>;
29
+ peerDependenciesMeta?: Record<string, Record<string, string>>;
30
+ exports?: string | Record<string, ExportCondition>;
31
+ types?: string;
32
+ typings?: string;
16
33
  };
17
34
 
18
35
  declare function bundle(entryPath: string, { cwd: _cwd, ...options }?: BundleConfig): Promise<any>;
package/dist/index.js CHANGED
@@ -52,12 +52,11 @@ const availableExportConventions = [
52
52
  'edge-light'
53
53
  ];
54
54
  const availableESExtensionsRegex = /\.(m|c)?[jt]sx?$/;
55
- const dtsExtensionRegex = /\.d\.(m|c)?ts$/;
56
55
  const SRC = 'src';
57
- const dtsExtensions = {
58
- js: '.d.ts',
59
- cjs: '.d.cts',
60
- mjs: '.d.mts'
56
+ const dtsExtensionsMap = {
57
+ js: 'd.ts',
58
+ cjs: 'd.cts',
59
+ mjs: 'd.mts'
61
60
  };
62
61
  const disabledWarnings = new Set([
63
62
  'MIXED_EXPORTS',
@@ -120,27 +119,24 @@ picocolors.exports.createColors = createColors;
120
119
  var picocolorsExports = picocolors.exports;
121
120
  var pc = /*@__PURE__*/ getDefaultExportFromCjs(picocolorsExports);
122
121
 
122
+ const defaultColorFn = (text)=>text;
123
+ function color(prefixColor) {
124
+ return pc.isColorSupported ? pc[prefixColor] : defaultColorFn;
125
+ }
123
126
  const logger = {
124
127
  log (...arg) {
125
- console.log(' ', ...arg);
128
+ console.log(...arg);
126
129
  },
127
130
  warn (...arg) {
128
- console.warn(' ⚠️', ...arg);
131
+ console.warn(color('yellow')('⚠️'), ...arg);
129
132
  },
130
133
  error (...arg) {
131
- console.error(' ⨯', ...arg);
134
+ console.error(color('red')('⨯'), ...arg);
132
135
  },
133
136
  info (...arg) {
134
- console.log(' ✓', ...arg);
137
+ console.log(color('green')('✓'), ...arg);
135
138
  }
136
139
  };
137
- function paint(prefix, prefixColor, ...arg) {
138
- if (pc.isColorSupported) {
139
- console.log(' ' + pc[prefixColor](prefix), ...arg);
140
- } else {
141
- console.log(' ' + prefix, ...arg);
142
- }
143
- }
144
140
 
145
141
  function exit(err) {
146
142
  logger.error(err);
@@ -184,9 +180,11 @@ async function findSourceEntryFile(cwd, exportPath, exportTypeSuffix, ext) {
184
180
  return filename;
185
181
  }
186
182
  const subFolderIndexFilename = resolveSourceFile(cwd, `${exportPath}/index${exportTypeSuffix ? `.${exportTypeSuffix}` : ''}.${ext}`);
187
- if (await fileExists(subFolderIndexFilename)) {
188
- return subFolderIndexFilename;
189
- }
183
+ try {
184
+ if (await fileExists(subFolderIndexFilename)) {
185
+ return subFolderIndexFilename;
186
+ }
187
+ } catch {}
190
188
  return undefined;
191
189
  }
192
190
  // Map '.' -> './index.[ext]'
@@ -195,7 +193,7 @@ async function findSourceEntryFile(cwd, exportPath, exportTypeSuffix, ext) {
195
193
  async function getSourcePathFromExportPath(cwd, exportPath, exportType) {
196
194
  for (const ext of availableExtensions){
197
195
  // ignore package.json
198
- if (exportPath.endsWith('package.json')) return;
196
+ if (exportPath === '/package.json') return;
199
197
  if (exportPath === '.') exportPath = './index';
200
198
  // Find convention-based source file for specific export types
201
199
  // $binary represents `pkg.bin`
@@ -209,7 +207,7 @@ async function getSourcePathFromExportPath(cwd, exportPath, exportType) {
209
207
  return;
210
208
  }
211
209
  // Unlike path.basename, forcedly removing extension
212
- function filenameWithoutExtension(file) {
210
+ function filePathWithoutExtension(file) {
213
211
  return file ? file.replace(new RegExp(`${path__default.default.extname(file)}$`), '') : undefined;
214
212
  }
215
213
  const nonNullable = (n)=>Boolean(n);
@@ -610,7 +608,7 @@ const getExportTypeDist = (parsedExportCondition, cwd)=>{
610
608
  continue;
611
609
  }
612
610
  const ext = path.extname(filePath).slice(1);
613
- const typeFile = getDistPath(`${filenameWithoutExtension(filePath) || ''}${dtsExtensions[ext]}`, cwd);
611
+ const typeFile = getDistPath(`${filePathWithoutExtension(filePath) || ''}.${dtsExtensionsMap[ext]}`, cwd);
614
612
  if (existed.has(typeFile)) {
615
613
  continue;
616
614
  }
@@ -691,7 +689,7 @@ function getExportConditionDist(pkg, parsedExportCondition, cwd) {
691
689
  return dist;
692
690
  }
693
691
  function getTypeFilePath(entryFilePath, exportCondition, cwd) {
694
- const name = filenameWithoutExtension(entryFilePath);
692
+ const name = filePathWithoutExtension(entryFilePath);
695
693
  const firstDistPath = exportCondition ? Object.values(exportCondition.export)[0] : undefined;
696
694
  const exportName = (exportCondition == null ? void 0 : exportCondition.name) || 'index';
697
695
  return entryFilePath ? name + '.d.ts' : path.resolve(firstDistPath ? path.dirname(firstDistPath) : path.join(cwd, 'dist'), (exportName === '.' ? 'index' : exportName) + '.d.ts');
@@ -739,7 +737,8 @@ function getBuildEnv(envs) {
739
737
  }
740
738
  return alias;
741
739
  }
742
- async function buildInputConfig(entry, entries, pkg, options, cwd, { tsConfigPath, tsCompilerOptions }, pluginContext, dts) {
740
+ async function buildInputConfig(entry, options, buildContext, dts) {
741
+ const { entries, pkg, cwd, tsOptions: { tsConfigPath, tsCompilerOptions }, pluginContext } = buildContext;
743
742
  const hasNoExternal = options.external === null;
744
743
  var _options_external;
745
744
  const externals = hasNoExternal ? [] : [
@@ -780,7 +779,7 @@ async function buildInputConfig(entry, entries, pkg, options, cwd, { tsConfigPat
780
779
  inlineSourcesContent: false,
781
780
  isModule: true
782
781
  };
783
- const sizePlugin = pluginContext.sizeCollector.plugin(cwd);
782
+ const sizePlugin = pluginContext.outputState.plugin(cwd);
784
783
  // common plugins for both dts and ts assets that need to be processed
785
784
  const commonPlugins = [
786
785
  sizePlugin,
@@ -962,14 +961,15 @@ function createSplitChunks(dependencyGraphMap, entryFiles) {
962
961
  return;
963
962
  };
964
963
  }
965
- function buildOutputConfigs(entries, pkg, exportPaths, options, exportCondition, cwd, { tsCompilerOptions }, pluginContext, dts) {
964
+ function buildOutputConfigs(options, exportCondition, buildContext, dts) {
966
965
  const { format } = options;
966
+ const { entries, pkg, exportPaths, cwd, tsOptions: { tsCompilerOptions }, pluginContext } = buildContext;
967
967
  // Add esm mark and interop helper if esm export is detected
968
968
  const useEsModuleMark = hasEsmExport(exportPaths, tsCompilerOptions);
969
969
  const typings = getTypings(pkg);
970
970
  const file = options.file && path.resolve(cwd, options.file);
971
971
  const dtsDir = typings ? path.dirname(path.resolve(cwd, typings)) : path.resolve(cwd, 'dist');
972
- const name = filenameWithoutExtension(file);
972
+ const name = filePathWithoutExtension(file);
973
973
  // TODO: simplify dts file name detection
974
974
  const dtsFile = file ? file : exportCondition.export['types'] ? path.resolve(cwd, exportCondition.export['types']) : path.resolve(dtsDir, (exportCondition.name === '.' ? 'index' : exportCondition.name) + '.d.ts');
975
975
  const dtsPathConfig = {
@@ -997,10 +997,11 @@ function buildOutputConfigs(entries, pkg, exportPaths, options, exportCondition,
997
997
  entryFileNames: path.basename(outputFile)
998
998
  };
999
999
  }
1000
- async function buildEntryConfig(entries, pkg, exportPaths, bundleConfig, cwd, tsOptions, pluginContext, dts) {
1000
+ async function buildEntryConfig(bundleConfig, pluginContext, dts) {
1001
1001
  const configs = [];
1002
+ const { entries } = pluginContext;
1002
1003
  for (const exportCondition of Object.values(entries)){
1003
- const rollupConfig = buildConfig(entries, pkg, exportPaths, bundleConfig, exportCondition, cwd, tsOptions, pluginContext, dts);
1004
+ const rollupConfig = buildConfig(bundleConfig, exportCondition, pluginContext, dts);
1004
1005
  configs.push(rollupConfig);
1005
1006
  }
1006
1007
  return await Promise.all(configs);
@@ -1042,7 +1043,9 @@ async function buildEntryConfig(entries, pkg, exportPaths, bundleConfig, cwd, ts
1042
1043
  name: entryExport,
1043
1044
  export: exportCondForType
1044
1045
  };
1045
- const entryImportPath = path__default.default.join(pkg.name || '', exportCondition.name) + (exportType ? `.${exportType}` : '');
1046
+ const nameWithExportPath = pkg.name ? path__default.default.join(pkg.name, exportCondition.name) : exportCondition.name;
1047
+ const needsDelimiter = !nameWithExportPath.endsWith('.') && exportType;
1048
+ const entryImportPath = nameWithExportPath + (needsDelimiter ? '.' : '') + exportType;
1046
1049
  entries[entryImportPath] = exportCondition;
1047
1050
  }
1048
1051
  const binaryExports = pkg.bin;
@@ -1092,47 +1095,48 @@ async function buildEntryConfig(entries, pkg, exportPaths, bundleConfig, cwd, ts
1092
1095
  await Promise.all(collectEntriesPromises);
1093
1096
  return entries;
1094
1097
  }
1095
- async function buildConfig(entries, pkg, exportPaths, bundleConfig, exportCondition, cwd, tsOptions, pluginContext, dts) {
1098
+ async function buildConfig(bundleConfig, exportCondition, pluginContext, dts) {
1096
1099
  const { file } = bundleConfig;
1100
+ const { pkg, cwd, tsOptions } = pluginContext;
1097
1101
  const useTypescript = Boolean(tsOptions.tsConfigPath);
1098
1102
  const options = {
1099
1103
  ...bundleConfig,
1100
1104
  useTypescript
1101
1105
  };
1102
1106
  const entry = exportCondition.source;
1103
- const inputOptions = await buildInputConfig(entry, entries, pkg, options, cwd, tsOptions, pluginContext, dts);
1107
+ const inputOptions = await buildInputConfig(entry, options, pluginContext, dts);
1104
1108
  const outputExports = getExportConditionDist(pkg, exportCondition, cwd);
1105
1109
  let outputConfigs = [];
1106
1110
  // Generate dts job - single config
1107
1111
  if (dts) {
1108
1112
  const typeOutputExports = getExportTypeDist(exportCondition, cwd);
1109
- outputConfigs = typeOutputExports.map((typeFile)=>buildOutputConfigs(entries, pkg, exportPaths, {
1113
+ outputConfigs = typeOutputExports.map((typeFile)=>buildOutputConfigs({
1110
1114
  ...bundleConfig,
1111
1115
  format: 'es',
1112
1116
  useTypescript,
1113
1117
  file: typeFile
1114
- }, exportCondition, cwd, tsOptions, pluginContext, dts));
1118
+ }, exportCondition, pluginContext, dts));
1115
1119
  } else {
1116
1120
  // multi outputs with specified format
1117
1121
  outputConfigs = outputExports.map((exportDist)=>{
1118
- return buildOutputConfigs(entries, pkg, exportPaths, {
1122
+ return buildOutputConfigs({
1119
1123
  ...bundleConfig,
1120
1124
  file: exportDist.file,
1121
1125
  format: exportDist.format,
1122
1126
  useTypescript
1123
- }, exportCondition, cwd, tsOptions, pluginContext, dts);
1127
+ }, exportCondition, pluginContext, dts);
1124
1128
  });
1125
1129
  // CLI output option is always prioritized
1126
1130
  if (file) {
1127
1131
  var _outputExports_;
1128
1132
  const fallbackFormat = (_outputExports_ = outputExports[0]) == null ? void 0 : _outputExports_.format;
1129
1133
  outputConfigs = [
1130
- buildOutputConfigs(entries, pkg, exportPaths, {
1134
+ buildOutputConfigs({
1131
1135
  ...bundleConfig,
1132
1136
  file,
1133
1137
  format: bundleConfig.format || fallbackFormat,
1134
1138
  useTypescript
1135
- }, exportCondition, cwd, tsOptions, pluginContext, dts)
1139
+ }, exportCondition, pluginContext, dts)
1136
1140
  ];
1137
1141
  }
1138
1142
  }
@@ -1143,7 +1147,11 @@ async function buildConfig(entries, pkg, exportPaths, bundleConfig, exportCondit
1143
1147
  };
1144
1148
  }
1145
1149
 
1146
- function createChunkSizeCollector({ entries }) {
1150
+ function relativify(path) {
1151
+ return path.startsWith('.') ? path : `./${path}`;
1152
+ }
1153
+
1154
+ function createOutputState({ entries }) {
1147
1155
  const sizeStats = new Map();
1148
1156
  function addSize({ fileName, size, sourceFileName, exportPath }) {
1149
1157
  if (!sizeStats.has(exportPath)) {
@@ -1159,8 +1167,8 @@ function createChunkSizeCollector({ entries }) {
1159
1167
  }
1160
1168
  }
1161
1169
  const reversedMapping = new Map();
1162
- Object.entries(entries).forEach(([, entry])=>{
1163
- reversedMapping.set(entry.source, entry.name || '.');
1170
+ Object.entries(entries).forEach(([resolvedExportName, entry])=>{
1171
+ reversedMapping.set(entry.source, resolvedExportName);
1164
1172
  });
1165
1173
  return {
1166
1174
  plugin: (cwd)=>{
@@ -1191,20 +1199,70 @@ function createChunkSizeCollector({ entries }) {
1191
1199
  }
1192
1200
  };
1193
1201
  }
1194
- function logSizeStats(sizeCollector) {
1202
+ function isBin(filename) {
1203
+ return filename === 'bin' || filename.startsWith('bin/');
1204
+ }
1205
+ function isTypeFile(filename) {
1206
+ return filename.endsWith('.d.ts') || filename.endsWith('.d.mts') || filename.endsWith('.d.cts');
1207
+ }
1208
+ function normalizeExportName(exportName) {
1209
+ const isBinary = isBin(exportName);
1210
+ let result = exportName;
1211
+ const isSubpathExport = exportName.includes('/');
1212
+ const isSpecialExport = exportName.includes('.');
1213
+ if (isBinary) {
1214
+ result = (exportName.replace(/bin(\/|$)/, '') || '.') + ' (bin)';
1215
+ } else if (isSubpathExport || isSpecialExport) {
1216
+ const subExportName = exportName.split('/')[1] || exportName;
1217
+ if (subExportName.includes('.') && subExportName !== '.') {
1218
+ const [originExportName, specialCondition] = subExportName.split('.');
1219
+ result = (isSubpathExport ? relativify(originExportName) : '.') + ' (' + specialCondition + ')';
1220
+ } else {
1221
+ result = isSubpathExport ? relativify(subExportName) : '.';
1222
+ }
1223
+ } else {
1224
+ result = '.';
1225
+ }
1226
+ return result;
1227
+ }
1228
+ function getExportNameWithoutExportCondition(exportName) {
1229
+ return exportName.includes('.') ? exportName.split('.')[0] : exportName;
1230
+ }
1231
+ function logOutputState(sizeCollector) {
1195
1232
  const stats = sizeCollector.getSizeStats();
1196
1233
  const allFileNameLengths = Array.from(stats.values()).flat(1).map(([filename])=>filename.length);
1197
- const maxLength = Math.max(...allFileNameLengths);
1198
- [
1234
+ const maxFilenameLength = Math.max(...allFileNameLengths);
1235
+ const statsArray = [
1199
1236
  ...stats.entries()
1200
- ].sort(([a], [b])=>a.length - b.length).forEach(([, filesList])=>{
1201
- filesList.forEach((item)=>{
1237
+ ].sort(([a], [b])=>{
1238
+ const comp = getExportNameWithoutExportCondition(a).length - getExportNameWithoutExportCondition(b).length;
1239
+ return comp === 0 ? a.localeCompare(b) : comp;
1240
+ });
1241
+ const maxLengthOfExportName = Math.max(...statsArray.map(([exportName])=>normalizeExportName(exportName).length));
1242
+ console.log(pc.underline('Exports'), ' '.repeat(Math.max(maxLengthOfExportName - 'Exports'.length, 0)), pc.underline('File'), ' '.repeat(Math.max(maxFilenameLength - 'File'.length, 0)), pc.underline('Size'));
1243
+ statsArray.forEach(([exportName, filesList])=>{
1244
+ // sort by file type, first js files then types, js/mjs/cjs are prioritized than .d.ts/.d.mts/.d.cts
1245
+ filesList.sort(([a], [b])=>{
1246
+ const aIsType = isTypeFile(a);
1247
+ const bIsType = isTypeFile(b);
1248
+ if (aIsType && bIsType) {
1249
+ return 0;
1250
+ }
1251
+ if (aIsType) {
1252
+ return 1;
1253
+ }
1254
+ if (bIsType) {
1255
+ return -1;
1256
+ }
1257
+ return 0;
1258
+ }).forEach((item, index)=>{
1202
1259
  const [filename, , size] = item;
1203
- const padding = ' '.repeat(maxLength - filename.length);
1204
- const isTypeFile = dtsExtensionRegex.test(filename);
1205
- const action = isTypeFile ? '[types]' : '[chunk]';
1260
+ const normalizedExportName = normalizeExportName(exportName);
1261
+ const prefix = index === 0 ? normalizedExportName + ' '.repeat(maxLengthOfExportName - normalizedExportName.length) : ' '.repeat(maxLengthOfExportName);
1262
+ const sizePadding = ' '.repeat(maxFilenameLength - filename.length);
1206
1263
  const prettiedSize = prettyBytes__default.default(size);
1207
- paint(' ' + action, isTypeFile ? 'blue' : 'white', `${filename}${padding} - ${prettiedSize}`);
1264
+ const isType = isTypeFile(filename);
1265
+ console.log(` ${prefix} ${pc[isType ? 'dim' : 'bold'](filename)}${sizePadding} ${prettiedSize}`);
1208
1266
  });
1209
1267
  });
1210
1268
  }
@@ -1235,7 +1293,7 @@ async function getExportables(cwd, excludeKeys) {
1235
1293
  function mapWildcard(wildcardExports, exportables) {
1236
1294
  return exportables.map((exportable)=>{
1237
1295
  const isFile = exportable.includes('.');
1238
- const filename = isFile ? filenameWithoutExtension(exportable) : exportable;
1296
+ const filename = isFile ? filePathWithoutExtension(exportable) : exportable;
1239
1297
  return {
1240
1298
  [`./${filename}`]: Object.fromEntries(Object.entries(wildcardExports['./*']).map(([key, value])=>[
1241
1299
  key,
@@ -1322,12 +1380,9 @@ async function bundle(entryPath, { cwd: _cwd, ...options } = {}) {
1322
1380
  const { input, exportName } = rollupConfig;
1323
1381
  const exportPath = getExportPath(pkg, cwd, exportName);
1324
1382
  // Log original entry file relative path
1325
- const source = typeof input.input === 'string' ? path.relative(cwd, input.input) : exportPath;
1326
- const buildMetadata = {
1327
- source
1328
- };
1383
+ typeof input.input === 'string' ? path.relative(cwd, input.input) : exportPath;
1329
1384
  if (options.watch) {
1330
- return Promise.resolve(runWatch(rollupConfig, buildMetadata));
1385
+ return Promise.resolve(runWatch(rollupConfig));
1331
1386
  }
1332
1387
  return runBundle(rollupConfig);
1333
1388
  };
@@ -1339,23 +1394,30 @@ async function bundle(entryPath, { cwd: _cwd, ...options } = {}) {
1339
1394
  return Promise.reject(err);
1340
1395
  }
1341
1396
  const entries = await collectEntries(pkg, entryPath, exportPaths, cwd);
1342
- const sizeCollector = createChunkSizeCollector({
1397
+ const sizeCollector = createOutputState({
1343
1398
  entries
1344
1399
  });
1345
1400
  const entriesAlias = getReversedAlias(entries);
1346
- const pluginContext = {
1347
- sizeCollector,
1348
- moduleDirectiveLayerMap: new Map(),
1349
- entriesAlias
1401
+ const buildContext = {
1402
+ entries,
1403
+ pkg,
1404
+ exportPaths,
1405
+ cwd,
1406
+ tsOptions: defaultTsOptions,
1407
+ pluginContext: {
1408
+ outputState: sizeCollector,
1409
+ moduleDirectiveLayerMap: new Map(),
1410
+ entriesAlias
1411
+ }
1350
1412
  };
1351
- const buildConfigs = await buildEntryConfig(entries, pkg, exportPaths, options, cwd, defaultTsOptions, pluginContext, false);
1413
+ const buildConfigs = await buildEntryConfig(options, buildContext, false);
1352
1414
  const assetsJobs = buildConfigs.map((rollupConfig)=>bundleOrWatch(rollupConfig));
1353
- const typesJobs = hasTsConfig ? (await buildEntryConfig(entries, pkg, exportPaths, options, cwd, defaultTsOptions, pluginContext, true)).map((rollupConfig)=>bundleOrWatch(rollupConfig)) : [];
1415
+ const typesJobs = hasTsConfig ? (await buildEntryConfig(options, buildContext, true)).map((rollupConfig)=>bundleOrWatch(rollupConfig)) : [];
1354
1416
  const result = await Promise.all(assetsJobs.concat(typesJobs));
1355
1417
  if (result.length === 0) {
1356
1418
  logger.warn('The "src" directory does not contain any entry files. ' + 'For proper usage, please refer to the following link: ' + 'https://github.com/huozhi/bunchee#usage');
1357
1419
  }
1358
- logSizeStats(sizeCollector);
1420
+ logOutputState(sizeCollector);
1359
1421
  return result;
1360
1422
  }
1361
1423
  function runWatch({ input, output }, metadata) {
@@ -1379,7 +1441,6 @@ function runWatch({ input, output }, metadata) {
1379
1441
  }
1380
1442
  case 'START':
1381
1443
  {
1382
- logger.log(`Start building ${metadata.source} ...`);
1383
1444
  break;
1384
1445
  }
1385
1446
  case 'END':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "4.2.11",
3
+ "version": "4.3.1",
4
4
  "description": "zero config bundler for js/ts/jsx libraries",
5
5
  "bin": "./dist/bin/cli.js",
6
6
  "main": "./dist/index.js",