@savvy-web/rslib-builder 0.13.0 → 0.14.0

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.
Files changed (4) hide show
  1. package/README.md +9 -26
  2. package/index.d.ts +25 -0
  3. package/index.js +79 -22
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -4,39 +4,22 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen)](https://nodejs.org)
6
6
 
7
- Build modern ESM Node.js libraries with minimal configuration. Handles
8
- TypeScript declarations, package.json transformations, and PNPM workspace
9
- resolution automatically.
7
+ Build modern ESM Node.js libraries with minimal configuration. Handles TypeScript declarations, package.json transformations, and PNPM workspace resolution automatically.
10
8
 
11
9
  ## Features
12
10
 
13
- - **Zero-Config Entry Detection** - Auto-discovers
14
- entry points from package.json exports
15
- - **10-100x Faster Types** - Uses tsgo (native
16
- TypeScript compiler) with API Extractor for
17
- bundled, clean public API declarations
18
- - **Production-Ready Transforms** - Converts `.ts`
19
- exports to `.js`, resolves PNPM `catalog:` and
20
- `workspace:` references, generates files array
21
- - **Bundled or Bundleless** - Choose single-file
22
- bundles per entry or bundleless mode that
23
- preserves your source file structure
24
- - **Multi-Target Builds** - Separate dev (with
25
- source maps) and npm (optimized) outputs from a
26
- single configuration
27
- - **Flexible Formats** - ESM, CJS, or dual format
28
- output with per-entry format overrides
29
- - **TSDoc Validation** - Pre-build documentation
30
- validation with automatic public API discovery
11
+ - **Zero-Config Entry Detection** - Auto-discovers entry points from package.json exports
12
+ - **10-100x Faster Types** - Uses tsgo (native TypeScript compiler) with API Extractor for bundled, clean public API declarations
13
+ - **Production-Ready Transforms** - Converts `.ts` exports to `.js`, resolves PNPM `catalog:` and `workspace:` references, generates files array
14
+ - **Bundled or Bundleless** - Choose single-file bundles per entry or bundleless mode that preserves your source file structure
15
+ - **Multi-Target Builds** - Separate dev (with source maps) and npm (optimized) outputs from a single configuration
16
+ - **Flexible Formats** - ESM, CJS, or dual format output with per-entry format overrides
17
+ - **TSDoc Validation** - Pre-build documentation validation with automatic public API discovery
31
18
 
32
19
  ## Installation
33
20
 
34
21
  ```bash
35
- npm install --save-dev \
36
- @savvy-web/rslib-builder \
37
- @rslib/core \
38
- @microsoft/api-extractor \
39
- @typescript/native-preview
22
+ npm install --save-dev @savvy-web/rslib-builder @rslib/core @microsoft/api-extractor @typescript/native-preview
40
23
  ```
41
24
 
42
25
  ## Quick Start
package/index.d.ts CHANGED
@@ -421,6 +421,13 @@ export declare interface DtsPluginOptions {
421
421
  * The API model is excluded from npm publish (not added to `files` array).
422
422
  */
423
423
  apiModel?: ApiModelOptions | boolean;
424
+ /**
425
+ * Path prefix for emitted DTS files.
426
+ * Used in dual format builds to place declarations in format subdirectories
427
+ * (e.g., `esm/index.d.ts`, `cjs/index.d.cts`).
428
+ * Metadata files (api.json, tsdoc-metadata.json, tsconfig.json) are NOT prefixed.
429
+ */
430
+ dtsPathPrefix?: string;
424
431
  }
425
432
 
426
433
  /**
@@ -647,6 +654,14 @@ export declare interface FilesArrayPluginOptions<TTarget extends string = string
647
654
  * Passed to the `transformFiles` callback to allow target-specific transformations.
648
655
  */
649
656
  target: TTarget;
657
+ /**
658
+ * Format directories to include in the files array.
659
+ * Used in dual format builds so npm's `files` field includes
660
+ * format directories (e.g., `["esm", "cjs"]`).
661
+ * Directory names are treated as recursive includes by npm,
662
+ * so individual files under these dirs are filtered out.
663
+ */
664
+ formatDirs?: string[];
650
665
  }
651
666
 
652
667
  /**
@@ -1164,6 +1179,7 @@ export declare class NodeLibraryBuilder {
1164
1179
  externals: never[];
1165
1180
  apiModel: true;
1166
1181
  bundle: true;
1182
+ cjsInterop: false;
1167
1183
  };
1168
1184
  /**
1169
1185
  * Merges user-provided options with default options.
@@ -1528,6 +1544,15 @@ export declare interface NodeLibraryBuilderOptions {
1528
1544
  * @defaultValue true
1529
1545
  */
1530
1546
  bundle?: boolean;
1547
+ /**
1548
+ * Enable CJS default export interop for CommonJS output files.
1549
+ * When true, CJS files are patched so `require('module')` returns
1550
+ * the default export directly instead of `{ default: value }`.
1551
+ * Named exports are preserved as properties on the default value.
1552
+ * Only affects CJS format output; ESM is unchanged.
1553
+ * @defaultValue false
1554
+ */
1555
+ cjsInterop?: boolean;
1531
1556
  }
1532
1557
 
1533
1558
  /**
package/index.js CHANGED
@@ -1502,14 +1502,15 @@ function runTsgo(options) {
1502
1502
  let outputPath = file.relativePath;
1503
1503
  if (outputPath.startsWith("src/")) outputPath = outputPath.slice(4);
1504
1504
  if (".d.ts" !== dtsExtension && outputPath.endsWith(".d.ts")) outputPath = outputPath.replace(/\.d\.ts$/, dtsExtension);
1505
- const jsOutputPath = outputPath.replace(/\.d\.(ts|mts|cts)$/, ".js");
1505
+ const prefixedPath = options.dtsPathPrefix ? `${options.dtsPathPrefix}/${outputPath}` : outputPath;
1506
+ const jsOutputPath = prefixedPath.replace(/\.d\.(ts|mts|cts)$/, ".js");
1506
1507
  if (!context.compilation.assets[jsOutputPath]) continue;
1507
1508
  let content = await readFile(file.path, "utf-8");
1508
1509
  content = stripSourceMapComment(content);
1509
- const source = new context.sources.OriginalSource(content, outputPath);
1510
- context.compilation.emitAsset(outputPath, source);
1510
+ const source = new context.sources.OriginalSource(content, prefixedPath);
1511
+ context.compilation.emitAsset(prefixedPath, source);
1511
1512
  emittedCount++;
1512
- if (filesArray && outputPath.endsWith(".d.ts")) filesArray.add(outputPath);
1513
+ if (filesArray && prefixedPath.endsWith(".d.ts")) filesArray.add(prefixedPath);
1513
1514
  }
1514
1515
  core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted ${emittedCount} declaration file${1 === emittedCount ? "" : "s"} through asset pipeline`);
1515
1516
  }
@@ -1597,12 +1598,13 @@ function runTsgo(options) {
1597
1598
  let emittedCount = 0;
1598
1599
  for (const [entryName, tempBundledPath] of bundledFiles){
1599
1600
  const bundledFileName = `${entryName}${bundledDtsExtension}`;
1601
+ const prefixedBundledFileName = options.dtsPathPrefix ? `${options.dtsPathPrefix}/${bundledFileName}` : bundledFileName;
1600
1602
  let content = await readFile(tempBundledPath, "utf-8");
1601
1603
  content = stripSourceMapComment(content);
1602
- const source = new context.sources.OriginalSource(content, bundledFileName);
1603
- context.compilation.emitAsset(bundledFileName, source);
1604
+ const source = new context.sources.OriginalSource(content, prefixedBundledFileName);
1605
+ context.compilation.emitAsset(prefixedBundledFileName, source);
1604
1606
  emittedCount++;
1605
- if (filesArray) filesArray.add(bundledFileName);
1607
+ if (filesArray) filesArray.add(prefixedBundledFileName);
1606
1608
  }
1607
1609
  core_logger.info(`${picocolors.dim(`[${envId}]`)} Emitted ${emittedCount} bundled declaration file${1 === emittedCount ? "" : "s"} through asset pipeline`);
1608
1610
  }
@@ -1624,7 +1626,7 @@ function runTsgo(options) {
1624
1626
  hasTsdocMetadata: !!tsdocMetadataPath,
1625
1627
  hasTsconfig: !!state.parsedConfig && !!state.tsconfigPath,
1626
1628
  cwd,
1627
- distPath: `dist/${envId}`
1629
+ distPath: `dist/${options.buildTarget ?? envId}`
1628
1630
  });
1629
1631
  }
1630
1632
  }
@@ -1654,14 +1656,16 @@ function runTsgo(options) {
1654
1656
  }
1655
1657
  if (options.bundle) for (const [entryName] of bundledFiles){
1656
1658
  const bundledFileName = `${entryName}${bundledDtsExtension}`;
1657
- const mapFileName = `${bundledFileName}.map`;
1659
+ const prefixedBundledFileName = options.dtsPathPrefix ? `${options.dtsPathPrefix}/${bundledFileName}` : bundledFileName;
1660
+ const mapFileName = `${prefixedBundledFileName}.map`;
1658
1661
  for (const file of dtsFiles){
1659
1662
  if (file.relativePath.endsWith(".d.ts.map")) continue;
1660
1663
  let outputPath = file.relativePath;
1661
1664
  if (outputPath.startsWith("src/")) outputPath = outputPath.slice(4);
1662
1665
  if (".d.ts" !== dtsExtension && outputPath.endsWith(".d.ts")) outputPath = outputPath.replace(/\.d\.ts$/, dtsExtension);
1663
- const mapFileName = `${outputPath}.map`;
1664
- if (context.compilation.assets[mapFileName]) delete context.compilation.assets[mapFileName];
1666
+ const prefixedOutputPath = options.dtsPathPrefix ? `${options.dtsPathPrefix}/${outputPath}` : outputPath;
1667
+ const individualMapFileName = `${prefixedOutputPath}.map`;
1668
+ if (context.compilation.assets[individualMapFileName]) delete context.compilation.assets[individualMapFileName];
1665
1669
  }
1666
1670
  if (context.compilation.assets[mapFileName]) delete context.compilation.assets[mapFileName];
1667
1671
  }
@@ -1830,6 +1834,7 @@ const FilesArrayPlugin = (options)=>({
1830
1834
  const license = await TextAsset.create(context, "LICENSE", false);
1831
1835
  if (license) filesArray.add(license.fileName);
1832
1836
  for (const assetName of Object.keys(context.compilation.assets))if (!assetName.endsWith(".map") && !filesArray.has(assetName)) filesArray.add(assetName);
1837
+ if (options?.formatDirs) for (const dir of options.formatDirs)filesArray.add(dir);
1833
1838
  if (options?.transformFiles) await options.transformFiles({
1834
1839
  compilation: context.compilation,
1835
1840
  filesArray,
@@ -1848,6 +1853,11 @@ const FilesArrayPlugin = (options)=>({
1848
1853
  ...previousFiles,
1849
1854
  ...Array.from(filesArray)
1850
1855
  ].sort());
1856
+ if (options?.formatDirs) {
1857
+ for (const file of [
1858
+ ...allFiles
1859
+ ])if (options.formatDirs.some((dir)=>file.startsWith(`${dir}/`))) allFiles.delete(file);
1860
+ }
1851
1861
  if (0 === allFiles.size) delete packageJson.data.files;
1852
1862
  else {
1853
1863
  const newFiles = new Set([
@@ -2678,6 +2688,20 @@ const VirtualEntryPlugin = (options)=>{
2678
2688
  }
2679
2689
  };
2680
2690
  };
2691
+ const CJS_INTEROP_FOOTER = `
2692
+ if (module.exports && module.exports.__esModule && 'default' in module.exports) {
2693
+ var _def = module.exports.default;
2694
+ if (_def !== null && _def !== undefined && (typeof _def === 'object' || typeof _def === 'function')) {
2695
+ var _keys = Object.keys(module.exports);
2696
+ for (var _i = 0; _i < _keys.length; _i++) {
2697
+ var _key = _keys[_i];
2698
+ if (_key !== 'default' && _key !== '__esModule' && !(_key in _def)) {
2699
+ _def[_key] = module.exports[_key];
2700
+ }
2701
+ }
2702
+ }
2703
+ module.exports = _def;
2704
+ }`;
2681
2705
  /* v8 ignore next -- @preserve */ class NodeLibraryBuilder {
2682
2706
  static VALID_TARGETS = [
2683
2707
  "dev",
@@ -2694,7 +2718,8 @@ const VirtualEntryPlugin = (options)=>{
2694
2718
  ],
2695
2719
  externals: [],
2696
2720
  apiModel: true,
2697
- bundle: true
2721
+ bundle: true,
2722
+ cjsInterop: false
2698
2723
  };
2699
2724
  static mergeOptions(options = {}) {
2700
2725
  const copyPatterns = [
@@ -2715,6 +2740,7 @@ const VirtualEntryPlugin = (options)=>{
2715
2740
  externals: options.externals ?? NodeLibraryBuilder.DEFAULT_OPTIONS.externals,
2716
2741
  apiModel: options.apiModel ?? NodeLibraryBuilder.DEFAULT_OPTIONS.apiModel,
2717
2742
  bundle: options.bundle ?? NodeLibraryBuilder.DEFAULT_OPTIONS.bundle,
2743
+ cjsInterop: options.cjsInterop ?? NodeLibraryBuilder.DEFAULT_OPTIONS.cjsInterop,
2718
2744
  ...void 0 !== options.entry && {
2719
2745
  entry: options.entry
2720
2746
  },
@@ -2794,7 +2820,6 @@ const VirtualEntryPlugin = (options)=>{
2794
2820
  const hasFormatOverrides = void 0 !== entryFormats && Object.keys(entryFormats).length > 0;
2795
2821
  const collapseIndex = bundle || !(options.exportsAsIndexes ?? false);
2796
2822
  const baseOutputDir = `dist/${target}`;
2797
- const outputDir = isDualFormat ? `${baseOutputDir}/${primaryFormat}` : baseOutputDir;
2798
2823
  const apiModelForTarget = "npm" === target ? options.apiModel : void 0;
2799
2824
  const sourceMap = "dev" === target;
2800
2825
  const externalsConfig = options.externals && options.externals.length > 0 ? {
@@ -2843,6 +2868,9 @@ const VirtualEntryPlugin = (options)=>{
2843
2868
  target,
2844
2869
  ...options.transformFiles && {
2845
2870
  transformFiles: options.transformFiles
2871
+ },
2872
+ ...isDualFormat && {
2873
+ formatDirs: formats
2846
2874
  }
2847
2875
  }));
2848
2876
  plugins.push(...options.plugins);
@@ -2859,11 +2887,14 @@ const VirtualEntryPlugin = (options)=>{
2859
2887
  format: primaryFormat,
2860
2888
  ...void 0 !== apiModelForTarget && {
2861
2889
  apiModel: apiModelForTarget
2890
+ },
2891
+ ...isDualFormat && {
2892
+ dtsPathPrefix: primaryFormat
2862
2893
  }
2863
2894
  }));
2864
2895
  const lib = {
2865
2896
  id: isDualFormat ? `${target}-${primaryFormat}` : target,
2866
- outBase: bundle ? outputDir : "src",
2897
+ outBase: bundle ? baseOutputDir : "src",
2867
2898
  output: {
2868
2899
  target: "node",
2869
2900
  module: true,
@@ -2871,7 +2902,10 @@ const VirtualEntryPlugin = (options)=>{
2871
2902
  sourceMap,
2872
2903
  ...bundlelessOutput,
2873
2904
  distPath: {
2874
- root: outputDir
2905
+ root: baseOutputDir,
2906
+ ...isDualFormat && {
2907
+ js: primaryFormat
2908
+ }
2875
2909
  },
2876
2910
  copy: {
2877
2911
  patterns: options.copyPatterns
@@ -2892,6 +2926,11 @@ const VirtualEntryPlugin = (options)=>{
2892
2926
  entry
2893
2927
  },
2894
2928
  define: sourceDefine
2929
+ },
2930
+ ...options.cjsInterop && "cjs" === primaryFormat && {
2931
+ footer: {
2932
+ js: CJS_INTEROP_FOOTER
2933
+ }
2895
2934
  }
2896
2935
  };
2897
2936
  const hasRegularEntries = void 0 !== options.entry || NodeLibraryBuilder.packageHasExports();
@@ -2902,11 +2941,17 @@ const VirtualEntryPlugin = (options)=>{
2902
2941
  if (hasRegularEntries) {
2903
2942
  libConfigs.push(lib);
2904
2943
  if (isDualFormat) for (const secondaryFormat of formats.slice(1)){
2905
- const secondaryOutputDir = `${baseOutputDir}/${secondaryFormat}`;
2906
2944
  const secondaryPlugins = [
2907
- FilesArrayPlugin({
2908
- target
2909
- }),
2945
+ {
2946
+ name: "strip-metadata-assets",
2947
+ setup (api) {
2948
+ api.processAssets({
2949
+ stage: "additional"
2950
+ }, (context)=>{
2951
+ for (const name of Object.keys(context.compilation.assets))if ("package.json" === name || "README.md" === name || "LICENSE" === name) delete context.compilation.assets[name];
2952
+ });
2953
+ }
2954
+ },
2910
2955
  DtsPlugin({
2911
2956
  ...options.tsconfigPath && {
2912
2957
  tsconfigPath: options.tsconfigPath
@@ -2917,19 +2962,21 @@ const VirtualEntryPlugin = (options)=>{
2917
2962
  bundledPackages: options.dtsBundledPackages
2918
2963
  },
2919
2964
  buildTarget: target,
2920
- format: secondaryFormat
2965
+ format: secondaryFormat,
2966
+ dtsPathPrefix: secondaryFormat
2921
2967
  })
2922
2968
  ];
2923
2969
  const secondaryLib = {
2924
2970
  id: `${target}-${secondaryFormat}`,
2925
- outBase: bundle ? secondaryOutputDir : "src",
2971
+ outBase: bundle ? baseOutputDir : "src",
2926
2972
  output: {
2927
2973
  target: "node",
2928
2974
  cleanDistPath: false,
2929
2975
  sourceMap,
2930
2976
  ...bundlelessOutput,
2931
2977
  distPath: {
2932
- root: secondaryOutputDir
2978
+ root: baseOutputDir,
2979
+ js: secondaryFormat
2933
2980
  },
2934
2981
  ...externalsConfig
2935
2982
  },
@@ -2947,6 +2994,11 @@ const VirtualEntryPlugin = (options)=>{
2947
2994
  entry
2948
2995
  },
2949
2996
  define: sourceDefine
2997
+ },
2998
+ ...options.cjsInterop && "cjs" === secondaryFormat && {
2999
+ footer: {
3000
+ js: CJS_INTEROP_FOOTER
3001
+ }
2950
3002
  }
2951
3003
  };
2952
3004
  libConfigs.push(secondaryLib);
@@ -3012,6 +3064,11 @@ const VirtualEntryPlugin = (options)=>{
3012
3064
  tsconfigPath: options.tsconfigPath
3013
3065
  },
3014
3066
  define: sourceDefine
3067
+ },
3068
+ ...options.cjsInterop && "cjs" === overrideFormat && {
3069
+ footer: {
3070
+ js: CJS_INTEROP_FOOTER
3071
+ }
3015
3072
  }
3016
3073
  };
3017
3074
  libConfigs.push(overrideLib);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/rslib-builder",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "private": false,
5
5
  "description": "RSlib-based build system for Node.js libraries with automatic package.json transformation, TypeScript declaration bundling, and multi-target support",
6
6
  "keywords": [
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "https://github.com/savvy-web/rslib-builder.git"
28
+ "url": "git+https://github.com/savvy-web/rslib-builder.git"
29
29
  },
30
30
  "license": "MIT",
31
31
  "author": {