@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.
- package/README.md +9 -26
- package/index.d.ts +25 -0
- package/index.js +79 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,39 +4,22 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](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
|
-
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
|
|
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
|
|
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,
|
|
1510
|
-
context.compilation.emitAsset(
|
|
1510
|
+
const source = new context.sources.OriginalSource(content, prefixedPath);
|
|
1511
|
+
context.compilation.emitAsset(prefixedPath, source);
|
|
1511
1512
|
emittedCount++;
|
|
1512
|
-
if (filesArray &&
|
|
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,
|
|
1603
|
-
context.compilation.emitAsset(
|
|
1604
|
+
const source = new context.sources.OriginalSource(content, prefixedBundledFileName);
|
|
1605
|
+
context.compilation.emitAsset(prefixedBundledFileName, source);
|
|
1604
1606
|
emittedCount++;
|
|
1605
|
-
if (filesArray) filesArray.add(
|
|
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
|
|
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
|
|
1664
|
-
|
|
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 ?
|
|
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:
|
|
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
|
-
|
|
2908
|
-
|
|
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 ?
|
|
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:
|
|
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.
|
|
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": {
|