Skip to content

Commit f999bc9

Browse files
committed
feat: add banner & footer option
1 parent 5b6f57b commit f999bc9

File tree

9 files changed

+113
-12
lines changed

9 files changed

+113
-12
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ If set to `true`, and you are only exporting a single item using `export default
9999
the output will use `export = ...` instead of the standard ES module syntax.
100100
This is useful for compatibility with CommonJS.
101101

102+
#### `banner`
103+
104+
Content to be added at the top of each generated `.d.ts` file.
105+
106+
#### `footer`
107+
108+
Content to be added at the bottom of each generated `.d.ts` file.
109+
102110
### `tsc` Options
103111

104112
> [!NOTE]

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
"birpc": "^2.5.0",
8181
"debug": "^4.4.1",
8282
"dts-resolver": "^2.1.2",
83-
"get-tsconfig": "^4.10.1"
83+
"get-tsconfig": "^4.10.1",
84+
"magic-string": "^0.30.19"
8485
},
8586
"devDependencies": {
8687
"@sxzz/eslint-config": "^7.1.4",

pnpm-lock.yaml

Lines changed: 11 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/banner.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import MagicString from 'magic-string'
2+
import { RE_DTS } from './filename.ts'
3+
import type { OptionsResolved } from './options.ts'
4+
import type { Plugin } from 'rolldown'
5+
6+
export function createBannerPlugin({
7+
banner,
8+
footer,
9+
}: Pick<OptionsResolved, 'banner' | 'footer'>): Plugin {
10+
return {
11+
name: 'rolldown-plugin-dts:banner',
12+
async renderChunk(code: string, chunk) {
13+
if (!RE_DTS.test(chunk.fileName)) {
14+
return
15+
}
16+
17+
const s = new MagicString(code)
18+
if (banner) {
19+
const code = await (typeof banner === 'function'
20+
? banner(chunk)
21+
: banner)
22+
if (code) s.prepend(`${code}\n`)
23+
}
24+
if (footer) {
25+
const code = await (typeof footer === 'function'
26+
? footer(chunk)
27+
: footer)
28+
if (code) s.append(`\n${code}`)
29+
}
30+
31+
return {
32+
code: s.toString(),
33+
get map() {
34+
return s.generateMap({
35+
source: chunk.fileName,
36+
includeContent: true,
37+
hires: 'boundary',
38+
})
39+
},
40+
}
41+
},
42+
}
43+
}

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Debug from 'debug'
2-
2+
import { createBannerPlugin } from './banner.ts'
33
import { createDtsInputPlugin } from './dts-input.ts'
44
import { createFakeJsPlugin } from './fake-js.ts'
55
import { createGeneratePlugin } from './generate.ts'
@@ -31,6 +31,9 @@ export function dts(options: Options = {}): Plugin[] {
3131
plugins.push(createGeneratePlugin(resolved))
3232
}
3333
plugins.push(createDtsResolvePlugin(resolved), createFakeJsPlugin(resolved))
34+
if (options.banner || options.footer) {
35+
plugins.push(createBannerPlugin(resolved))
36+
}
3437
return plugins
3538
}
3639

src/options.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type TsConfigJson,
77
type TsConfigJsonResolved,
88
} from 'get-tsconfig'
9+
import type { AddonFunction } from 'rolldown'
910
import type { IsolatedDeclarationsOptions } from 'rolldown/experimental'
1011

1112
//#region General Options
@@ -166,6 +167,15 @@ export interface TscOptions {
166167
* `false`.
167168
*/
168169
emitJs?: boolean
170+
171+
/**
172+
* Content to be added at the top of each generated `.d.ts` file.
173+
*/
174+
banner?: string | Promise<string> | AddonFunction
175+
/**
176+
* Content to be added at the bottom of each generated `.d.ts` file.
177+
*/
178+
footer?: string | Promise<string> | AddonFunction
169179
}
170180

171181
export interface Options extends GeneralOptions, TscOptions {
@@ -193,11 +203,13 @@ export interface Options extends GeneralOptions, TscOptions {
193203
}
194204

195205
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U
206+
type MarkPartial<T, K extends keyof T> = Omit<Required<T>, K> &
207+
Partial<Pick<T, K>>
196208

197209
export type OptionsResolved = Overwrite<
198-
Required<Omit<Options, 'compilerOptions'>>,
210+
MarkPartial<Omit<Options, 'compilerOptions'>, 'banner' | 'footer'>,
199211
{
200-
tsconfig: string | undefined
212+
tsconfig?: string
201213
oxc: IsolatedDeclarationsOptions | false
202214
tsconfigRaw: TsConfigJson
203215
}
@@ -215,6 +227,8 @@ export function resolveOptions({
215227
sourcemap,
216228
resolve = false,
217229
cjsDefault = false,
230+
banner,
231+
footer,
218232

219233
// tsc
220234
build = false,
@@ -313,6 +327,8 @@ export function resolveOptions({
313327
sourcemap,
314328
resolve,
315329
cjsDefault,
330+
banner,
331+
footer,
316332

317333
// tsc
318334
build,

tests/__snapshots__/index.test.ts.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`banner 1`] = `
4+
"// minimal.d.ts
5+
/* My Banner */
6+
//#region tests/fixtures/minimal.d.ts
7+
declare function foo(): void;
8+
//#endregion
9+
export { foo };
10+
/* My Footer minimal.d.ts */"
11+
`;
12+
313
exports[`basic 1`] = `
414
"// basic.d.ts
515
//#region tests/fixtures/basic.d.ts

tests/fixtures/minimal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function foo(): void {}

tests/index.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,19 @@ test('should error when file import cannot be found', async () => {
456456
]),
457457
).rejects.toThrow("Could not resolve './missing-file'")
458458
})
459+
460+
test('banner', async () => {
461+
const { snapshot } = await rolldownBuild(
462+
path.resolve(dirname, 'fixtures/minimal.ts'),
463+
[
464+
dts({
465+
emitDtsOnly: true,
466+
banner: '/* My Banner */',
467+
footer: (chunk) => `/* My Footer ${chunk.fileName} */`,
468+
}),
469+
],
470+
)
471+
expect(snapshot).toMatchSnapshot()
472+
expect(snapshot).toContain('/* My Banner */\n')
473+
expect(snapshot).toContain('\n/* My Footer minimal.d.ts */')
474+
})

0 commit comments

Comments
 (0)