@savvy-web/tsdown-plugins 1.0.1 → 1.1.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/build/build-target-groups.js +2 -1
- package/index.d.ts +24 -2
- package/manifest/emit-manifest.js +24 -4
- package/manifest/transform.js +18 -8
- package/package.json +1 -1
|
@@ -108,6 +108,7 @@ async function buildTargetGroups(options) {
|
|
|
108
108
|
transform: options.transform,
|
|
109
109
|
sourceDir: options.cwd,
|
|
110
110
|
dual: options.dualExports ?? js.format.includes("cjs"),
|
|
111
|
+
emitDts: options.emitDts !== false,
|
|
111
112
|
...options.subdirExports !== void 0 ? { subdirExports: options.subdirExports } : {},
|
|
112
113
|
...options.exeRewrite !== void 0 ? { exeRewrite: options.exeRewrite } : {}
|
|
113
114
|
}) : void 0;
|
|
@@ -141,7 +142,7 @@ async function buildTargetGroups(options) {
|
|
|
141
142
|
]
|
|
142
143
|
}));
|
|
143
144
|
const dtsNeverBundle = [...partExternals ?? [], ...partDtsExternals ?? []];
|
|
144
|
-
if (Object.keys(dts.entry).length === 0) continue;
|
|
145
|
+
if (options.emitDts === false || Object.keys(dts.entry).length === 0) continue;
|
|
145
146
|
const dtsDeps = dtsNeverBundle.length > 0 || partBundleNodeModules || dts.bundledPackages ? { deps: {
|
|
146
147
|
...dtsNeverBundle.length > 0 ? { neverBundle: dtsNeverBundle } : {},
|
|
147
148
|
...partBundleNodeModules ? {
|
package/index.d.ts
CHANGED
|
@@ -68,9 +68,14 @@ interface ExeRewrite {
|
|
|
68
68
|
*
|
|
69
69
|
* Export keys in `subdirExports` are built into an isolated `<key>/index.*` subdir (e.g. an
|
|
70
70
|
* RSPress `./runtime`), so their conditions gain an `/index` segment.
|
|
71
|
+
*
|
|
72
|
+
* `emitDts` gates the `types` condition on every GENERATED (non-ambient) TS export: pass `false`
|
|
73
|
+
* when the build's dts pass was skipped (issue #198) so the manifest does not point at a `.d.ts`
|
|
74
|
+
* that was never written. Defaults to `true`. Ambient `.d.ts` exports (hand-authored, copied
|
|
75
|
+
* verbatim regardless of the dts pass) are unaffected — their `types` condition is always kept.
|
|
71
76
|
* @public
|
|
72
77
|
*/
|
|
73
|
-
declare function transformExports(exports: unknown, dual?: DualExports, subdirExports?: ReadonlySet<string
|
|
78
|
+
declare function transformExports(exports: unknown, dual?: DualExports, subdirExports?: ReadonlySet<string>, emitDts?: boolean): unknown;
|
|
74
79
|
/**
|
|
75
80
|
* Rewrite bin: TS targets to bin/[command].js (string to bin/cli.js); strip leading ./ otherwise.
|
|
76
81
|
*
|
|
@@ -93,6 +98,12 @@ interface TransformManifestOptions {
|
|
|
93
98
|
readonly subdirExports?: ReadonlySet<string> | undefined;
|
|
94
99
|
/** When set, rewrite exports/bin values equal to `source` to the SEA path and add it to `files`. */
|
|
95
100
|
readonly exeRewrite?: ExeRewrite | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Whether the build's dts pass ran. `false` omits the `types` condition from generated (non-
|
|
103
|
+
* ambient) TS exports — the manifest must not point at a `.d.ts` that was never written
|
|
104
|
+
* (issue #198). Defaults to `true`.
|
|
105
|
+
*/
|
|
106
|
+
readonly emitDts?: boolean | undefined;
|
|
96
107
|
}
|
|
97
108
|
/**
|
|
98
109
|
* Apply the full standard manifest transform (excluding catalog resolution, done upstream).
|
|
@@ -124,6 +135,8 @@ interface BuildEmittedManifestOptions {
|
|
|
124
135
|
readonly subdirExports?: ReadonlySet<string> | undefined;
|
|
125
136
|
/** When set, rewrite exports/bin values equal to the exe source to the SEA path and add it to `files`. */
|
|
126
137
|
readonly exeRewrite?: ExeRewrite | undefined;
|
|
138
|
+
/** Whether the dts pass ran; `false` omits `types` conditions from the emitted manifest (issue #198). Defaults to `true`. */
|
|
139
|
+
readonly emitDts?: boolean | undefined;
|
|
127
140
|
}
|
|
128
141
|
/**
|
|
129
142
|
* Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms).
|
|
@@ -147,6 +160,8 @@ interface EmitManifestOptions {
|
|
|
147
160
|
readonly subdirExports?: ReadonlySet<string> | undefined;
|
|
148
161
|
/** When set, rewrite exports/bin values equal to the exe source to the SEA path and add it to `files`. */
|
|
149
162
|
readonly exeRewrite?: ExeRewrite | undefined;
|
|
163
|
+
/** Whether the dts pass ran; `false` omits `types` conditions from the emitted manifest (issue #198). Defaults to `true`. */
|
|
164
|
+
readonly emitDts?: boolean | undefined;
|
|
150
165
|
}
|
|
151
166
|
/**
|
|
152
167
|
* Rolldown plugin: emit the transformed package.json + LICENSE/README into the output pkg/ root.
|
|
@@ -660,9 +675,16 @@ interface BuildTargetGroupsOptions {
|
|
|
660
675
|
* Emit a per-module (`unbundle: true`) declaration tree into `dist/prod/<id>/declarations/` per
|
|
661
676
|
* group, in addition to the bundled dts in `pkg/`. API Extractor's diagnostics-run input for the
|
|
662
677
|
* meta pass. Prod-only; the bundler sets it for `--target prod`. Absent → no third pass
|
|
663
|
-
* (byte-identical to the two-pass default). Not captured by the collector.
|
|
678
|
+
* (byte-identical to the two-pass default). Not captured by the collector. Has no effect when
|
|
679
|
+
* `emitDts` is `false` — the declarations pass exists only to feed the dts-derived meta pass.
|
|
664
680
|
*/
|
|
665
681
|
readonly emitDeclarations?: boolean | undefined;
|
|
682
|
+
/**
|
|
683
|
+
* When `false`, skip BOTH the bundled per-entry dts pass (Pass 2) and the prod per-module
|
|
684
|
+
* declarations pass (Pass 3) — no TypeScript compiler load. The JS pass (Pass 1) and
|
|
685
|
+
* `copyPublicDir` still run. Defaults to `true` (current two/three-pass behavior).
|
|
686
|
+
*/
|
|
687
|
+
readonly emitDts?: boolean | undefined;
|
|
666
688
|
}
|
|
667
689
|
/**
|
|
668
690
|
* Run tsdown.build() per TargetGroup. Composable so the escape hatch gets multi-group too.
|
|
@@ -4,6 +4,24 @@ import { join } from "node:path";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
|
|
6
6
|
//#region src/manifest/emit-manifest.ts
|
|
7
|
+
const DEPENDENCY_FIELDS = [
|
|
8
|
+
"dependencies",
|
|
9
|
+
"devDependencies",
|
|
10
|
+
"peerDependencies",
|
|
11
|
+
"optionalDependencies"
|
|
12
|
+
];
|
|
13
|
+
const isCatalogOrWorkspaceSpec = (spec) => typeof spec === "string" && (spec.startsWith("catalog:") || spec.startsWith("workspace:"));
|
|
14
|
+
/**
|
|
15
|
+
* Whether any of `pkg`'s four dependency fields carries at least one `catalog:`/`workspace:`
|
|
16
|
+
* specifier. `resolveManifest` returns a manifest with none of these unchanged, so callers can
|
|
17
|
+
* skip the CatalogResolver + pnpm-workspace + lockfile assembly entirely when this is false.
|
|
18
|
+
*/
|
|
19
|
+
function manifestNeedsCatalogResolution(pkg) {
|
|
20
|
+
return DEPENDENCY_FIELDS.some((field) => {
|
|
21
|
+
const deps = pkg[field];
|
|
22
|
+
return typeof deps === "object" && deps !== null && Object.values(deps).some(isCatalogOrWorkspaceSpec);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
7
25
|
/**
|
|
8
26
|
* Compute the final manifest bytes for a TargetGroup (catalog resolution + standard transforms).
|
|
9
27
|
*
|
|
@@ -11,7 +29,7 @@ import { readFile } from "node:fs/promises";
|
|
|
11
29
|
*/
|
|
12
30
|
async function buildEmittedManifest(options) {
|
|
13
31
|
const { pkg, targetGroup, devManifest, transform } = options;
|
|
14
|
-
const shouldResolve = targetGroup.isProd || devManifest === "resolve";
|
|
32
|
+
const shouldResolve = (targetGroup.isProd || devManifest === "resolve") && manifestNeedsCatalogResolution(pkg);
|
|
15
33
|
let base = pkg;
|
|
16
34
|
if (shouldResolve) base = await resolveManifest(pkg);
|
|
17
35
|
base = {
|
|
@@ -25,7 +43,8 @@ async function buildEmittedManifest(options) {
|
|
|
25
43
|
}) : void 0,
|
|
26
44
|
dual: options.dual ?? false,
|
|
27
45
|
subdirExports: options.subdirExports,
|
|
28
|
-
exeRewrite: options.exeRewrite
|
|
46
|
+
exeRewrite: options.exeRewrite,
|
|
47
|
+
emitDts: options.emitDts ?? true
|
|
29
48
|
});
|
|
30
49
|
}
|
|
31
50
|
/**
|
|
@@ -45,7 +64,8 @@ function emitManifest(options) {
|
|
|
45
64
|
transform: options.transform,
|
|
46
65
|
dual: options.dual,
|
|
47
66
|
subdirExports: options.subdirExports,
|
|
48
|
-
exeRewrite: options.exeRewrite
|
|
67
|
+
exeRewrite: options.exeRewrite,
|
|
68
|
+
emitDts: options.emitDts
|
|
49
69
|
});
|
|
50
70
|
this.emitFile({
|
|
51
71
|
type: "asset",
|
|
@@ -65,4 +85,4 @@ function emitManifest(options) {
|
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
//#endregion
|
|
68
|
-
export { buildEmittedManifest, emitManifest };
|
|
88
|
+
export { buildEmittedManifest, emitManifest, manifestNeedsCatalogResolution };
|
package/manifest/transform.js
CHANGED
|
@@ -77,9 +77,14 @@ const toBuiltDts = (exportKey, subdirExports) => toBuiltJs(exportKey, subdirExpo
|
|
|
77
77
|
const toBuiltCjs = (exportKey, subdirExports) => toBuiltJs(exportKey, subdirExports).replace(/\.js$/, ".cjs");
|
|
78
78
|
const isDeclarationFile = (p) => p.endsWith(".d.ts") || p.endsWith(".d.cts") || p.endsWith(".d.mts");
|
|
79
79
|
const isTs = (p) => !isDeclarationFile(p) && (p.endsWith(".ts") || p.endsWith(".tsx"));
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Build the conditions object for a TS export target (adds require when dual-format). `emitDts`
|
|
82
|
+
* gates the `types` condition: when `false` (the build's dts pass was skipped, see issue #198),
|
|
83
|
+
* omitting `types` avoids pointing the published manifest at a declaration file that was never
|
|
84
|
+
* written. Defaults to `true` (current behavior, byte-identical).
|
|
85
|
+
*/
|
|
86
|
+
const tsConditions = (exportKey, dual, subdirExports, emitDts = true) => ({
|
|
87
|
+
...emitDts ? { types: toBuiltDts(exportKey, subdirExports) } : {},
|
|
83
88
|
import: toBuiltJs(exportKey, subdirExports),
|
|
84
89
|
...dual ? { require: toBuiltCjs(exportKey, subdirExports) } : {}
|
|
85
90
|
});
|
|
@@ -103,10 +108,15 @@ function stripPublicPrefix(value) {
|
|
|
103
108
|
*
|
|
104
109
|
* Export keys in `subdirExports` are built into an isolated `<key>/index.*` subdir (e.g. an
|
|
105
110
|
* RSPress `./runtime`), so their conditions gain an `/index` segment.
|
|
111
|
+
*
|
|
112
|
+
* `emitDts` gates the `types` condition on every GENERATED (non-ambient) TS export: pass `false`
|
|
113
|
+
* when the build's dts pass was skipped (issue #198) so the manifest does not point at a `.d.ts`
|
|
114
|
+
* that was never written. Defaults to `true`. Ambient `.d.ts` exports (hand-authored, copied
|
|
115
|
+
* verbatim regardless of the dts pass) are unaffected — their `types` condition is always kept.
|
|
106
116
|
* @public
|
|
107
117
|
*/
|
|
108
|
-
function transformExports(exports, dual = false, subdirExports) {
|
|
109
|
-
if (typeof exports === "string") return isTs(exports) ? tsConditions(".", isDualKey(dual, "."), subdirExports) : stripPublicPrefix(exports);
|
|
118
|
+
function transformExports(exports, dual = false, subdirExports, emitDts = true) {
|
|
119
|
+
if (typeof exports === "string") return isTs(exports) ? tsConditions(".", isDualKey(dual, "."), subdirExports, emitDts) : stripPublicPrefix(exports);
|
|
110
120
|
if (exports && typeof exports === "object") {
|
|
111
121
|
const out = {};
|
|
112
122
|
for (const [key, value] of Object.entries(exports)) {
|
|
@@ -120,8 +130,8 @@ function transformExports(exports, dual = false, subdirExports) {
|
|
|
120
130
|
out[key] = { types: `./${ambientOutName(key, cls.source)}` };
|
|
121
131
|
continue;
|
|
122
132
|
}
|
|
123
|
-
if (typeof value === "string" && isTs(value)) out[key] = tsConditions(key, isDualKey(dual, key), subdirExports);
|
|
124
|
-
else out[key] = transformExports(value, dual, subdirExports);
|
|
133
|
+
if (typeof value === "string" && isTs(value)) out[key] = tsConditions(key, isDualKey(dual, key), subdirExports, emitDts);
|
|
134
|
+
else out[key] = transformExports(value, dual, subdirExports, emitDts);
|
|
125
135
|
}
|
|
126
136
|
return out;
|
|
127
137
|
}
|
|
@@ -184,7 +194,7 @@ function transformManifest(pkg, options = {}) {
|
|
|
184
194
|
}
|
|
185
195
|
if (result.exports !== void 0 && result.exports !== null) {
|
|
186
196
|
const original = result.exports;
|
|
187
|
-
const transformed = transformExports(original, options.dual ?? false, options.subdirExports);
|
|
197
|
+
const transformed = transformExports(original, options.dual ?? false, options.subdirExports, options.emitDts ?? true);
|
|
188
198
|
const asMap = typeof original === "string" ? { ".": transformed } : { ...transformed };
|
|
189
199
|
if (!("./package.json" in asMap)) asMap["./package.json"] = "./package.json";
|
|
190
200
|
result.exports = asMap;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/tsdown-plugins",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Interface-only tsdown/rolldown plugin pack powering @savvy-web/bundler",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/tsdown-plugins",
|