@b9g/libuild 0.1.15 → 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,16 @@
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
+
5
15
  ## [0.1.15] - 2025-01-14
6
16
 
7
17
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/libuild",
3
- "version": "0.1.15",
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
  }
@@ -215950,8 +215954,37 @@ async function build2(cwd, save = false) {
215950
215954
  }
215951
215955
  }
215952
215956
  const binDir = Path3.join(cwd, "bin");
215953
- const srcEntries = await findEntrypoints(srcDir);
215957
+ const allSrcFiles = await findEntrypoints(srcDir);
215954
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
+ }
215955
215988
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
215956
215989
  const entries = [
215957
215990
  ...srcEntries,
@@ -216060,6 +216093,8 @@ async function build2(cwd, save = false) {
216060
216093
  format: "esm",
216061
216094
  outExtension: { ".js": ".js" },
216062
216095
  bundle: true,
216096
+ splitting: true,
216097
+ // Enable code splitting for dynamic imports
216063
216098
  minify: false,
216064
216099
  sourcemap: false,
216065
216100
  external: externalDeps,
@@ -216086,6 +216121,20 @@ async function build2(cwd, save = false) {
216086
216121
  })] : []
216087
216122
  ]
216088
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
+ }
216089
216138
  if (binEntryPoints.length > 0) {
216090
216139
  const runtimeBanner = generateRuntimeBanner(pkg);
216091
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
  }
@@ -937,8 +941,37 @@ async function build2(cwd, save = false) {
937
941
  }
938
942
  }
939
943
  const binDir = Path3.join(cwd, "bin");
940
- const srcEntries = await findEntrypoints(srcDir);
944
+ const allSrcFiles = await findEntrypoints(srcDir);
941
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
+ }
942
975
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
943
976
  const entries = [
944
977
  ...srcEntries,
@@ -1047,6 +1080,8 @@ async function build2(cwd, save = false) {
1047
1080
  format: "esm",
1048
1081
  outExtension: { ".js": ".js" },
1049
1082
  bundle: true,
1083
+ splitting: true,
1084
+ // Enable code splitting for dynamic imports
1050
1085
  minify: false,
1051
1086
  sourcemap: false,
1052
1087
  external: externalDeps,
@@ -1073,6 +1108,20 @@ async function build2(cwd, save = false) {
1073
1108
  })] : []
1074
1109
  ]
1075
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
+ }
1076
1125
  if (binEntryPoints.length > 0) {
1077
1126
  const runtimeBanner = generateRuntimeBanner(pkg);
1078
1127
  for (const binEntryName of binEntryNames) {