@savvy-web/rslib-builder 0.13.0 → 0.13.1

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 +8 -21
  2. package/index.d.ts +15 -0
  3. package/index.js +47 -21
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -4,30 +4,17 @@
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
 
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
  /**
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([
@@ -2794,7 +2804,6 @@ const VirtualEntryPlugin = (options)=>{
2794
2804
  const hasFormatOverrides = void 0 !== entryFormats && Object.keys(entryFormats).length > 0;
2795
2805
  const collapseIndex = bundle || !(options.exportsAsIndexes ?? false);
2796
2806
  const baseOutputDir = `dist/${target}`;
2797
- const outputDir = isDualFormat ? `${baseOutputDir}/${primaryFormat}` : baseOutputDir;
2798
2807
  const apiModelForTarget = "npm" === target ? options.apiModel : void 0;
2799
2808
  const sourceMap = "dev" === target;
2800
2809
  const externalsConfig = options.externals && options.externals.length > 0 ? {
@@ -2843,6 +2852,9 @@ const VirtualEntryPlugin = (options)=>{
2843
2852
  target,
2844
2853
  ...options.transformFiles && {
2845
2854
  transformFiles: options.transformFiles
2855
+ },
2856
+ ...isDualFormat && {
2857
+ formatDirs: formats
2846
2858
  }
2847
2859
  }));
2848
2860
  plugins.push(...options.plugins);
@@ -2859,11 +2871,14 @@ const VirtualEntryPlugin = (options)=>{
2859
2871
  format: primaryFormat,
2860
2872
  ...void 0 !== apiModelForTarget && {
2861
2873
  apiModel: apiModelForTarget
2874
+ },
2875
+ ...isDualFormat && {
2876
+ dtsPathPrefix: primaryFormat
2862
2877
  }
2863
2878
  }));
2864
2879
  const lib = {
2865
2880
  id: isDualFormat ? `${target}-${primaryFormat}` : target,
2866
- outBase: bundle ? outputDir : "src",
2881
+ outBase: bundle ? baseOutputDir : "src",
2867
2882
  output: {
2868
2883
  target: "node",
2869
2884
  module: true,
@@ -2871,7 +2886,10 @@ const VirtualEntryPlugin = (options)=>{
2871
2886
  sourceMap,
2872
2887
  ...bundlelessOutput,
2873
2888
  distPath: {
2874
- root: outputDir
2889
+ root: baseOutputDir,
2890
+ ...isDualFormat && {
2891
+ js: primaryFormat
2892
+ }
2875
2893
  },
2876
2894
  copy: {
2877
2895
  patterns: options.copyPatterns
@@ -2902,11 +2920,17 @@ const VirtualEntryPlugin = (options)=>{
2902
2920
  if (hasRegularEntries) {
2903
2921
  libConfigs.push(lib);
2904
2922
  if (isDualFormat) for (const secondaryFormat of formats.slice(1)){
2905
- const secondaryOutputDir = `${baseOutputDir}/${secondaryFormat}`;
2906
2923
  const secondaryPlugins = [
2907
- FilesArrayPlugin({
2908
- target
2909
- }),
2924
+ {
2925
+ name: "strip-metadata-assets",
2926
+ setup (api) {
2927
+ api.processAssets({
2928
+ stage: "additional"
2929
+ }, (context)=>{
2930
+ for (const name of Object.keys(context.compilation.assets))if ("package.json" === name || "README.md" === name || "LICENSE" === name) delete context.compilation.assets[name];
2931
+ });
2932
+ }
2933
+ },
2910
2934
  DtsPlugin({
2911
2935
  ...options.tsconfigPath && {
2912
2936
  tsconfigPath: options.tsconfigPath
@@ -2917,19 +2941,21 @@ const VirtualEntryPlugin = (options)=>{
2917
2941
  bundledPackages: options.dtsBundledPackages
2918
2942
  },
2919
2943
  buildTarget: target,
2920
- format: secondaryFormat
2944
+ format: secondaryFormat,
2945
+ dtsPathPrefix: secondaryFormat
2921
2946
  })
2922
2947
  ];
2923
2948
  const secondaryLib = {
2924
2949
  id: `${target}-${secondaryFormat}`,
2925
- outBase: bundle ? secondaryOutputDir : "src",
2950
+ outBase: bundle ? baseOutputDir : "src",
2926
2951
  output: {
2927
2952
  target: "node",
2928
2953
  cleanDistPath: false,
2929
2954
  sourceMap,
2930
2955
  ...bundlelessOutput,
2931
2956
  distPath: {
2932
- root: secondaryOutputDir
2957
+ root: baseOutputDir,
2958
+ js: secondaryFormat
2933
2959
  },
2934
2960
  ...externalsConfig
2935
2961
  },
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.13.1",
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": {