Skip to content

Commit 09d0a2f

Browse files
authored
feat: support bundle DTS of multiple entries (#793)
1 parent 7d2a158 commit 09d0a2f

File tree

11 files changed

+204
-83
lines changed

11 files changed

+204
-83
lines changed

packages/plugin-dts/src/apiExtractor.ts

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type BundleOptions = {
1616
dtsExtension: string;
1717
banner?: string;
1818
footer?: string;
19-
dtsEntry: DtsEntry;
19+
dtsEntry: DtsEntry[];
2020
tsconfigPath?: string;
2121
bundledPackages?: string[];
2222
};
@@ -29,52 +29,56 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
2929
dtsExtension,
3030
banner,
3131
footer,
32-
dtsEntry = {
33-
name: 'index',
34-
path: 'index.d.ts',
35-
},
32+
dtsEntry,
3633
tsconfigPath = 'tsconfig.json',
3734
bundledPackages = [],
3835
} = options;
3936
try {
40-
const start = Date.now();
41-
const untrimmedFilePath = join(
42-
cwd,
43-
relative(cwd, distPath),
44-
`${dtsEntry.name}${dtsExtension}`,
45-
);
46-
const mainEntryPointFilePath = dtsEntry.path!.replace(/\?.*$/, '')!;
47-
const internalConfig = {
48-
mainEntryPointFilePath,
49-
bundledPackages,
50-
dtsRollup: {
51-
enabled: true,
52-
untrimmedFilePath,
53-
},
54-
compiler: {
55-
tsconfigFilePath: tsconfigPath,
56-
},
57-
projectFolder: cwd,
58-
};
37+
await Promise.all(
38+
dtsEntry.map(async (entry) => {
39+
const start = Date.now();
40+
const untrimmedFilePath = join(
41+
cwd,
42+
relative(cwd, distPath),
43+
`${entry.name}${dtsExtension}`,
44+
);
45+
const mainEntryPointFilePath = entry.path!.replace(/\?.*$/, '')!;
46+
const internalConfig = {
47+
mainEntryPointFilePath,
48+
bundledPackages,
49+
dtsRollup: {
50+
enabled: true,
51+
untrimmedFilePath,
52+
},
53+
compiler: {
54+
tsconfigFilePath: tsconfigPath,
55+
},
56+
projectFolder: cwd,
57+
};
5958

60-
const extractorConfig = ExtractorConfig.prepare({
61-
configObject: internalConfig,
62-
configObjectFullPath: undefined,
63-
packageJsonFullPath: join(cwd, 'package.json'),
64-
});
59+
const extractorConfig = ExtractorConfig.prepare({
60+
configObject: internalConfig,
61+
configObjectFullPath: undefined,
62+
packageJsonFullPath: join(cwd, 'package.json'),
63+
});
6564

66-
const extractorResult: ExtractorResult = Extractor.invoke(extractorConfig, {
67-
localBuild: true,
68-
});
65+
const extractorResult: ExtractorResult = Extractor.invoke(
66+
extractorConfig,
67+
{
68+
localBuild: true,
69+
},
70+
);
6971

70-
if (!extractorResult.succeeded) {
71-
throw new Error(`API Extractor error. ${color.gray(`(${name})`)}`);
72-
}
72+
if (!extractorResult.succeeded) {
73+
throw new Error(`API Extractor error. ${color.gray(`(${name})`)}`);
74+
}
7375

74-
await addBannerAndFooter(untrimmedFilePath, banner, footer);
76+
await addBannerAndFooter(untrimmedFilePath, banner, footer);
7577

76-
logger.info(
77-
`API Extractor bundle DTS succeeded: ${color.cyan(untrimmedFilePath)} in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
78+
logger.info(
79+
`API Extractor bundle DTS succeeded: ${color.cyan(untrimmedFilePath)} in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
80+
);
81+
}),
7882
);
7983
} catch (e) {
8084
logger.error('API Extractor Error');

packages/plugin-dts/src/dts.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from 'node:path';
1111
import { logger } from '@rsbuild/core';
1212
import color from 'picocolors';
13-
import type { DtsGenOptions } from './index';
13+
import type { DtsEntry, DtsGenOptions } from './index';
1414
import { emitDts } from './tsc';
1515
import { calcLongestCommonPath, ensureTempDeclarationDir } from './utils';
1616

@@ -175,21 +175,30 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
175175
? ensureTempDeclarationDir(cwd, name)
176176
: dtsEmitPath;
177177

178-
const { name: entryName, path: entryPath } = dtsEntry;
179-
let entry = '';
180-
181-
if (bundle === true && entryPath) {
182-
const entrySourcePath = isAbsolute(entryPath)
183-
? entryPath
184-
: join(cwd, entryPath);
185-
const relativePath = relative(rootDir, dirname(entrySourcePath));
186-
entry = join(declarationDir!, relativePath, basename(entrySourcePath))
187-
// Remove query in file path, such as RSLIB_ENTRY_QUERY.
188-
.replace(/\?.*$/, '')
189-
.replace(
190-
/\.(js|mjs|jsx|ts|mts|tsx|cjs|cts|cjsx|ctsx|mjsx|mtsx)$/,
191-
'.d.ts',
192-
);
178+
let dtsEntries: { name: string; path: string }[] = [];
179+
if (bundle === true) {
180+
dtsEntries = dtsEntry
181+
.map((entryObj) => {
182+
const { name: entryName, path: entryPath } = entryObj;
183+
if (!entryPath) return null;
184+
const entrySourcePath = isAbsolute(entryPath)
185+
? entryPath
186+
: join(cwd, entryPath);
187+
const relativePath = relative(rootDir, dirname(entrySourcePath));
188+
const newPath = join(
189+
declarationDir!,
190+
relativePath,
191+
basename(entrySourcePath),
192+
)
193+
// Remove query in file path, such as RSLIB_ENTRY_QUERY.
194+
.replace(/\?.*$/, '')
195+
.replace(
196+
/\.(js|mjs|jsx|ts|mts|tsx|cjs|cts|cjsx|ctsx|mjsx|mtsx)$/,
197+
'.d.ts',
198+
);
199+
return { name: entryName, path: newPath };
200+
})
201+
.filter(Boolean) as Required<DtsEntry>[];
193202
}
194203

195204
const bundleDtsIfNeeded = async () => {
@@ -199,10 +208,7 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
199208
name,
200209
cwd,
201210
distPath: dtsEmitPath,
202-
dtsEntry: {
203-
name: entryName,
204-
path: entry,
205-
},
211+
dtsEntry: dtsEntries,
206212
tsconfigPath,
207213
dtsExtension,
208214
banner,

packages/plugin-dts/src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export type DtsGenOptions = PluginDtsOptions & {
4949
name: string;
5050
cwd: string;
5151
isWatch: boolean;
52-
dtsEntry: DtsEntry;
52+
dtsEntry: DtsEntry[];
5353
dtsEmitPath: string;
5454
build?: boolean;
5555
tsconfigPath: string;
@@ -89,8 +89,9 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({
8989

9090
const { config } = environment;
9191

92-
// TODO: @microsoft/api-extractor only support single entry to bundle DTS
93-
// use first element of Record<string, string> type entry config
92+
// @microsoft/api-extractor only support single entry to bundle DTS
93+
// see https://github.com/microsoft/rushstack/issues/1596#issuecomment-546790721
94+
// we support multiple entries by calling api-extractor multiple times
9495
const dtsEntry = processSourceEntry(
9596
options.bundle!,
9697
config.source?.entry,

packages/plugin-dts/src/utils.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -406,26 +406,25 @@ export async function processDtsFiles(
406406
export function processSourceEntry(
407407
bundle: boolean,
408408
entryConfig: NonNullable<RsbuildConfig['source']>['entry'],
409-
): DtsEntry {
409+
): DtsEntry[] {
410410
if (!bundle) {
411-
return {
412-
name: undefined,
413-
path: undefined,
414-
};
411+
return [];
415412
}
416413

417414
if (
418415
entryConfig &&
419416
Object.values(entryConfig).every((val) => typeof val === 'string')
420417
) {
421-
return {
422-
name: Object.keys(entryConfig)[0] as string,
423-
path: Object.values(entryConfig)[0] as string,
424-
};
418+
return Object.entries(entryConfig as Record<string, string>).map(
419+
([name, path]) => ({
420+
name,
421+
path,
422+
}),
423+
);
425424
}
426425

427426
throw new Error(
428-
'@microsoft/api-extractor only support single entry of Record<string, string> type to bundle DTS, please check your entry config.',
427+
'@microsoft/api-extractor only support entry of Record<string, string> type to bundle DTS, please check your entry config.',
429428
);
430429
}
431430

pnpm-lock.yaml

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

tests/integration/dts/__snapshots__/index.test.ts.snap

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,59 @@ export { }
6161
}
6262
`;
6363

64+
exports[`dts when bundle: true > multiple entries 3`] = `
65+
[
66+
"export declare const num1 = 1;
67+
68+
export declare const num2 = 2;
69+
70+
export declare const num3 = 3;
71+
72+
export declare const numSum: number;
73+
74+
export declare const str1 = "str1";
75+
76+
export declare const str2 = "str2";
77+
78+
export declare const str3 = "str3";
79+
80+
export declare const strSum: string;
81+
82+
export { }
83+
",
84+
"export declare const num1 = 1;
85+
86+
export declare const num2 = 2;
87+
88+
export declare const num3 = 3;
89+
90+
export declare const numSum: number;
91+
92+
export declare const str1 = "str1";
93+
94+
export declare const str2 = "str2";
95+
96+
export declare const str3 = "str3";
97+
98+
export declare const strSum: string;
99+
100+
export { }
101+
",
102+
"export declare const numSum: number;
103+
104+
export declare const strSum: string;
105+
106+
export { }
107+
",
108+
"export declare const numSum: number;
109+
110+
export declare const strSum: string;
111+
112+
export { }
113+
",
114+
]
115+
`;
116+
64117
exports[`dts when bundle: true > rootdir calculation should ignore declaration files 3`] = `
65118
{
66119
"cjs": "export declare const num1 = 1;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "dts-bundle-multiple-entries-test",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module"
6+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { defineConfig } from '@rslib/core';
2+
import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper';
3+
4+
export default defineConfig({
5+
lib: [
6+
generateBundleEsmConfig({
7+
dts: {
8+
bundle: true,
9+
},
10+
}),
11+
generateBundleCjsConfig({
12+
dts: {
13+
bundle: true,
14+
},
15+
}),
16+
],
17+
source: {
18+
entry: {
19+
index: '../__fixtures__/src/index.ts',
20+
sum: '../__fixtures__/src/sum.ts',
21+
},
22+
tsconfigPath: '../__fixtures__/tsconfig.json',
23+
},
24+
});

tests/integration/dts/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createTempFiles,
77
globContentJSON,
88
proxyConsole,
9+
queryContent,
910
} from 'test-helper';
1011
import { describe, expect, test } from 'vitest';
1112

@@ -339,6 +340,43 @@ describe('dts when bundle: true', () => {
339340
]
340341
`);
341342
});
343+
344+
test('multiple entries', async () => {
345+
const fixturePath = join(__dirname, 'bundle', 'multiple-entries');
346+
const { files, contents } = await buildAndGetResults({
347+
fixturePath,
348+
type: 'dts',
349+
});
350+
351+
expect(files.esm).toMatchInlineSnapshot(`
352+
[
353+
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/esm/index.d.ts",
354+
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/esm/sum.d.ts",
355+
]
356+
`);
357+
358+
expect(files.cjs).toMatchInlineSnapshot(`
359+
[
360+
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/cjs/index.d.ts",
361+
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/cjs/sum.d.ts",
362+
]
363+
`);
364+
365+
const { content: indexEsm } = queryContent(contents.esm, 'index.d.ts', {
366+
basename: true,
367+
});
368+
const { content: indexCjs } = queryContent(contents.cjs, 'index.d.ts', {
369+
basename: true,
370+
});
371+
const { content: sumEsm } = queryContent(contents.esm, 'sum.d.ts', {
372+
basename: true,
373+
});
374+
const { content: sumCjs } = queryContent(contents.cjs, 'sum.d.ts', {
375+
basename: true,
376+
});
377+
378+
expect([indexEsm, indexCjs, sumEsm, sumCjs]).toMatchSnapshot();
379+
});
342380
});
343381

344382
describe('dts when build: true', () => {

website/docs/en/config/lib/dts.mdx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,6 @@ export default {
8686
};
8787
```
8888

89-
::: note
90-
91-
[@microsoft/api-extractor](https://www.npmjs.com/package/@microsoft/api-extractor) only supports bundle DTS for single entry. If you want to generate bundle DTS for multiple entries, you can add extra lib configuration in [lib](/config/lib/) field to split multiple entries into multiple lib configurations.
92-
93-
:::
94-
9589
#### Handle third-party packages
9690

9791
When we bundle DTS files, we should specify which third-party package types need to be bundled, refer to the [Handle Third-Party Dependencies](/guide/advanced/third-party-deps) documentation for more details about externals related configurations.

0 commit comments

Comments
 (0)