Skip to content

Commit 665e5ac

Browse files
authored
feat(plugin): add tsdownConfig and tsdownConfigResolved plugin hooks (#918)
* feat(plugin): add `tsdownConfig` and `tsdownConfigResolved` plugin hooks Introduce two tsdown-specific per-plugin lifecycle hooks modeled on Vite's `config` and `configResolved`: - `tsdownConfig(config, env)` — called before the user config is resolved. May mutate in place or return a partial `UserConfig` to be deep-merged. - `tsdownConfigResolved(resolvedConfig)` — read-only notification fired once per produced `ResolvedConfig` (per output format). Both hooks are detected on user plugins via duck-typing, so existing Rolldown plugins continue to work unchanged. A `TsdownPlugin` interface, `TsdownConfigEnv` interface, and `flattenPlugins` helper are exported from `tsdown/plugins`. * refactor * refactor(plugin): simplify `TsdownConfigEnv` to only `inlineConfig` `watch` and `cwd` are already reachable via the first `config` argument passed to `tsdownConfig`, so keeping them in env duplicates data and risks going stale. Drop both — `inlineConfig` remains because it is the only piece of context that cannot be derived from `UserConfig`. * refactor(plugin): replace `TsdownConfigEnv` with `InlineConfig` The env object only wrapped a single `inlineConfig` field, so pass the `InlineConfig` directly as the second argument instead. Drops the `TsdownConfigEnv` interface and its re-exports. * feat(plugin): add `TsdownPluginOption` type Mirrors Rolldown's `RolldownPluginOption` but with `TsdownPlugin` as the atom, so that `UserConfig.plugins` entries get proper type-checking for the new `tsdownConfig` / `tsdownConfigResolved` hooks. * fix * update * refactor
1 parent e6021f5 commit 665e5ac

File tree

6 files changed

+421
-4
lines changed

6 files changed

+421
-4
lines changed

__snapshots__/tsnapi/config.snapshot.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface UserConfig {
2424
checks?: ChecksOptions & {
2525
legacyCjs?: boolean;
2626
};
27-
plugins?: InputOptions["plugins"];
27+
plugins?: TsdownPluginOption;
2828
inputOptions?: InputOptions | ((_: InputOptions, _: NormalizedFormat, _: {
2929
cjsDts: boolean;
3030
}) => Awaitable<InputOptions | void | null>);

__snapshots__/tsnapi/index.snapshot.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export interface TsdownHooks {
125125
chunks: RolldownChunk[];
126126
}) => void | Promise<void>;
127127
}
128+
export interface TsdownPlugin<A = any> extends Plugin<A> {
129+
tsdownConfig?: (_: UserConfig, _: InlineConfig) => Awaitable<UserConfig | void | null>;
130+
tsdownConfigResolved?: (_: ResolvedConfig) => Awaitable<void>;
131+
}
128132
export interface UserConfig {
129133
entry?: TsdownInputOption;
130134
deps?: DepsConfig;
@@ -148,7 +152,7 @@ export interface UserConfig {
148152
checks?: ChecksOptions & {
149153
legacyCjs?: boolean;
150154
};
151-
plugins?: InputOptions["plugins"];
155+
plugins?: TsdownPluginOption;
152156
inputOptions?: InputOptions | ((_: InputOptions, _: NormalizedFormat, _: {
153157
cjsDts: boolean;
154158
}) => Awaitable<InputOptions | void | null>);
@@ -244,6 +248,9 @@ export type RolldownChunk = (OutputChunk | OutputAsset) & {
244248
};
245249
export type Sourcemap = boolean | "inline" | "hidden";
246250
export type TsdownInputOption = Arrayable<string | Record<string, Arrayable<string>>>;
251+
export type TsdownPluginOption<A = any> = Awaitable<TsdownPlugin<A> | RolldownPlugin<A> | {
252+
name: string;
253+
} | undefined | null | void | false | TsdownPluginOption<A>[]>;
247254
export type UserConfigExport = Awaitable<Arrayable<UserConfig> | UserConfigFn>;
248255
export type UserConfigFn = (_: InlineConfig, _: {
249256
ci: boolean;

src/config/options.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { resolveDepsConfig } from '../features/deps.ts'
1010
import { resolveEntry } from '../features/entry.ts'
1111
import { validateSea } from '../features/exe.ts'
1212
import { hasExportsTypes } from '../features/pkg/exports.ts'
13+
import { flattenPlugins } from '../features/plugin.ts'
1314
import { resolveTarget } from '../features/target.ts'
1415
import { resolveTsconfig } from '../features/tsconfig.ts'
1516
import {
@@ -44,6 +45,19 @@ export async function resolveUserConfig(
4445
userConfig: UserConfig,
4546
inlineConfig: InlineConfig,
4647
): Promise<ResolvedConfig[]> {
48+
// Dispatch `tsdownConfig` hook on user plugins before any resolution work.
49+
// Plugins are snapshotted: new plugins added by a hook don't re-dispatch,
50+
// preventing infinite recursion and matching Vite's `config` semantics.
51+
{
52+
const flat = await flattenPlugins(userConfig.plugins)
53+
for (const plugin of flat) {
54+
const result = await plugin.tsdownConfig?.(userConfig, inlineConfig)
55+
if (result) {
56+
userConfig = mergeConfig(userConfig, result)
57+
}
58+
}
59+
}
60+
4761
let {
4862
entry,
4963
format,
@@ -323,7 +337,7 @@ export async function resolveUserConfig(
323337
? (Object.keys(format) as Format[])
324338
: resolveComma(toArray<Format>(format, 'esm'))
325339

326-
return formats.map((fmt, idx): ResolvedConfig => {
340+
const resolvedConfigs = formats.map((fmt, idx): ResolvedConfig => {
327341
const once = idx === 0
328342
const overrides = objectFormat ? format[fmt] : undefined
329343
return {
@@ -336,6 +350,18 @@ export async function resolveUserConfig(
336350
...overrides,
337351
}
338352
})
353+
354+
// Dispatch `tsdownConfigResolved` hook. Re-flatten from the final plugin
355+
// list so plugins added during `tsdownConfig` (via fromVite or in-place
356+
// mutation) still participate. Fires once per resolved format.
357+
for (const resolved of resolvedConfigs) {
358+
const finalPlugins = await flattenPlugins(resolved.plugins)
359+
for (const plugin of finalPlugins) {
360+
await plugin.tsdownConfigResolved?.(resolved)
361+
}
362+
}
363+
364+
return resolvedConfigs
339365
}
340366

341367
/** filter env variables by prefixes */

src/config/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
import type { AttwOptions } from '../features/pkg/attw.ts'
2323
import type { ExportsOptions } from '../features/pkg/exports.ts'
2424
import type { PublintOptions } from '../features/pkg/publint.ts'
25+
import type { TsdownPlugin, TsdownPluginOption } from '../features/plugin.ts'
2526
import type { ReportOptions } from '../features/report.ts'
2627
import type { RolldownChunk, TsdownBundle } from '../utils/chunks.ts'
2728
import type { Logger, LogLevel } from '../utils/logger.ts'
@@ -118,6 +119,8 @@ export type {
118119
TreeshakingOptions,
119120
TsdownBundle,
120121
TsdownHooks,
122+
TsdownPlugin,
123+
TsdownPluginOption,
121124
UnusedOptions,
122125
}
123126

@@ -328,7 +331,7 @@ export interface UserConfig {
328331
legacyCjs?: boolean
329332
}
330333

331-
plugins?: InputOptions['plugins']
334+
plugins?: TsdownPluginOption
332335

333336
/**
334337
* Use with caution; ensure you understand the implications.

0 commit comments

Comments
 (0)