@b9g/libuild 0.1.15 → 0.1.17

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,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.17] - 2025-01-25
6
+
7
+ ### Added
8
+ - **Ambient .d.ts file copying** - Hand-written ambient TypeScript declaration files (e.g., global.d.ts with `declare module` statements) are now automatically copied from src/ to dist/src/ during build. These files are automatically discovered by TypeScript when the package is consumed, providing global type declarations for module augmentation.
9
+
10
+ ### Fixed
11
+ - **Non-deterministic export field ordering** - Fixed issue where package.json exports fields (types/import/require) would reorder non-deterministically across rebuilds with --save flag, causing unnecessary git diffs. All builds now consistently use types-first ordering.
12
+
13
+ ## [0.1.16] - 2025-01-14
14
+
15
+ ### Added
16
+ - **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
17
+ - **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
18
+ - **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)
19
+
20
+ ### Fixed
21
+ - **Export validation** - Invalid export paths that don't point to src/ files are now properly validated and rejected with clear error messages
22
+
5
23
  ## [0.1.15] - 2025-01-14
6
24
 
7
25
  ### 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.17",
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
  }
@@ -215407,20 +215411,17 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
215407
215411
  async function createExportEntry(entry) {
215408
215412
  if (entry.startsWith("bin/")) {
215409
215413
  const binEntry = entry.replace("bin/", "");
215410
- const exportEntry = {
215411
- import: `./bin/${binEntry}.js`
215412
- };
215414
+ const exportEntry = {};
215413
215415
  if (distDir) {
215414
215416
  const dtsPath = Path3.join(distDir, "bin", `${binEntry}.d.ts`);
215415
215417
  if (await fileExists(dtsPath)) {
215416
215418
  exportEntry.types = `./bin/${binEntry}.d.ts`;
215417
215419
  }
215418
215420
  }
215421
+ exportEntry.import = `./bin/${binEntry}.js`;
215419
215422
  return exportEntry;
215420
215423
  } else {
215421
- const exportEntry = {
215422
- import: `./src/${entry}.js`
215423
- };
215424
+ const exportEntry = {};
215424
215425
  if (distDir) {
215425
215426
  const dtsPath = Path3.join(distDir, "src", `${entry}.d.ts`);
215426
215427
  const exists = await fileExists(dtsPath);
@@ -215428,6 +215429,7 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
215428
215429
  exportEntry.types = `./src/${entry}.d.ts`;
215429
215430
  }
215430
215431
  }
215432
+ exportEntry.import = `./src/${entry}.js`;
215431
215433
  if (options.formats.cjs) {
215432
215434
  exportEntry.require = `./src/${entry}.cjs`;
215433
215435
  }
@@ -215950,8 +215952,37 @@ async function build2(cwd, save = false) {
215950
215952
  }
215951
215953
  }
215952
215954
  const binDir = Path3.join(cwd, "bin");
215953
- const srcEntries = await findEntrypoints(srcDir);
215955
+ const allSrcFiles = await findEntrypoints(srcDir);
215954
215956
  const binEntries = await findBinEntrypoints(binDir);
215957
+ let srcEntries;
215958
+ if (pkg.exports && typeof pkg.exports === "object") {
215959
+ const srcExportKeys = Object.keys(pkg.exports).filter((key) => {
215960
+ if (key === "." || key === "./package.json")
215961
+ return false;
215962
+ const val = pkg.exports[key];
215963
+ if (typeof val === "string")
215964
+ return val.includes("/src/");
215965
+ if (typeof val === "object" && val !== null) {
215966
+ return Object.values(val).some((v) => typeof v === "string" && v.includes("/src/"));
215967
+ }
215968
+ return false;
215969
+ });
215970
+ const dotExport = pkg.exports["."];
215971
+ let hasCustomMain = false;
215972
+ if (dotExport) {
215973
+ const importPath = typeof dotExport === "string" ? dotExport : dotExport.import || dotExport.default;
215974
+ if (importPath && !importPath.includes("/index.")) {
215975
+ hasCustomMain = true;
215976
+ }
215977
+ }
215978
+ if (srcExportKeys.length > 0 || hasCustomMain) {
215979
+ srcEntries = allSrcFiles;
215980
+ } else {
215981
+ srcEntries = allSrcFiles.includes("index") ? ["index"] : allSrcFiles;
215982
+ }
215983
+ } else {
215984
+ srcEntries = allSrcFiles;
215985
+ }
215955
215986
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
215956
215987
  const entries = [
215957
215988
  ...srcEntries,
@@ -216060,6 +216091,8 @@ async function build2(cwd, save = false) {
216060
216091
  format: "esm",
216061
216092
  outExtension: { ".js": ".js" },
216062
216093
  bundle: true,
216094
+ splitting: true,
216095
+ // Enable code splitting for dynamic imports
216063
216096
  minify: false,
216064
216097
  sourcemap: false,
216065
216098
  external: externalDeps,
@@ -216086,6 +216119,20 @@ async function build2(cwd, save = false) {
216086
216119
  })] : []
216087
216120
  ]
216088
216121
  });
216122
+ if (options.formats.cjs) {
216123
+ const distFiles = await FS3.readdir(distDir);
216124
+ const chunkFiles = distFiles.filter(
216125
+ (f) => f.endsWith(".js") && !f.endsWith(".d.ts") && (f.startsWith("chunk-") || f.includes("-") && !f.startsWith("package"))
216126
+ );
216127
+ if (chunkFiles.length > 0) {
216128
+ console.info(`
216129
+ \u26A0\uFE0F Code splitting detected - CommonJS build will bundle dynamic imports inline`);
216130
+ console.info(` ESM build: ${chunkFiles.length} chunk file(s) created for lazy loading`);
216131
+ console.info(` CJS build: Dynamic imports bundled inline (no chunks)`);
216132
+ console.info(` To get code splitting benefits, use ESM imports or remove "main" field
216133
+ `);
216134
+ }
216135
+ }
216089
216136
  if (binEntryPoints.length > 0) {
216090
216137
  const runtimeBanner = generateRuntimeBanner(pkg);
216091
216138
  for (const binEntryName of binEntryNames) {
@@ -216191,6 +216238,18 @@ async function build2(cwd, save = false) {
216191
216238
  await FS3.copyFile(srcPath, Path3.join(distDir, file));
216192
216239
  }
216193
216240
  }
216241
+ if (await fileExists(srcDir)) {
216242
+ const srcFiles = await FS3.readdir(srcDir);
216243
+ const ambientDtsFiles = srcFiles.filter((f) => f.endsWith(".d.ts"));
216244
+ if (ambientDtsFiles.length > 0) {
216245
+ console.info(` Copying ${ambientDtsFiles.length} ambient .d.ts file(s)...`);
216246
+ for (const dtsFile of ambientDtsFiles) {
216247
+ const srcPath = Path3.join(srcDir, dtsFile);
216248
+ const destPath = Path3.join(distSrcDir, dtsFile);
216249
+ await FS3.copyFile(srcPath, destPath);
216250
+ }
216251
+ }
216252
+ }
216194
216253
  const commonFiles = ["README.md", "LICENSE", "CHANGELOG.md", "COPYING", "AUTHORS"];
216195
216254
  if (pkg.files && Array.isArray(pkg.files)) {
216196
216255
  for (const commonFile of commonFiles) {
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
  }
@@ -394,20 +398,17 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
394
398
  async function createExportEntry(entry) {
395
399
  if (entry.startsWith("bin/")) {
396
400
  const binEntry = entry.replace("bin/", "");
397
- const exportEntry = {
398
- import: `./bin/${binEntry}.js`
399
- };
401
+ const exportEntry = {};
400
402
  if (distDir) {
401
403
  const dtsPath = Path3.join(distDir, "bin", `${binEntry}.d.ts`);
402
404
  if (await fileExists(dtsPath)) {
403
405
  exportEntry.types = `./bin/${binEntry}.d.ts`;
404
406
  }
405
407
  }
408
+ exportEntry.import = `./bin/${binEntry}.js`;
406
409
  return exportEntry;
407
410
  } else {
408
- const exportEntry = {
409
- import: `./src/${entry}.js`
410
- };
411
+ const exportEntry = {};
411
412
  if (distDir) {
412
413
  const dtsPath = Path3.join(distDir, "src", `${entry}.d.ts`);
413
414
  const exists = await fileExists(dtsPath);
@@ -415,6 +416,7 @@ async function generateExports(entries, mainEntry, options, existingExports = {}
415
416
  exportEntry.types = `./src/${entry}.d.ts`;
416
417
  }
417
418
  }
419
+ exportEntry.import = `./src/${entry}.js`;
418
420
  if (options.formats.cjs) {
419
421
  exportEntry.require = `./src/${entry}.cjs`;
420
422
  }
@@ -937,8 +939,37 @@ async function build2(cwd, save = false) {
937
939
  }
938
940
  }
939
941
  const binDir = Path3.join(cwd, "bin");
940
- const srcEntries = await findEntrypoints(srcDir);
942
+ const allSrcFiles = await findEntrypoints(srcDir);
941
943
  const binEntries = await findBinEntrypoints(binDir);
944
+ let srcEntries;
945
+ if (pkg.exports && typeof pkg.exports === "object") {
946
+ const srcExportKeys = Object.keys(pkg.exports).filter((key) => {
947
+ if (key === "." || key === "./package.json")
948
+ return false;
949
+ const val = pkg.exports[key];
950
+ if (typeof val === "string")
951
+ return val.includes("/src/");
952
+ if (typeof val === "object" && val !== null) {
953
+ return Object.values(val).some((v) => typeof v === "string" && v.includes("/src/"));
954
+ }
955
+ return false;
956
+ });
957
+ const dotExport = pkg.exports["."];
958
+ let hasCustomMain = false;
959
+ if (dotExport) {
960
+ const importPath = typeof dotExport === "string" ? dotExport : dotExport.import || dotExport.default;
961
+ if (importPath && !importPath.includes("/index.")) {
962
+ hasCustomMain = true;
963
+ }
964
+ }
965
+ if (srcExportKeys.length > 0 || hasCustomMain) {
966
+ srcEntries = allSrcFiles;
967
+ } else {
968
+ srcEntries = allSrcFiles.includes("index") ? ["index"] : allSrcFiles;
969
+ }
970
+ } else {
971
+ srcEntries = allSrcFiles;
972
+ }
942
973
  const allBinEntries = binEntries.map((entry) => ({ name: entry, source: "top-level" }));
943
974
  const entries = [
944
975
  ...srcEntries,
@@ -1047,6 +1078,8 @@ async function build2(cwd, save = false) {
1047
1078
  format: "esm",
1048
1079
  outExtension: { ".js": ".js" },
1049
1080
  bundle: true,
1081
+ splitting: true,
1082
+ // Enable code splitting for dynamic imports
1050
1083
  minify: false,
1051
1084
  sourcemap: false,
1052
1085
  external: externalDeps,
@@ -1073,6 +1106,20 @@ async function build2(cwd, save = false) {
1073
1106
  })] : []
1074
1107
  ]
1075
1108
  });
1109
+ if (options.formats.cjs) {
1110
+ const distFiles = await FS3.readdir(distDir);
1111
+ const chunkFiles = distFiles.filter(
1112
+ (f) => f.endsWith(".js") && !f.endsWith(".d.ts") && (f.startsWith("chunk-") || f.includes("-") && !f.startsWith("package"))
1113
+ );
1114
+ if (chunkFiles.length > 0) {
1115
+ console.info(`
1116
+ \u26A0\uFE0F Code splitting detected - CommonJS build will bundle dynamic imports inline`);
1117
+ console.info(` ESM build: ${chunkFiles.length} chunk file(s) created for lazy loading`);
1118
+ console.info(` CJS build: Dynamic imports bundled inline (no chunks)`);
1119
+ console.info(` To get code splitting benefits, use ESM imports or remove "main" field
1120
+ `);
1121
+ }
1122
+ }
1076
1123
  if (binEntryPoints.length > 0) {
1077
1124
  const runtimeBanner = generateRuntimeBanner(pkg);
1078
1125
  for (const binEntryName of binEntryNames) {
@@ -1178,6 +1225,18 @@ async function build2(cwd, save = false) {
1178
1225
  await FS3.copyFile(srcPath, Path3.join(distDir, file));
1179
1226
  }
1180
1227
  }
1228
+ if (await fileExists(srcDir)) {
1229
+ const srcFiles = await FS3.readdir(srcDir);
1230
+ const ambientDtsFiles = srcFiles.filter((f) => f.endsWith(".d.ts"));
1231
+ if (ambientDtsFiles.length > 0) {
1232
+ console.info(` Copying ${ambientDtsFiles.length} ambient .d.ts file(s)...`);
1233
+ for (const dtsFile of ambientDtsFiles) {
1234
+ const srcPath = Path3.join(srcDir, dtsFile);
1235
+ const destPath = Path3.join(distSrcDir, dtsFile);
1236
+ await FS3.copyFile(srcPath, destPath);
1237
+ }
1238
+ }
1239
+ }
1181
1240
  const commonFiles = ["README.md", "LICENSE", "CHANGELOG.md", "COPYING", "AUTHORS"];
1182
1241
  if (pkg.files && Array.isArray(pkg.files)) {
1183
1242
  for (const commonFile of commonFiles) {