@b9g/libuild 0.1.14 → 0.1.16

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.16] - 2025-01-14
6
+
7
+ ### Added
8
+ - **Code splitting for dynamic imports** - ESM builds now use ESBuild's `splitting: true` to create separate chunk files for dynamically imported modules, enabling lazy loading and reducing initial bundle size
9
+ - **Smart entry point detection** - Packages with only "." or bin exports build only index.ts as entry point, allowing subdirectory files to be chunked when dynamically imported
10
+ - **Code splitting warning** - Warns when dual-format builds have chunks, informing users that CommonJS builds cannot benefit from code splitting (CJS bundles dynamic imports inline)
11
+
12
+ ### Fixed
13
+ - **Export validation** - Invalid export paths that don't point to src/ files are now properly validated and rejected with clear error messages
14
+
15
+ ## [0.1.15] - 2025-01-14
16
+
17
+ ### Fixed
18
+ - **Support bin-only packages** - Packages with only bin/ executables and no src/ library code no longer crash. Main/module/types fields and "." export are now correctly omitted for bin-only packages, while bin exports are properly generated.
19
+
5
20
  ## [0.1.14] - 2025-01-14
6
21
 
7
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/libuild",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Zero-config library builds",
5
5
  "keywords": [
6
6
  "build",
package/src/libuild.cjs CHANGED
@@ -215370,6 +215370,8 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
215370
215370
  } else {
215371
215371
  entryName = match[1];
215372
215372
  }
215373
+ } else if (exportValue.startsWith("./") && !exportValue.includes("package.json")) {
215374
+ hasInvalidPath = true;
215373
215375
  }
215374
215376
  }
215375
215377
  } else if (typeof exportValue === "object" && exportValue !== null) {
@@ -215386,6 +215388,8 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
215386
215388
  } else {
215387
215389
  entryName = match[1];
215388
215390
  }
215391
+ } else if (importPath.startsWith("./") && !importPath.includes("package.json")) {
215392
+ hasInvalidPath = true;
215389
215393
  }
215390
215394
  }
215391
215395
  }
@@ -215505,10 +215509,12 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
215505
215509
  exports2[key] = expandExistingExport(value);
215506
215510
  }
215507
215511
  }
215508
- if (!exports2["."]) {
215509
- exports2["."] = await createExportEntry(mainEntry);
215510
- } else {
215511
- exports2["."] = expandExistingExport(exports2["."], mainEntry);
215512
+ if (mainEntry) {
215513
+ if (!exports2["."]) {
215514
+ exports2["."] = await createExportEntry(mainEntry);
215515
+ } else {
215516
+ exports2["."] = expandExistingExport(exports2["."], mainEntry);
215517
+ }
215512
215518
  }
215513
215519
  for (const entry of entries) {
215514
215520
  if (entry === "umd")
@@ -215883,15 +215889,17 @@ async function cleanPackageJSON(pkg, mainEntry, options, cwd, distDir) {
215883
215889
  if (!cleaned.type) {
215884
215890
  cleaned.type = "module";
215885
215891
  }
215886
- if (options.formats.cjs) {
215887
- cleaned.main = `src/${mainEntry}.cjs`;
215888
- }
215889
- cleaned.module = `src/${mainEntry}.js`;
215890
- if (distDir) {
215891
- const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
215892
- const exists = await fileExists(dtsPath);
215893
- if (exists) {
215894
- cleaned.types = `src/${mainEntry}.d.ts`;
215892
+ if (mainEntry) {
215893
+ if (options.formats.cjs) {
215894
+ cleaned.main = `src/${mainEntry}.cjs`;
215895
+ }
215896
+ cleaned.module = `src/${mainEntry}.js`;
215897
+ if (distDir) {
215898
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
215899
+ const exists = await fileExists(dtsPath);
215900
+ if (exists) {
215901
+ cleaned.types = `src/${mainEntry}.d.ts`;
215902
+ }
215895
215903
  }
215896
215904
  }
215897
215905
  return cleaned;
@@ -215946,8 +215954,37 @@ async function build2(cwd, save = false) {
215946
215954
  }
215947
215955
  }
215948
215956
  const binDir = Path3.join(cwd, "bin");
215949
- const srcEntries = await findEntrypoints(srcDir);
215957
+ const allSrcFiles = await findEntrypoints(srcDir);
215950
215958
  const binEntries = await findBinEntrypoints(binDir);
215959
+ let srcEntries;
215960
+ if (pkg.exports && typeof pkg.exports === "object") {
215961
+ const srcExportKeys = Object.keys(pkg.exports).filter((key) => {
215962
+ if (key === "." || key === "./package.json")
215963
+ return false;
215964
+ const val = pkg.exports[key];
215965
+ if (typeof val === "string")
215966
+ return val.includes("/src/");
215967
+ if (typeof val === "object" && val !== null) {
215968
+ return Object.values(val).some((v) => typeof v === "string" && v.includes("/src/"));
215969
+ }
215970
+ return false;
215971
+ });
215972
+ const dotExport = pkg.exports["."];
215973
+ let hasCustomMain = false;
215974
+ if (dotExport) {
215975
+ const importPath = typeof dotExport === "string" ? dotExport : dotExport.import || dotExport.default;
215976
+ if (importPath && !importPath.includes("/index.")) {
215977
+ hasCustomMain = true;
215978
+ }
215979
+ }
215980
+ if (srcExportKeys.length > 0 || hasCustomMain) {
215981
+ srcEntries = allSrcFiles;
215982
+ } else {
215983
+ srcEntries = allSrcFiles.includes("index") ? ["index"] : allSrcFiles;
215984
+ }
215985
+ } else {
215986
+ srcEntries = allSrcFiles;
215987
+ }
215951
215988
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
215952
215989
  const entries = [
215953
215990
  ...srcEntries,
@@ -216056,6 +216093,8 @@ async function build2(cwd, save = false) {
216056
216093
  format: "esm",
216057
216094
  outExtension: { ".js": ".js" },
216058
216095
  bundle: true,
216096
+ splitting: true,
216097
+ // Enable code splitting for dynamic imports
216059
216098
  minify: false,
216060
216099
  sourcemap: false,
216061
216100
  external: externalDeps,
@@ -216082,6 +216121,20 @@ async function build2(cwd, save = false) {
216082
216121
  })] : []
216083
216122
  ]
216084
216123
  });
216124
+ if (options.formats.cjs) {
216125
+ const distFiles = await FS3.readdir(distDir);
216126
+ const chunkFiles = distFiles.filter(
216127
+ (f) => f.endsWith(".js") && !f.endsWith(".d.ts") && (f.startsWith("chunk-") || f.includes("-") && !f.startsWith("package"))
216128
+ );
216129
+ if (chunkFiles.length > 0) {
216130
+ console.info(`
216131
+ \u26A0\uFE0F Code splitting detected - CommonJS build will bundle dynamic imports inline`);
216132
+ console.info(` ESM build: ${chunkFiles.length} chunk file(s) created for lazy loading`);
216133
+ console.info(` CJS build: Dynamic imports bundled inline (no chunks)`);
216134
+ console.info(` To get code splitting benefits, use ESM imports or remove "main" field
216135
+ `);
216136
+ }
216137
+ }
216085
216138
  if (binEntryPoints.length > 0) {
216086
216139
  const runtimeBanner = generateRuntimeBanner(pkg);
216087
216140
  for (const binEntryName of binEntryNames) {
package/src/libuild.js CHANGED
@@ -357,6 +357,8 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
357
357
  } else {
358
358
  entryName = match[1];
359
359
  }
360
+ } else if (exportValue.startsWith("./") && !exportValue.includes("package.json")) {
361
+ hasInvalidPath = true;
360
362
  }
361
363
  }
362
364
  } else if (typeof exportValue === "object" && exportValue !== null) {
@@ -373,6 +375,8 @@ function checkIfExportIsStale(exportKey, exportValue, entries) {
373
375
  } else {
374
376
  entryName = match[1];
375
377
  }
378
+ } else if (importPath.startsWith("./") && !importPath.includes("package.json")) {
379
+ hasInvalidPath = true;
376
380
  }
377
381
  }
378
382
  }
@@ -492,10 +496,12 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
492
496
  exports[key] = expandExistingExport(value);
493
497
  }
494
498
  }
495
- if (!exports["."]) {
496
- exports["."] = await createExportEntry(mainEntry);
497
- } else {
498
- exports["."] = expandExistingExport(exports["."], mainEntry);
499
+ if (mainEntry) {
500
+ if (!exports["."]) {
501
+ exports["."] = await createExportEntry(mainEntry);
502
+ } else {
503
+ exports["."] = expandExistingExport(exports["."], mainEntry);
504
+ }
499
505
  }
500
506
  for (const entry of entries) {
501
507
  if (entry === "umd")
@@ -870,15 +876,17 @@ async function cleanPackageJSON(pkg, mainEntry, options, cwd, distDir) {
870
876
  if (!cleaned.type) {
871
877
  cleaned.type = "module";
872
878
  }
873
- if (options.formats.cjs) {
874
- cleaned.main = `src/${mainEntry}.cjs`;
875
- }
876
- cleaned.module = `src/${mainEntry}.js`;
877
- if (distDir) {
878
- const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
879
- const exists = await fileExists(dtsPath);
880
- if (exists) {
881
- cleaned.types = `src/${mainEntry}.d.ts`;
879
+ if (mainEntry) {
880
+ if (options.formats.cjs) {
881
+ cleaned.main = `src/${mainEntry}.cjs`;
882
+ }
883
+ cleaned.module = `src/${mainEntry}.js`;
884
+ if (distDir) {
885
+ const dtsPath = Path3.join(distDir, "src", `${mainEntry}.d.ts`);
886
+ const exists = await fileExists(dtsPath);
887
+ if (exists) {
888
+ cleaned.types = `src/${mainEntry}.d.ts`;
889
+ }
882
890
  }
883
891
  }
884
892
  return cleaned;
@@ -933,8 +941,37 @@ async function build2(cwd, save = false) {
933
941
  }
934
942
  }
935
943
  const binDir = Path3.join(cwd, "bin");
936
- const srcEntries = await findEntrypoints(srcDir);
944
+ const allSrcFiles = await findEntrypoints(srcDir);
937
945
  const binEntries = await findBinEntrypoints(binDir);
946
+ let srcEntries;
947
+ if (pkg.exports && typeof pkg.exports === "object") {
948
+ const srcExportKeys = Object.keys(pkg.exports).filter((key) => {
949
+ if (key === "." || key === "./package.json")
950
+ return false;
951
+ const val = pkg.exports[key];
952
+ if (typeof val === "string")
953
+ return val.includes("/src/");
954
+ if (typeof val === "object" && val !== null) {
955
+ return Object.values(val).some((v) => typeof v === "string" && v.includes("/src/"));
956
+ }
957
+ return false;
958
+ });
959
+ const dotExport = pkg.exports["."];
960
+ let hasCustomMain = false;
961
+ if (dotExport) {
962
+ const importPath = typeof dotExport === "string" ? dotExport : dotExport.import || dotExport.default;
963
+ if (importPath && !importPath.includes("/index.")) {
964
+ hasCustomMain = true;
965
+ }
966
+ }
967
+ if (srcExportKeys.length > 0 || hasCustomMain) {
968
+ srcEntries = allSrcFiles;
969
+ } else {
970
+ srcEntries = allSrcFiles.includes("index") ? ["index"] : allSrcFiles;
971
+ }
972
+ } else {
973
+ srcEntries = allSrcFiles;
974
+ }
938
975
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
939
976
  const entries = [
940
977
  ...srcEntries,
@@ -1043,6 +1080,8 @@ async function build2(cwd, save = false) {
1043
1080
  format: "esm",
1044
1081
  outExtension: { ".js": ".js" },
1045
1082
  bundle: true,
1083
+ splitting: true,
1084
+ // Enable code splitting for dynamic imports
1046
1085
  minify: false,
1047
1086
  sourcemap: false,
1048
1087
  external: externalDeps,
@@ -1069,6 +1108,20 @@ async function build2(cwd, save = false) {
1069
1108
  })] : []
1070
1109
  ]
1071
1110
  });
1111
+ if (options.formats.cjs) {
1112
+ const distFiles = await FS3.readdir(distDir);
1113
+ const chunkFiles = distFiles.filter(
1114
+ (f) => f.endsWith(".js") && !f.endsWith(".d.ts") && (f.startsWith("chunk-") || f.includes("-") && !f.startsWith("package"))
1115
+ );
1116
+ if (chunkFiles.length > 0) {
1117
+ console.info(`
1118
+ \u26A0\uFE0F Code splitting detected - CommonJS build will bundle dynamic imports inline`);
1119
+ console.info(` ESM build: ${chunkFiles.length} chunk file(s) created for lazy loading`);
1120
+ console.info(` CJS build: Dynamic imports bundled inline (no chunks)`);
1121
+ console.info(` To get code splitting benefits, use ESM imports or remove "main" field
1122
+ `);
1123
+ }
1124
+ }
1072
1125
  if (binEntryPoints.length > 0) {
1073
1126
  const runtimeBanner = generateRuntimeBanner(pkg);
1074
1127
  for (const binEntryName of binEntryNames) {