Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 42 additions & 38 deletions packages/plugin-dts/src/apiExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type BundleOptions = {
dtsExtension: string;
banner?: string;
footer?: string;
dtsEntry: DtsEntry;
dtsEntry: DtsEntry[];
tsconfigPath?: string;
bundledPackages?: string[];
};
Expand All @@ -29,52 +29,56 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
dtsExtension,
banner,
footer,
dtsEntry = {
name: 'index',
path: 'index.d.ts',
},
dtsEntry,
tsconfigPath = 'tsconfig.json',
bundledPackages = [],
} = options;
try {
const start = Date.now();
const untrimmedFilePath = join(
cwd,
relative(cwd, distPath),
`${dtsEntry.name}${dtsExtension}`,
);
const mainEntryPointFilePath = dtsEntry.path!.replace(/\?.*$/, '')!;
const internalConfig = {
mainEntryPointFilePath,
bundledPackages,
dtsRollup: {
enabled: true,
untrimmedFilePath,
},
compiler: {
tsconfigFilePath: tsconfigPath,
},
projectFolder: cwd,
};
await Promise.all(
dtsEntry.map(async (entry) => {
const start = Date.now();
const untrimmedFilePath = join(
cwd,
relative(cwd, distPath),
`${entry.name}${dtsExtension}`,
);
const mainEntryPointFilePath = entry.path!.replace(/\?.*$/, '')!;
const internalConfig = {
mainEntryPointFilePath,
bundledPackages,
dtsRollup: {
enabled: true,
untrimmedFilePath,
},
compiler: {
tsconfigFilePath: tsconfigPath,
},
projectFolder: cwd,
};

const extractorConfig = ExtractorConfig.prepare({
configObject: internalConfig,
configObjectFullPath: undefined,
packageJsonFullPath: join(cwd, 'package.json'),
});
const extractorConfig = ExtractorConfig.prepare({
configObject: internalConfig,
configObjectFullPath: undefined,
packageJsonFullPath: join(cwd, 'package.json'),
});

const extractorResult: ExtractorResult = Extractor.invoke(extractorConfig, {
localBuild: true,
});
const extractorResult: ExtractorResult = Extractor.invoke(
extractorConfig,
{
localBuild: true,
},
);

if (!extractorResult.succeeded) {
throw new Error(`API Extractor error. ${color.gray(`(${name})`)}`);
}
if (!extractorResult.succeeded) {
throw new Error(`API Extractor error. ${color.gray(`(${name})`)}`);
}

await addBannerAndFooter(untrimmedFilePath, banner, footer);
await addBannerAndFooter(untrimmedFilePath, banner, footer);

logger.info(
`API Extractor bundle DTS succeeded: ${color.cyan(untrimmedFilePath)} in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
logger.info(
`API Extractor bundle DTS succeeded: ${color.cyan(untrimmedFilePath)} in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
);
}),
);
} catch (e) {
logger.error('API Extractor Error');
Expand Down
46 changes: 26 additions & 20 deletions packages/plugin-dts/src/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from 'node:path';
import { logger } from '@rsbuild/core';
import color from 'picocolors';
import type { DtsGenOptions } from './index';
import type { DtsEntry, DtsGenOptions } from './index';
import { emitDts } from './tsc';
import { calcLongestCommonPath, ensureTempDeclarationDir } from './utils';

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

const { name: entryName, path: entryPath } = dtsEntry;
let entry = '';

if (bundle === true && entryPath) {
const entrySourcePath = isAbsolute(entryPath)
? entryPath
: join(cwd, entryPath);
const relativePath = relative(rootDir, dirname(entrySourcePath));
entry = join(declarationDir!, relativePath, basename(entrySourcePath))
// Remove query in file path, such as RSLIB_ENTRY_QUERY.
.replace(/\?.*$/, '')
.replace(
/\.(js|mjs|jsx|ts|mts|tsx|cjs|cts|cjsx|ctsx|mjsx|mtsx)$/,
'.d.ts',
);
let dtsEntries: { name: string; path: string }[] = [];
if (bundle === true) {
dtsEntries = dtsEntry
.map((entryObj) => {
const { name: entryName, path: entryPath } = entryObj;
if (!entryPath) return null;
const entrySourcePath = isAbsolute(entryPath)
? entryPath
: join(cwd, entryPath);
const relativePath = relative(rootDir, dirname(entrySourcePath));
const newPath = join(
declarationDir!,
relativePath,
basename(entrySourcePath),
)
// Remove query in file path, such as RSLIB_ENTRY_QUERY.
.replace(/\?.*$/, '')
.replace(
/\.(js|mjs|jsx|ts|mts|tsx|cjs|cts|cjsx|ctsx|mjsx|mtsx)$/,
'.d.ts',
);
return { name: entryName, path: newPath };
})
.filter(Boolean) as Required<DtsEntry>[];
}

const bundleDtsIfNeeded = async () => {
Expand All @@ -199,10 +208,7 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
name,
cwd,
distPath: dtsEmitPath,
dtsEntry: {
name: entryName,
path: entry,
},
dtsEntry: dtsEntries,
tsconfigPath,
dtsExtension,
banner,
Expand Down
7 changes: 4 additions & 3 deletions packages/plugin-dts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type DtsGenOptions = PluginDtsOptions & {
name: string;
cwd: string;
isWatch: boolean;
dtsEntry: DtsEntry;
dtsEntry: DtsEntry[];
dtsEmitPath: string;
build?: boolean;
tsconfigPath: string;
Expand Down Expand Up @@ -89,8 +89,9 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({

const { config } = environment;

// TODO: @microsoft/api-extractor only support single entry to bundle DTS
// use first element of Record<string, string> type entry config
// @microsoft/api-extractor only support single entry to bundle DTS
// see https://github.com/microsoft/rushstack/issues/1596#issuecomment-546790721
// we support multiple entries by calling api-extractor multiple times
const dtsEntry = processSourceEntry(
options.bundle!,
config.source?.entry,
Expand Down
19 changes: 9 additions & 10 deletions packages/plugin-dts/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,26 +406,25 @@ export async function processDtsFiles(
export function processSourceEntry(
bundle: boolean,
entryConfig: NonNullable<RsbuildConfig['source']>['entry'],
): DtsEntry {
): DtsEntry[] {
if (!bundle) {
return {
name: undefined,
path: undefined,
};
return [];
}

if (
entryConfig &&
Object.values(entryConfig).every((val) => typeof val === 'string')
) {
return {
name: Object.keys(entryConfig)[0] as string,
path: Object.values(entryConfig)[0] as string,
};
return Object.entries(entryConfig as Record<string, string>).map(
([name, path]) => ({
name,
path,
}),
);
}

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

Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions tests/integration/dts/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,59 @@ export { }
}
`;

exports[`dts when bundle: true > multiple entries 3`] = `
[
"export declare const num1 = 1;

export declare const num2 = 2;

export declare const num3 = 3;

export declare const numSum: number;

export declare const str1 = "str1";

export declare const str2 = "str2";

export declare const str3 = "str3";

export declare const strSum: string;

export { }
",
"export declare const num1 = 1;

export declare const num2 = 2;

export declare const num3 = 3;

export declare const numSum: number;

export declare const str1 = "str1";

export declare const str2 = "str2";

export declare const str3 = "str3";

export declare const strSum: string;

export { }
",
"export declare const numSum: number;

export declare const strSum: string;

export { }
",
"export declare const numSum: number;

export declare const strSum: string;

export { }
",
]
`;

exports[`dts when bundle: true > rootdir calculation should ignore declaration files 3`] = `
{
"cjs": "export declare const num1 = 1;
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/dts/bundle/multiple-entries/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "dts-bundle-multiple-entries-test",
"version": "1.0.0",
"private": true,
"type": "module"
}
24 changes: 24 additions & 0 deletions tests/integration/dts/bundle/multiple-entries/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineConfig } from '@rslib/core';
import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper';

export default defineConfig({
lib: [
generateBundleEsmConfig({
dts: {
bundle: true,
},
}),
generateBundleCjsConfig({
dts: {
bundle: true,
},
}),
],
source: {
entry: {
index: '../__fixtures__/src/index.ts',
sum: '../__fixtures__/src/sum.ts',
},
tsconfigPath: '../__fixtures__/tsconfig.json',
},
});
38 changes: 38 additions & 0 deletions tests/integration/dts/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createTempFiles,
globContentJSON,
proxyConsole,
queryContent,
} from 'test-helper';
import { describe, expect, test } from 'vitest';

Expand Down Expand Up @@ -339,6 +340,43 @@ describe('dts when bundle: true', () => {
]
`);
});

test('multiple entries', async () => {
const fixturePath = join(__dirname, 'bundle', 'multiple-entries');
const { files, contents } = await buildAndGetResults({
fixturePath,
type: 'dts',
});

expect(files.esm).toMatchInlineSnapshot(`
[
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/esm/index.d.ts",
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/esm/sum.d.ts",
]
`);

expect(files.cjs).toMatchInlineSnapshot(`
[
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/cjs/index.d.ts",
"<ROOT>/tests/integration/dts/bundle/multiple-entries/dist/cjs/sum.d.ts",
]
`);

const { content: indexEsm } = queryContent(contents.esm, 'index.d.ts', {
basename: true,
});
const { content: indexCjs } = queryContent(contents.cjs, 'index.d.ts', {
basename: true,
});
const { content: sumEsm } = queryContent(contents.esm, 'sum.d.ts', {
basename: true,
});
const { content: sumCjs } = queryContent(contents.cjs, 'sum.d.ts', {
basename: true,
});

expect([indexEsm, indexCjs, sumEsm, sumCjs]).toMatchSnapshot();
});
});

describe('dts when build: true', () => {
Expand Down
6 changes: 0 additions & 6 deletions website/docs/en/config/lib/dts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@ export default {
};
```

::: note

[@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.

:::

#### Handle third-party packages

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.
Expand Down
Loading
Loading