Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@ const composeDtsConfig = async (
// Only setting ⁠dts.bundle to true will generate the bundled d.ts.
bundle: dts?.bundle ?? false,
distPath: dts?.distPath ?? output?.distPath?.root ?? './dist',
build: dts?.build ?? false,
abortOnError: dts?.abortOnError ?? true,
dtsExtension: dts?.autoExtension ? dtsExtension : '.d.ts',
autoExternal,
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/types/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export type Syntax =
| string[];

export type Dts =
| (Pick<PluginDtsOptions, 'bundle' | 'distPath' | 'abortOnError'> & {
| (Pick<
PluginDtsOptions,
'bundle' | 'distPath' | 'abortOnError' | 'build'
> & {
autoExtension?: boolean;
})
| boolean;
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-dts/src/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
tsconfigPath,
name,
cwd,
build,
isWatch,
dtsExtension = '.d.ts',
autoExternal = true,
Expand Down Expand Up @@ -204,6 +205,7 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
onComplete,
bundle,
isWatch,
build,
);

if (!isWatch) {
Expand Down
4 changes: 3 additions & 1 deletion packages/plugin-dts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const __dirname = dirname(__filename);
export type PluginDtsOptions = {
bundle?: boolean;
distPath?: string;
build?: boolean;
abortOnError?: boolean;
dtsExtension?: string;
autoExternal?:
Expand All @@ -33,6 +34,7 @@ export type DtsGenOptions = PluginDtsOptions & {
cwd: string;
isWatch: boolean;
dtsEntry: DtsEntry;
build?: boolean;
tsconfigPath?: string;
userExternals?: NonNullable<RsbuildConfig['output']>['externals'];
};
Expand All @@ -46,14 +48,14 @@ export const PLUGIN_DTS_NAME = 'rsbuild:dts';

// use ts compiler API to generate bundleless dts
// use ts compiler API and api-extractor to generate dts bundle
// TODO: support incremental build, to build one or more projects and their dependencies
// TODO: deal alias in dts
export const pluginDts = (options: PluginDtsOptions): RsbuildPlugin => ({
name: PLUGIN_DTS_NAME,

setup(api) {
options.bundle = options.bundle ?? false;
options.abortOnError = options.abortOnError ?? true;
options.build = options.build || false;

const dtsPromises: Promise<TaskResult>[] = [];
let promisesResult: TaskResult[] = [];
Expand Down
250 changes: 146 additions & 104 deletions packages/plugin-dts/src/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function emitDts(
onComplete: (isSuccess: boolean) => void,
bundle = false,
isWatch = false,
build = false,
): Promise<void> {
const start = Date.now();
const { configPath, declarationDir, name, dtsExtension, banner, footer } =
Expand All @@ -42,131 +43,172 @@ export async function emitDts(
emitDeclarationOnly: true,
};

if (!isWatch) {
const host: ts.CompilerHost = ts.createCompilerHost(compilerOptions);

const program: ts.Program = ts.createProgram({
rootNames: fileNames,
options: compilerOptions,
projectReferences,
host,
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
configFileParseResult,
),
});

const emitResult = program.emit();

const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
const formatHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
};

const diagnosticMessages: string[] = [];
const reportDiagnostic = (diagnostic: ts.Diagnostic) => {
const fileLoc = getFileLoc(diagnostic, configPath);

for (const diagnostic of allDiagnostics) {
const fileLoc = getFileLoc(diagnostic, configPath);
const message = `${fileLoc} - ${color.red('error')} ${color.gray(`TS${diagnostic.code}:`)} ${ts.flattenDiagnosticMessageText(
logger.error(
`${fileLoc} - ${color.red('error')} ${color.gray(`TS${diagnostic.code}:`)}`,
ts.flattenDiagnosticMessageText(
diagnostic.messageText,
host.getNewLine(),
)}`;
diagnosticMessages.push(message);
}

await processDtsFiles(bundle, declarationDir, dtsExtension, banner, footer);
formatHost.getNewLine(),
),
);
};

if (diagnosticMessages.length) {
logger.error(
`Failed to emit declaration files. ${color.gray(`(${name})`)}`,
);
const reportWatchStatusChanged: ts.WatchStatusReporter = async (
diagnostic: ts.Diagnostic,
_newLine: string,
_options: ts.CompilerOptions,
errorCount?: number,
) => {
const message = `${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
formatHost.getNewLine(),
)} ${color.gray(`(${name})`)}`;

// 6031: File change detected. Starting incremental compilation...
// 6032: Starting compilation in watch mode...
if (diagnostic.code === 6031 || diagnostic.code === 6032) {
logger.info(message);
}

for (const message of diagnosticMessages) {
// 6194: 0 errors or 2+ errors!
if (diagnostic.code === 6194) {
if (errorCount === 0 || !errorCount) {
logger.info(message);
onComplete(true);
} else {
logger.error(message);
}
await processDtsFiles(
bundle,
declarationDir,
dtsExtension,
banner,
footer,
);
}

throw new Error('DTS generation failed');
// 6193: 1 error
if (diagnostic.code === 6193) {
logger.error(message);
await processDtsFiles(
bundle,
declarationDir,
dtsExtension,
banner,
footer,
);
}
};

logger.ready(
`DTS generated in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
);
} else {
const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
const formatHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
};

const reportDiagnostic = (diagnostic: ts.Diagnostic) => {
const fileLoc = getFileLoc(diagnostic, configPath);

logger.error(
`${fileLoc} - ${color.red('error')} ${color.gray(`TS${diagnostic.code}:`)}`,
ts.flattenDiagnosticMessageText(
diagnostic.messageText,
formatHost.getNewLine(),
const system = { ...ts.sys };

if (!isWatch) {
// build mode
if (!build) {
const host: ts.CompilerHost = ts.createCompilerHost(compilerOptions);

const program: ts.Program = ts.createProgram({
rootNames: fileNames,
options: compilerOptions,
projectReferences,
host,
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
configFileParseResult,
),
);
};

const reportWatchStatusChanged: ts.WatchStatusReporter = async (
diagnostic: ts.Diagnostic,
_newLine: string,
_options: ts.CompilerOptions,
errorCount?: number,
) => {
const message = `${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
formatHost.getNewLine(),
)} ${color.gray(`(${name})`)}`;
});

// 6031: File change detected. Starting incremental compilation...
// 6032: Starting compilation in watch mode...
if (diagnostic.code === 6031 || diagnostic.code === 6032) {
logger.info(message);
const emitResult = program.emit();

const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);

const diagnosticMessages: string[] = [];

for (const diagnostic of allDiagnostics) {
const fileLoc = getFileLoc(diagnostic, configPath);
const message = `${fileLoc} - ${color.red('error')} ${color.gray(`TS${diagnostic.code}:`)} ${ts.flattenDiagnosticMessageText(
diagnostic.messageText,
host.getNewLine(),
)}`;
diagnosticMessages.push(message);
}

// 6194: 0 errors or 2+ errors!
if (diagnostic.code === 6194) {
if (errorCount === 0) {
logger.info(message);
onComplete(true);
} else {
await processDtsFiles(
bundle,
declarationDir,
dtsExtension,
banner,
footer,
);

if (diagnosticMessages.length) {
logger.error(
`Failed to emit declaration files. ${color.gray(`(${name})`)}`,
);

for (const message of diagnosticMessages) {
logger.error(message);
}
await processDtsFiles(
bundle,
declarationDir,
dtsExtension,
banner,
footer,
);
}

// 6193: 1 error
if (diagnostic.code === 6193) {
logger.error(message);
await processDtsFiles(
bundle,
declarationDir,
dtsExtension,
banner,
footer,
);
throw new Error('DTS generation failed');
}
};
} else {
// incremental build with project references
const host = ts.createSolutionBuilderHost(
system,
createProgram,
reportDiagnostic,
);

const system = { ...ts.sys };
const solutionBuilder = ts.createSolutionBuilder(host, [configPath], {});

solutionBuilder.build();
}

const host = ts.createWatchCompilerHost(
configPath,
compilerOptions,
system,
createProgram,
reportDiagnostic,
reportWatchStatusChanged,
logger.ready(
`DTS generated in ${getTimeCost(start)} ${color.gray(`(${name})`)}`,
);
} else {
// watch mode
if (!build) {
const host = ts.createWatchCompilerHost(
configPath,
compilerOptions,
system,
createProgram,
reportDiagnostic,
reportWatchStatusChanged,
);

ts.createWatchProgram(host);
} else {
// incremental build with project references
const host = ts.createSolutionBuilderWithWatchHost(
system,
createProgram,
reportDiagnostic,
undefined,
reportWatchStatusChanged,
);

ts.createWatchProgram(host);
const solutionBuilder = ts.createSolutionBuilderWithWatch(
host,
[configPath],
{},
{ watch: true },
);

solutionBuilder.build();
}
}
}
13 changes: 10 additions & 3 deletions packages/plugin-dts/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,17 @@ export async function addBannerAndFooter(
const content = await fsP.readFile(file, 'utf-8');
const code = new MagicString(content);

banner && code.prepend(`${banner}\n`);
footer && code.append(`\n${footer}\n`);
if (banner && !content.trimStart().startsWith(banner.trim())) {
code.prepend(`${banner}\n`);
}

if (footer && !content.trimEnd().endsWith(footer.trim())) {
code.append(`\n${footer}\n`);
}

await fsP.writeFile(file, code.toString());
if (code.hasChanged()) {
await fsP.writeFile(file, code.toString());
}
}

export async function processDtsFiles(
Expand Down
Loading