bunchee 5.0.0-beta.1 → 5.0.0-beta.3

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
@@ -14,7 +14,7 @@
14
14
  </a>
15
15
  </p>
16
16
 
17
- Bunchee makes bundling your library into one file effortless, with zero configuration required. It is built on top of Rollup and SWC ⚡️, allowing you to focus on writing code and generating multiple module types (CommonJS, ESModules) simultaneously.
17
+ **bunchee** is a zero configuration bundler makes bundling JS/tS library effortless. It's built on top of Rollup and SWC ⚡️, allowing you to focus on writing code and generating multiple bundles (CommonJS or ESModule) at the same time.
18
18
  It uses the standard exports configuration in `package.json` as the only source of truth, and uses entry file conventions to match your exports and build them into bundles.
19
19
 
20
20
  ## Quick Start
@@ -44,7 +44,7 @@ mkdir src && touch ./src/index.ts
44
44
 
45
45
  ```sh
46
46
  # Use bunchee to prepare package.json configuration
47
- npm bunchee --prepare
47
+ npm exec bunchee --prepare
48
48
  # "If you're using other package manager such as pnpm"
49
49
  # pnpm bunchee --prepare
50
50
 
@@ -252,6 +252,63 @@ 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
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.
258
+
259
+ <details>
260
+ <summary>Shared Utils Example</summary>
261
+
262
+ ```js
263
+ // src/util.shared-runtime.js
264
+ export function sharedUtil() {
265
+ /* ... */
266
+ }
267
+ ```
268
+
269
+ Then you can use them in different entry files:
270
+
271
+ ```js
272
+ // src/index.js
273
+ import { sharedUtil } from './util.shared-runtime'
274
+ ```
275
+
276
+ ```js
277
+ // src/lite.js
278
+ import { sharedUtil } from './util.shared-runtime'
279
+ ```
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.
282
+
283
+ </details>
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.
286
+
287
+ <details>
288
+ <summary>Shared Runtime Module Example</summary>
289
+
290
+ ```js
291
+ 'use client'
292
+ // src/app-context.shared-runtime.js
293
+ export const AppContext = React.createContext(null)
294
+ ```
295
+
296
+ Then you can use them in different entry files:
297
+
298
+ ```js
299
+ // src/index.js
300
+ import { AppContext } from './app-context.shared-runtime'
301
+ ```
302
+
303
+ ```js
304
+ // src/index.react-server.js
305
+ import { AppContext } from './app-context.shared-runtime'
306
+ ```
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.
309
+
310
+ </details>
311
+
255
312
  ### CLI
256
313
 
257
314
  #### CLI Options
package/dist/bin/cli.js CHANGED
@@ -189,9 +189,10 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
189
189
  // End of searching, export value is file path.
190
190
  // <export key>: <export value> (string)
191
191
  if (typeof exportValue === 'string') {
192
- exportTypes.add(exportKey.startsWith('./') ? 'default' : exportKey);
192
+ const composedTypes = new Set(exportTypes);
193
+ composedTypes.add(exportKey.startsWith('.') ? 'default' : exportKey);
193
194
  const exportInfo = exportToDist.get(currentPath);
194
- const exportCondition = Array.from(exportTypes).join('.');
195
+ const exportCondition = Array.from(composedTypes).join('.');
195
196
  if (!exportInfo) {
196
197
  const outputConditionPair = [
197
198
  exportValue,
@@ -254,6 +255,9 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
254
255
  const exportTypes = new Set();
255
256
  const isExportPath = exportKey.startsWith('.');
256
257
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
258
+ if (!isExportPath) {
259
+ exportTypes.add(exportKey);
260
+ }
257
261
  collectExportPath(exportValue, exportKey, childPath, exportTypes, exportToDist);
258
262
  }
259
263
  }
@@ -279,14 +283,15 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
279
283
  ]);
280
284
  }
281
285
  }
282
- if (pkg.main || pkg.module) {
286
+ // Handle package.json global exports fields
287
+ if (pkg.main || pkg.module || pkg.types) {
283
288
  const mainExportPath = pkg.main;
284
289
  const moduleExportPath = pkg.module;
285
290
  const typesEntryPath = pkg.types;
286
291
  const existingExportInfo = exportToDist.get('.');
287
292
  exportToDist.set('.', [
288
293
  ...existingExportInfo || [],
289
- [
294
+ Boolean(mainExportPath) && [
290
295
  mainExportPath,
291
296
  getMainFieldExportType(pkg)
292
297
  ],
@@ -345,8 +350,7 @@ function lint$1(pkg) {
345
350
  if (hasCjsExtension(exports)) {
346
351
  state.badMainExport = true;
347
352
  }
348
- }
349
- if (typeof exports !== 'object') {
353
+ } else if (typeof exports !== 'object') {
350
354
  state.invalidExportsFieldType = true;
351
355
  } else {
352
356
  parsedExports.forEach((outputPairs)=>{
@@ -384,8 +388,7 @@ function lint$1(pkg) {
384
388
  if (path__default.default.extname(exports) === '.mjs') {
385
389
  state.badMainExport = true;
386
390
  }
387
- }
388
- if (typeof exports !== 'object') {
391
+ } else if (typeof exports !== 'object') {
389
392
  state.invalidExportsFieldType = true;
390
393
  } else {
391
394
  parsedExports.forEach((outputPairs)=>{
@@ -453,7 +456,7 @@ function lint$1(pkg) {
453
456
  }
454
457
  }
455
458
 
456
- var version = "5.0.0-beta.1";
459
+ var version = "5.0.0-beta.3";
457
460
 
458
461
  function relativify(path) {
459
462
  return path.startsWith('.') ? path : `./${path}`;
@@ -495,7 +498,7 @@ function getExportTypeFromExportTypes(types) {
495
498
  new Set(types).forEach((value)=>{
496
499
  if (specialExportConventions.has(value)) {
497
500
  exportType = value;
498
- } else if (value === 'import' || value === 'require') {
501
+ } else if (value === 'import' || value === 'require' || value === 'types') {
499
502
  exportType = value;
500
503
  }
501
504
  });
package/dist/index.js CHANGED
@@ -601,9 +601,10 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
601
601
  // End of searching, export value is file path.
602
602
  // <export key>: <export value> (string)
603
603
  if (typeof exportValue === 'string') {
604
- exportTypes.add(exportKey.startsWith('./') ? 'default' : exportKey);
604
+ const composedTypes = new Set(exportTypes);
605
+ composedTypes.add(exportKey.startsWith('.') ? 'default' : exportKey);
605
606
  const exportInfo = exportToDist.get(currentPath);
606
- const exportCondition = Array.from(exportTypes).join('.');
607
+ const exportCondition = Array.from(composedTypes).join('.');
607
608
  if (!exportInfo) {
608
609
  const outputConditionPair = [
609
610
  exportValue,
@@ -666,6 +667,9 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
666
667
  const exportTypes = new Set();
667
668
  const isExportPath = exportKey.startsWith('.');
668
669
  const childPath = isExportPath ? joinRelativePath(currentPath, exportKey) : currentPath;
670
+ if (!isExportPath) {
671
+ exportTypes.add(exportKey);
672
+ }
669
673
  collectExportPath(exportValue, exportKey, childPath, exportTypes, exportToDist);
670
674
  }
671
675
  }
@@ -691,14 +695,15 @@ function collectExportPath(exportValue, exportKey, currentPath, exportTypes, exp
691
695
  ]);
692
696
  }
693
697
  }
694
- if (pkg.main || pkg.module) {
698
+ // Handle package.json global exports fields
699
+ if (pkg.main || pkg.module || pkg.types) {
695
700
  const mainExportPath = pkg.main;
696
701
  const moduleExportPath = pkg.module;
697
702
  const typesEntryPath = pkg.types;
698
703
  const existingExportInfo = exportToDist.get('.');
699
704
  exportToDist.set('.', [
700
705
  ...existingExportInfo || [],
701
- [
706
+ Boolean(mainExportPath) && [
702
707
  mainExportPath,
703
708
  getMainFieldExportType(pkg)
704
709
  ],
@@ -729,12 +734,21 @@ function isCjsExportName(pkg, exportCondition, ext) {
729
734
  const isNotEsmExportName = !isEsmExportName(exportCondition, ext);
730
735
  return !isESModule && isNotEsmExportName && (ext !== 'mjs' || isCjsCondition) || ext === 'cjs';
731
736
  }
732
- function getExportsDistFilesOfCondition(pkg, parsedExportCondition, cwd) {
737
+ function getFileExportType(composedTypes) {
738
+ return composedTypes.split('.').pop();
739
+ }
740
+ function getExportsDistFilesOfCondition(pkg, parsedExportCondition, cwd, dts) {
733
741
  const dist = [];
734
742
  const exportConditionNames = Object.keys(parsedExportCondition.export);
735
743
  const uniqueFiles = new Set();
736
744
  for (const exportCondition of exportConditionNames){
737
- if (exportCondition === 'types') {
745
+ const exportType = getFileExportType(exportCondition);
746
+ // Filter out non-types field when generating types jobs
747
+ if (dts && exportType !== 'types') {
748
+ continue;
749
+ }
750
+ // Filter out types field when generating asset jobs
751
+ if (!dts && exportType === 'types') {
738
752
  continue;
739
753
  }
740
754
  const filePath = parsedExportCondition.export[exportCondition];
@@ -911,7 +925,7 @@ function getExportTypeFromExportTypes(types) {
911
925
  new Set(types).forEach((value)=>{
912
926
  if (specialExportConventions.has(value)) {
913
927
  exportType = value;
914
- } else if (value === 'import' || value === 'require') {
928
+ } else if (value === 'import' || value === 'require' || value === 'types') {
915
929
  exportType = value;
916
930
  }
917
931
  });
@@ -1018,7 +1032,9 @@ async function collectSourceEntries(sourceFolderPath) {
1018
1032
  }
1019
1033
 
1020
1034
  const swcMinifyOptions = {
1021
- compress: true,
1035
+ compress: {
1036
+ directives: false
1037
+ },
1022
1038
  format: {
1023
1039
  comments: 'some'
1024
1040
  },
@@ -1189,7 +1205,7 @@ async function buildInputConfig(entry, bundleConfig, exportCondition, buildConte
1189
1205
  if (dts && code === 'EMPTY_BUNDLE') return;
1190
1206
  if (disabledWarnings.has(code)) return;
1191
1207
  // If the circular dependency warning is from node_modules, ignore it
1192
- if (code === 'CIRCULAR_DEPENDENCY' && /Circular dependency: node_modules/.test(warning.message)) {
1208
+ if (code === 'CIRCULAR_DEPENDENCY' && /Circular dependency:(\s|\S)*node_modules/.test(warning.message)) {
1193
1209
  return;
1194
1210
  }
1195
1211
  warn(warning);
@@ -1203,6 +1219,19 @@ function getModuleLater(moduleMeta) {
1203
1219
  const moduleLayer = directives[0];
1204
1220
  return moduleLayer;
1205
1221
  }
1222
+ function getCustomModuleLayer(moduleId) {
1223
+ const segments = path__default.default.basename(moduleId).split('.');
1224
+ if (segments.length >= 2) {
1225
+ const [layerSegment, ext] = segments.slice(-2);
1226
+ const baseName = segments[0];
1227
+ const match = layerSegment.match(/^(\w+)-runtime$/);
1228
+ const layer = match && match[1];
1229
+ if (availableExtensions.has(ext) && layer && layer.length > 0) {
1230
+ return baseName + '-' + layer;
1231
+ }
1232
+ }
1233
+ return undefined;
1234
+ }
1206
1235
  // dependencyGraphMap: Map<subModuleId, Set<entryParentId>>
1207
1236
  function createSplitChunks(dependencyGraphMap, entryFiles) {
1208
1237
  // If there's existing chunk being splitted, and contains a layer { <id>: <chunkGroup> }
@@ -1215,6 +1244,15 @@ function createSplitChunks(dependencyGraphMap, entryFiles) {
1215
1244
  const { isEntry } = moduleInfo;
1216
1245
  const moduleMeta = moduleInfo.meta;
1217
1246
  const moduleLayer = getModuleLater(moduleMeta);
1247
+ if (!isEntry) {
1248
+ const cachedCustomModuleLayer = splitChunksGroupMap.get(id);
1249
+ if (cachedCustomModuleLayer) return cachedCustomModuleLayer;
1250
+ const customModuleLayer = getCustomModuleLayer(id);
1251
+ if (customModuleLayer) {
1252
+ splitChunksGroupMap.set(id, customModuleLayer);
1253
+ return customModuleLayer;
1254
+ }
1255
+ }
1218
1256
  // Collect the sub modules of the entry, if they're having layer, and the same layer with the entry, push them to the dependencyGraphMap.
1219
1257
  if (isEntry) {
1220
1258
  const subModuleIds = ctx.getModuleIds();
@@ -1323,7 +1361,7 @@ async function buildConfig(bundleConfig, exportCondition, pluginContext, dts) {
1323
1361
  const { file } = bundleConfig;
1324
1362
  const { pkg, cwd } = pluginContext;
1325
1363
  const entry = exportCondition.source;
1326
- const outputExports = getExportsDistFilesOfCondition(pkg, exportCondition, cwd);
1364
+ const outputExports = getExportsDistFilesOfCondition(pkg, exportCondition, cwd, dts);
1327
1365
  // If there's nothing found, give a default output
1328
1366
  if (outputExports.length === 0 && !pkg.bin) {
1329
1367
  const isEsmPkg = isESModulePackage(pkg.type);
@@ -1335,44 +1373,52 @@ async function buildConfig(bundleConfig, exportCondition, pluginContext, dts) {
1335
1373
  });
1336
1374
  }
1337
1375
  let bundleOptions = [];
1338
- // multi outputs with specified format
1339
- // CLI output option is always prioritized
1340
1376
  if (file) {
1341
- const fallbackExport = outputExports[0];
1342
- bundleOptions = [
1343
- {
1344
- file: path.resolve(cwd, file),
1345
- format: bundleConfig.format || fallbackExport.format,
1346
- exportCondition: fallbackExport.exportCondition
1347
- }
1348
- ];
1377
+ const absoluteFile = path.resolve(cwd, file);
1378
+ const absoluteTypeFile = getExportFileTypePath(absoluteFile);
1379
+ if (dts) {
1380
+ bundleOptions = [
1381
+ {
1382
+ file: absoluteTypeFile,
1383
+ format: 'esm',
1384
+ exportCondition: 'types'
1385
+ }
1386
+ ];
1387
+ } else {
1388
+ const fallbackExport = outputExports[0];
1389
+ bundleOptions = [
1390
+ {
1391
+ file: absoluteFile,
1392
+ format: bundleConfig.format || fallbackExport.format,
1393
+ exportCondition: fallbackExport.exportCondition
1394
+ }
1395
+ ];
1396
+ }
1349
1397
  } else {
1350
- bundleOptions = outputExports.map((exportDist)=>{
1351
- return {
1352
- file: path.resolve(cwd, exportDist.file),
1353
- format: exportDist.format,
1354
- exportCondition: exportDist.exportCondition
1355
- };
1356
- });
1357
- }
1358
- if (dts) {
1359
- // types could have duplicates, dedupe them
1360
- // e.g. { types, import, .. } use the same `types` condition with all conditions
1361
- const uniqTypes = new Set();
1362
- bundleOptions.forEach((bundleOption)=>{
1363
- if (exportCondition.export.types) {
1364
- uniqTypes.add(path.resolve(cwd, exportCondition.export.types));
1365
- }
1366
- const typeForExtension = getExportFileTypePath(bundleOption.file);
1367
- uniqTypes.add(typeForExtension);
1368
- });
1369
- bundleOptions = Array.from(uniqTypes).map((typeFile)=>{
1370
- return {
1371
- file: typeFile,
1372
- format: 'esm',
1373
- exportCondition: 'types'
1374
- };
1375
- });
1398
+ // CLI output option is always prioritized
1399
+ if (dts) {
1400
+ // types could have duplicates, dedupe them
1401
+ // e.g. { types, import, .. } use the same `types` condition with all conditions
1402
+ const uniqTypes = new Set();
1403
+ outputExports.forEach((exportDist)=>{
1404
+ uniqTypes.add(path.resolve(cwd, exportDist.file));
1405
+ });
1406
+ bundleOptions = Array.from(uniqTypes).map((typeFile)=>{
1407
+ return {
1408
+ file: typeFile,
1409
+ format: 'esm',
1410
+ exportCondition: 'types'
1411
+ };
1412
+ });
1413
+ } else {
1414
+ bundleOptions = outputExports.map((exportDist)=>{
1415
+ return {
1416
+ file: path.resolve(cwd, exportDist.file),
1417
+ format: exportDist.format,
1418
+ exportCondition: exportDist.exportCondition
1419
+ };
1420
+ });
1421
+ }
1376
1422
  }
1377
1423
  const outputConfigs = bundleOptions.map(async (bundleOption)=>{
1378
1424
  const targetExportCondition = {
@@ -1394,17 +1440,21 @@ async function buildConfig(bundleConfig, exportCondition, pluginContext, dts) {
1394
1440
  const removeScope = (exportPath)=>exportPath.replace(/^@[^/]+\//, '');
1395
1441
  function createOutputState({ entries }) {
1396
1442
  const sizeStats = new Map();
1443
+ const uniqFiles = new Set();
1397
1444
  function addSize({ fileName, size, sourceFileName, exportPath }) {
1398
1445
  if (!sizeStats.has(exportPath)) {
1399
1446
  sizeStats.set(exportPath, []);
1400
1447
  }
1401
1448
  const distFilesStats = sizeStats.get(exportPath);
1402
- if (distFilesStats) {
1403
- distFilesStats.push([
1404
- fileName,
1405
- sourceFileName,
1406
- size
1407
- ]);
1449
+ if (!uniqFiles.has(fileName)) {
1450
+ uniqFiles.add(fileName);
1451
+ if (distFilesStats) {
1452
+ distFilesStats.push([
1453
+ fileName,
1454
+ sourceFileName,
1455
+ size
1456
+ ]);
1457
+ }
1408
1458
  }
1409
1459
  }
1410
1460
  const reversedMapping = new Map();
@@ -1458,9 +1508,6 @@ function normalizeExportName(exportName) {
1458
1508
  }
1459
1509
  return result;
1460
1510
  }
1461
- function getExportNameWithoutExportCondition(exportName) {
1462
- return exportName.includes('.') ? exportName.split('.')[0] : exportName;
1463
- }
1464
1511
  function logOutputState(sizeCollector) {
1465
1512
  const stats = sizeCollector.getSizeStats();
1466
1513
  if (stats.size === 0) {
@@ -1472,7 +1519,7 @@ function logOutputState(sizeCollector) {
1472
1519
  const statsArray = [
1473
1520
  ...stats.entries()
1474
1521
  ].sort(([a], [b])=>{
1475
- const comp = getExportNameWithoutExportCondition(a).length - getExportNameWithoutExportCondition(b).length;
1522
+ const comp = normalizeExportPath(a).length - normalizeExportPath(b).length;
1476
1523
  return comp === 0 ? a.localeCompare(b) : comp;
1477
1524
  });
1478
1525
  const maxLengthOfExportName = Math.max(...statsArray.map(([exportName])=>normalizeExportName(exportName).length));
@@ -1616,8 +1663,7 @@ async function bundle(cliEntryPath, { cwd: _cwd, ...options } = {}) {
1616
1663
  entriesAlias
1617
1664
  }
1618
1665
  };
1619
- const buildConfigs = await buildEntryConfig(options, buildContext, false);
1620
- const assetsJobs = buildConfigs.map((rollupConfig)=>bundleOrWatch(rollupConfig));
1666
+ const assetsJobs = (await buildEntryConfig(options, buildContext, false)).map((rollupConfig)=>bundleOrWatch(rollupConfig));
1621
1667
  const typesJobs = hasTsConfig && options.dts !== false ? (await buildEntryConfig(options, buildContext, true)).map((rollupConfig)=>bundleOrWatch(rollupConfig)) : [];
1622
1668
  const totalJobs = assetsJobs.concat(typesJobs);
1623
1669
  const result = await Promise.all(totalJobs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunchee",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "zero config bundler for js/ts/jsx libraries",
5
5
  "bin": "./dist/bin/cli.js",
6
6
  "main": "./dist/index.js",
@@ -110,7 +110,7 @@
110
110
  "test:update": "TEST_UPDATE_SNAPSHOT=1 pnpm test",
111
111
  "test:post": "POST_BUILD=1 pnpm jest test/compile.test.ts test/integration.test.ts",
112
112
  "clean": "rm -rf ./dist",
113
- "typecheck": "tsc --noEmit",
113
+ "typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit",
114
114
  "build": "tsx ./src/bin/index.ts --runtime node",
115
115
  "format": "prettier --write ."
116
116
  }
package/dist/bin/cli.d.ts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node