From 317cf7347bf4c88aa23cc064f8da681fbf546ee5 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Mon, 24 Feb 2025 20:28:56 +0800 Subject: [PATCH 1/3] feat: support bundle DTS of multiple entries --- packages/plugin-dts/src/apiExtractor.ts | 80 ++++++++++--------- packages/plugin-dts/src/dts.ts | 44 +++++----- packages/plugin-dts/src/index.ts | 7 +- packages/plugin-dts/src/utils.ts | 19 +++-- pnpm-lock.yaml | 2 + .../dts/__snapshots__/index.test.ts.snap | 53 ++++++++++++ .../dts/bundle/multiple-entries/package.json | 6 ++ .../bundle/multiple-entries/rslib.config.ts | 24 ++++++ tests/integration/dts/index.test.ts | 38 +++++++++ 9 files changed, 203 insertions(+), 70 deletions(-) create mode 100644 tests/integration/dts/bundle/multiple-entries/package.json create mode 100644 tests/integration/dts/bundle/multiple-entries/rslib.config.ts diff --git a/packages/plugin-dts/src/apiExtractor.ts b/packages/plugin-dts/src/apiExtractor.ts index 1e24c4e92..794f15b69 100644 --- a/packages/plugin-dts/src/apiExtractor.ts +++ b/packages/plugin-dts/src/apiExtractor.ts @@ -16,7 +16,7 @@ export type BundleOptions = { dtsExtension: string; banner?: string; footer?: string; - dtsEntry: DtsEntry; + dtsEntry: DtsEntry[]; tsconfigPath?: string; bundledPackages?: string[]; }; @@ -29,52 +29,56 @@ export async function bundleDts(options: BundleOptions): Promise { 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'); diff --git a/packages/plugin-dts/src/dts.ts b/packages/plugin-dts/src/dts.ts index 3423e5816..a28597353 100644 --- a/packages/plugin-dts/src/dts.ts +++ b/packages/plugin-dts/src/dts.ts @@ -175,21 +175,30 @@ export async function generateDts(data: DtsGenOptions): Promise { ? 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 { name: string; path: string }[]; } const bundleDtsIfNeeded = async () => { @@ -199,10 +208,7 @@ export async function generateDts(data: DtsGenOptions): Promise { name, cwd, distPath: dtsEmitPath, - dtsEntry: { - name: entryName, - path: entry, - }, + dtsEntry: dtsEntries, tsconfigPath, dtsExtension, banner, diff --git a/packages/plugin-dts/src/index.ts b/packages/plugin-dts/src/index.ts index be80dcc02..bd5154bf7 100644 --- a/packages/plugin-dts/src/index.ts +++ b/packages/plugin-dts/src/index.ts @@ -49,7 +49,7 @@ export type DtsGenOptions = PluginDtsOptions & { name: string; cwd: string; isWatch: boolean; - dtsEntry: DtsEntry; + dtsEntry: DtsEntry[]; dtsEmitPath: string; build?: boolean; tsconfigPath: string; @@ -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 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, diff --git a/packages/plugin-dts/src/utils.ts b/packages/plugin-dts/src/utils.ts index 8ebe4e0c2..6c541638f 100644 --- a/packages/plugin-dts/src/utils.ts +++ b/packages/plugin-dts/src/utils.ts @@ -406,26 +406,25 @@ export async function processDtsFiles( export function processSourceEntry( bundle: boolean, entryConfig: NonNullable['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).map( + ([name, path]) => ({ + name, + path, + }), + ); } throw new Error( - '@microsoft/api-extractor only support single entry of Record type to bundle DTS, please check your entry config.', + '@microsoft/api-extractor only support entry of Record type to bundle DTS, please check your entry config.', ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1efc9aa6e..4d06fcb74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -667,6 +667,8 @@ importers: tests/integration/dts/bundle/false: {} + tests/integration/dts/bundle/multiple-entries: {} + tests/integration/dts/bundle/rootdir: devDependencies: '@types/chromecast-caf-sender': diff --git a/tests/integration/dts/__snapshots__/index.test.ts.snap b/tests/integration/dts/__snapshots__/index.test.ts.snap index 4aca4680f..f932b63c3 100644 --- a/tests/integration/dts/__snapshots__/index.test.ts.snap +++ b/tests/integration/dts/__snapshots__/index.test.ts.snap @@ -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; diff --git a/tests/integration/dts/bundle/multiple-entries/package.json b/tests/integration/dts/bundle/multiple-entries/package.json new file mode 100644 index 000000000..742025835 --- /dev/null +++ b/tests/integration/dts/bundle/multiple-entries/package.json @@ -0,0 +1,6 @@ +{ + "name": "dts-bundle-multiple-entries-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/dts/bundle/multiple-entries/rslib.config.ts b/tests/integration/dts/bundle/multiple-entries/rslib.config.ts new file mode 100644 index 000000000..23da196ba --- /dev/null +++ b/tests/integration/dts/bundle/multiple-entries/rslib.config.ts @@ -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', + }, +}); diff --git a/tests/integration/dts/index.test.ts b/tests/integration/dts/index.test.ts index ea48205c9..ca4012912 100644 --- a/tests/integration/dts/index.test.ts +++ b/tests/integration/dts/index.test.ts @@ -6,6 +6,7 @@ import { createTempFiles, globContentJSON, proxyConsole, + queryContent, } from 'test-helper'; import { describe, expect, test } from 'vitest'; @@ -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(` + [ + "/tests/integration/dts/bundle/multiple-entries/dist/esm/index.d.ts", + "/tests/integration/dts/bundle/multiple-entries/dist/esm/sum.d.ts", + ] + `); + + expect(files.cjs).toMatchInlineSnapshot(` + [ + "/tests/integration/dts/bundle/multiple-entries/dist/cjs/index.d.ts", + "/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', () => { From 624608eeb75e26d9e374957bec0b4862bc2e2e68 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Mon, 24 Feb 2025 20:33:29 +0800 Subject: [PATCH 2/3] chore: update --- packages/plugin-dts/src/dts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-dts/src/dts.ts b/packages/plugin-dts/src/dts.ts index a28597353..da3f03201 100644 --- a/packages/plugin-dts/src/dts.ts +++ b/packages/plugin-dts/src/dts.ts @@ -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'; @@ -198,7 +198,7 @@ export async function generateDts(data: DtsGenOptions): Promise { ); return { name: entryName, path: newPath }; }) - .filter(Boolean) as { name: string; path: string }[]; + .filter(Boolean) as Required[]; } const bundleDtsIfNeeded = async () => { From 0fcf8cfb30cdf5263ea0854df68b6f3b9c3c6de1 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Mon, 24 Feb 2025 20:36:52 +0800 Subject: [PATCH 3/3] chore: update --- website/docs/en/config/lib/dts.mdx | 6 ------ website/docs/zh/config/lib/dts.mdx | 6 ------ 2 files changed, 12 deletions(-) diff --git a/website/docs/en/config/lib/dts.mdx b/website/docs/en/config/lib/dts.mdx index dbe53e337..9c1063b49 100644 --- a/website/docs/en/config/lib/dts.mdx +++ b/website/docs/en/config/lib/dts.mdx @@ -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. diff --git a/website/docs/zh/config/lib/dts.mdx b/website/docs/zh/config/lib/dts.mdx index 7893d637f..82a66c666 100644 --- a/website/docs/zh/config/lib/dts.mdx +++ b/website/docs/zh/config/lib/dts.mdx @@ -86,12 +86,6 @@ export default { }; ``` -::: note - -[@microsoft/api-extractor](https://www.npmjs.com/package/@microsoft/api-extractor) 只支持为单个入口打包 DTS。如果你想要为多个入口生成打包后的 DTS,你可以在 [lib](/config/lib/) 字段中添加额外的 lib 配置,将多个入口拆分为多个 lib 配置。 - -::: - #### 处理第三方依赖 当我们打包 DTS 文件时,我们需要指定哪些第三方包的类型需要被打包,可以参考 [处理第三方依赖](/guide/advanced/third-party-deps) 文档了解更多关于 externals 相关的配置。