Skip to content

Commit df132a4

Browse files
authored
feat: clean dts files / buildinfo / .rslib temp folder (#486)
1 parent f96adda commit df132a4

File tree

22 files changed

+517
-58
lines changed

22 files changed

+517
-58
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ coverage/
1111
doc_build/
1212
playwright-report/
1313
tsconfig.tsbuildinfo
14+
tsconfig.esm.tsbuildinfo
15+
tsconfig.cjs.tsbuildinfo
1416
test-temp-*
1517
test-results
1618

packages/plugin-dts/src/apiExtractor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
5252
untrimmedFilePath,
5353
},
5454
compiler: {
55-
tsconfigFilePath: tsconfigPath.includes(cwd)
56-
? tsconfigPath
57-
: join(cwd, tsconfigPath),
55+
tsconfigFilePath: tsconfigPath,
5856
},
5957
projectFolder: cwd,
6058
};

packages/plugin-dts/src/dts.ts

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,9 @@ import {
1010
} from 'node:path';
1111
import { logger } from '@rsbuild/core';
1212
import color from 'picocolors';
13-
import ts from 'typescript';
1413
import type { DtsGenOptions } from './index';
1514
import { emitDts } from './tsc';
16-
import {
17-
calcLongestCommonPath,
18-
ensureTempDeclarationDir,
19-
loadTsconfig,
20-
} from './utils';
15+
import { calcLongestCommonPath, ensureTempDeclarationDir } from './utils';
2116

2217
const isObject = (obj: unknown): obj is Record<string, any> =>
2318
Object.prototype.toString.call(obj) === '[object Object]';
@@ -113,9 +108,10 @@ export const calcBundledPackages = (options: {
113108
export async function generateDts(data: DtsGenOptions): Promise<void> {
114109
const {
115110
bundle,
116-
distPath,
111+
dtsEmitPath,
117112
dtsEntry,
118113
tsconfigPath,
114+
tsConfigResult,
119115
name,
120116
cwd,
121117
build,
@@ -125,33 +121,25 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
125121
userExternals,
126122
banner,
127123
footer,
128-
rootDistPath,
129124
} = data;
130125
logger.start(`Generating DTS... ${color.gray(`(${name})`)}`);
131-
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, tsconfigPath);
132-
if (!configPath) {
133-
logger.error(`tsconfig.json not found in ${cwd}`);
134-
throw new Error();
135-
}
136-
const { options: rawCompilerOptions, fileNames } = loadTsconfig(configPath);
126+
127+
const { options: rawCompilerOptions, fileNames } = tsConfigResult;
137128

138129
// The longest common path of all non-declaration input files.
139130
// If composite is set, the default is instead the directory containing the tsconfig.json file.
140131
// see https://www.typescriptlang.org/tsconfig/#rootDir
141132
const rootDir =
142133
rawCompilerOptions.rootDir ??
143134
(rawCompilerOptions.composite
144-
? dirname(configPath)
135+
? dirname(tsconfigPath)
145136
: await calcLongestCommonPath(
146137
fileNames.filter((fileName) => !/\.d\.(ts|mts|cts)$/.test(fileName)),
147138
)) ??
148-
dirname(configPath);
149-
150-
const dtsEmitPath =
151-
distPath ?? rawCompilerOptions.declarationDir ?? rootDistPath;
139+
dirname(tsconfigPath);
152140

153141
const resolvedDtsEmitPath = normalize(
154-
resolve(dirname(configPath), dtsEmitPath),
142+
resolve(dirname(tsconfigPath), dtsEmitPath),
155143
);
156144

157145
if (build) {
@@ -173,13 +161,15 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
173161
: 'declarationDir';
174162
throw Error(
175163
`Please set ${info}: "${dtsEmitPath}" in ${color.underline(
176-
configPath,
164+
tsconfigPath,
177165
)} to keep it same as "dts.distPath" or "output.distPath.root" field in lib config.`,
178166
);
179167
}
180168
}
181169

182-
const declarationDir = bundle ? ensureTempDeclarationDir(cwd) : dtsEmitPath;
170+
const declarationDir = bundle
171+
? ensureTempDeclarationDir(cwd, name)
172+
: dtsEmitPath;
183173

184174
const { name: entryName, path: entryPath } = dtsEntry;
185175
let entry = '';
@@ -232,7 +222,8 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
232222
{
233223
name,
234224
cwd,
235-
configPath,
225+
configPath: tsconfigPath,
226+
tsConfigResult,
236227
declarationDir,
237228
dtsExtension,
238229
banner,

packages/plugin-dts/src/index.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { fork } from 'node:child_process';
22
import { dirname, extname, join } from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { type RsbuildConfig, type RsbuildPlugin, logger } from '@rsbuild/core';
5-
import { processSourceEntry } from './utils';
5+
import ts from 'typescript';
6+
import {
7+
cleanDtsFiles,
8+
cleanTsBuildInfoFile,
9+
clearTempDeclarationDir,
10+
loadTsconfig,
11+
processSourceEntry,
12+
} from './utils';
613

714
const __filename = fileURLToPath(import.meta.url);
815
const __dirname = dirname(__filename);
@@ -34,9 +41,10 @@ export type DtsGenOptions = PluginDtsOptions & {
3441
cwd: string;
3542
isWatch: boolean;
3643
dtsEntry: DtsEntry;
37-
rootDistPath: string;
44+
dtsEmitPath: string;
3845
build?: boolean;
39-
tsconfigPath?: string;
46+
tsconfigPath: string;
47+
tsConfigResult: ts.ParsedCommandLine;
4048
userExternals?: NonNullable<RsbuildConfig['output']>['externals'];
4149
};
4250

@@ -62,33 +70,67 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({
6270
let promisesResult: TaskResult[] = [];
6371

6472
api.onBeforeEnvironmentCompile(
65-
({ isWatch, isFirstCompile, environment }) => {
73+
async ({ isWatch, isFirstCompile, environment }) => {
6674
if (!isFirstCompile) {
6775
return;
6876
}
6977

7078
const { config } = environment;
7179

72-
const jsExtension = extname(__filename);
73-
const childProcess = fork(join(__dirname, `./dts${jsExtension}`), [], {
74-
stdio: 'inherit',
75-
});
76-
7780
// TODO: @microsoft/api-extractor only support single entry to bundle DTS
7881
// use first element of Record<string, string> type entry config
7982
const dtsEntry = processSourceEntry(
8083
options.bundle!,
8184
config.source?.entry,
8285
);
8386

87+
const cwd = api.context.rootPath;
88+
const tsconfigPath = ts.findConfigFile(
89+
cwd,
90+
ts.sys.fileExists,
91+
config.source.tsconfigPath,
92+
);
93+
94+
if (!tsconfigPath) {
95+
logger.error(`tsconfig.json not found in ${cwd}`);
96+
throw new Error();
97+
}
98+
99+
const tsConfigResult = loadTsconfig(tsconfigPath);
100+
const dtsEmitPath =
101+
options.distPath ??
102+
tsConfigResult.options.declarationDir ??
103+
config.output?.distPath?.root;
104+
105+
const jsExtension = extname(__filename);
106+
const childProcess = fork(join(__dirname, `./dts${jsExtension}`), [], {
107+
stdio: 'inherit',
108+
});
109+
110+
const { cleanDistPath } = config.output;
111+
112+
// clean dts files
113+
if (cleanDistPath !== false) {
114+
await cleanDtsFiles(dtsEmitPath);
115+
}
116+
117+
// clean .rslib temp folder
118+
if (options.bundle) {
119+
await clearTempDeclarationDir(cwd);
120+
}
121+
122+
// clean tsbuildinfo file
123+
await cleanTsBuildInfoFile(tsconfigPath, tsConfigResult);
124+
84125
const dtsGenOptions: DtsGenOptions = {
85126
...options,
86127
dtsEntry,
128+
dtsEmitPath,
87129
userExternals: config.output.externals,
88-
rootDistPath: config.output?.distPath?.root,
89-
tsconfigPath: config.source.tsconfigPath,
130+
tsconfigPath,
131+
tsConfigResult,
90132
name: environment.name,
91-
cwd: api.context.rootPath,
133+
cwd,
92134
isWatch,
93135
};
94136

packages/plugin-dts/src/tsc.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { logger } from '@rsbuild/core';
22
import color from 'picocolors';
33
import ts from 'typescript';
4-
import {
5-
getFileLoc,
6-
getTimeCost,
7-
loadTsconfig,
8-
processDtsFiles,
9-
} from './utils';
4+
import { getFileLoc, getTimeCost, processDtsFiles } from './utils';
105

116
export type EmitDtsOptions = {
127
name: string;
138
cwd: string;
149
configPath: string;
10+
tsConfigResult: ts.ParsedCommandLine;
1511
declarationDir: string;
1612
dtsExtension: string;
1713
banner?: string;
@@ -63,14 +59,20 @@ export async function emitDts(
6359
build = false,
6460
): Promise<void> {
6561
const start = Date.now();
66-
const { configPath, declarationDir, name, dtsExtension, banner, footer } =
67-
options;
68-
const configFileParseResult = loadTsconfig(configPath);
62+
const {
63+
configPath,
64+
tsConfigResult,
65+
declarationDir,
66+
name,
67+
dtsExtension,
68+
banner,
69+
footer,
70+
} = options;
6971
const {
7072
options: rawCompilerOptions,
7173
fileNames,
7274
projectReferences,
73-
} = configFileParseResult;
75+
} = tsConfigResult;
7476

7577
const compilerOptions = {
7678
...rawCompilerOptions,
@@ -160,9 +162,8 @@ export async function emitDts(
160162
options: compilerOptions,
161163
projectReferences,
162164
host,
163-
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
164-
configFileParseResult,
165-
),
165+
configFileParsingDiagnostics:
166+
ts.getConfigFileParsingDiagnostics(tsConfigResult),
166167
});
167168

168169
const emitResult = program.emit();
@@ -190,9 +191,8 @@ export async function emitDts(
190191
const program = ts.createIncrementalProgram({
191192
rootNames: fileNames,
192193
options: compilerOptions,
193-
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
194-
configFileParseResult,
195-
),
194+
configFileParsingDiagnostics:
195+
ts.getConfigFileParsingDiagnostics(tsConfigResult),
196196
projectReferences,
197197
host,
198198
createProgram,

packages/plugin-dts/src/utils.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'node:fs';
22
import fsP from 'node:fs/promises';
33
import { platform } from 'node:os';
4-
import path, { join } from 'node:path';
4+
import path, { basename, dirname, join, relative, resolve } from 'node:path';
55
import { type RsbuildConfig, logger } from '@rsbuild/core';
66
import MagicString from 'magic-string';
77
import color from 'picocolors';
@@ -23,8 +23,8 @@ export function loadTsconfig(tsconfigPath: string): ts.ParsedCommandLine {
2323
export const TEMP_FOLDER = '.rslib';
2424
export const TEMP_DTS_DIR: string = `${TEMP_FOLDER}/declarations`;
2525

26-
export function ensureTempDeclarationDir(cwd: string): string {
27-
const dirPath = path.join(cwd, TEMP_DTS_DIR);
26+
export function ensureTempDeclarationDir(cwd: string, name: string): string {
27+
const dirPath = path.join(cwd, TEMP_DTS_DIR, name);
2828

2929
if (fs.existsSync(dirPath)) {
3030
return dirPath;
@@ -38,6 +38,37 @@ export function ensureTempDeclarationDir(cwd: string): string {
3838
return dirPath;
3939
}
4040

41+
export async function pathExists(path: string): Promise<boolean> {
42+
return fs.promises
43+
.access(path)
44+
.then(() => true)
45+
.catch(() => false);
46+
}
47+
48+
export async function emptyDir(dir: string): Promise<void> {
49+
if (!(await pathExists(dir))) {
50+
return;
51+
}
52+
53+
try {
54+
for (const file of await fs.promises.readdir(dir)) {
55+
await fs.promises.rm(path.resolve(dir, file), {
56+
recursive: true,
57+
force: true,
58+
});
59+
}
60+
} catch (err) {
61+
logger.debug(`Failed to empty dir: ${dir}`);
62+
logger.debug(err);
63+
}
64+
}
65+
66+
export async function clearTempDeclarationDir(cwd: string): Promise<void> {
67+
const dirPath = path.join(cwd, TEMP_DTS_DIR);
68+
69+
await emptyDir(dirPath);
70+
}
71+
4172
export function getFileLoc(
4273
diagnostic: ts.Diagnostic,
4374
configPath: string,
@@ -196,3 +227,41 @@ export async function calcLongestCommonPath(
196227

197228
return lca;
198229
}
230+
231+
export async function cleanDtsFiles(dir: string): Promise<void> {
232+
const patterns = ['/**/*.d.ts', '/**/*.d.cts', '/**/*.d.mts'];
233+
const files = await Promise.all(
234+
patterns.map((pattern) =>
235+
glob(convertPath(join(dir, pattern)), { absolute: true }),
236+
),
237+
);
238+
239+
const allFiles = files.flat();
240+
241+
await Promise.all(allFiles.map((file) => fsP.rm(file, { force: true })));
242+
}
243+
244+
export async function cleanTsBuildInfoFile(
245+
tsconfigPath: string,
246+
tsConfigResult: ts.ParsedCommandLine,
247+
): Promise<void> {
248+
const tsconfigDir = dirname(tsconfigPath);
249+
const { outDir, rootDir, tsBuildInfoFile } = tsConfigResult.options;
250+
let tsbuildInfoFilePath = `${basename(
251+
tsconfigPath,
252+
'.json',
253+
)}${tsBuildInfoFile ?? '.tsbuildinfo'}`;
254+
if (outDir) {
255+
if (rootDir) {
256+
tsbuildInfoFilePath = join(
257+
outDir,
258+
relative(resolve(tsconfigDir, rootDir), tsconfigDir),
259+
tsbuildInfoFilePath,
260+
);
261+
} else {
262+
tsbuildInfoFilePath = join(outDir, tsbuildInfoFilePath);
263+
}
264+
}
265+
266+
await fsP.rm(tsbuildInfoFilePath, { force: true });
267+
}

0 commit comments

Comments
 (0)