Skip to content

feat: clean dts files / buildinfo / .rslib temp folder #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 28, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ coverage/
doc_build/
playwright-report/
tsconfig.tsbuildinfo
tsconfig.esm.tsbuildinfo
tsconfig.cjs.tsbuildinfo
test-temp-*
test-results

Expand Down
4 changes: 1 addition & 3 deletions packages/plugin-dts/src/apiExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
untrimmedFilePath,
},
compiler: {
tsconfigFilePath: tsconfigPath.includes(cwd)
? tsconfigPath
: join(cwd, tsconfigPath),
tsconfigFilePath: tsconfigPath,
},
projectFolder: cwd,
};
Expand Down
37 changes: 14 additions & 23 deletions packages/plugin-dts/src/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,9 @@ import {
} from 'node:path';
import { logger } from '@rsbuild/core';
import color from 'picocolors';
import ts from 'typescript';
import type { DtsGenOptions } from './index';
import { emitDts } from './tsc';
import {
calcLongestCommonPath,
ensureTempDeclarationDir,
loadTsconfig,
} from './utils';
import { calcLongestCommonPath, ensureTempDeclarationDir } from './utils';

const isObject = (obj: unknown): obj is Record<string, any> =>
Object.prototype.toString.call(obj) === '[object Object]';
Expand Down Expand Up @@ -113,9 +108,10 @@ export const calcBundledPackages = (options: {
export async function generateDts(data: DtsGenOptions): Promise<void> {
const {
bundle,
distPath,
dtsEmitPath,
dtsEntry,
tsconfigPath,
tsConfigResult,
name,
cwd,
build,
Expand All @@ -125,33 +121,25 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
userExternals,
banner,
footer,
rootDistPath,
} = data;
logger.start(`Generating DTS... ${color.gray(`(${name})`)}`);
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, tsconfigPath);
if (!configPath) {
logger.error(`tsconfig.json not found in ${cwd}`);
throw new Error();
}
const { options: rawCompilerOptions, fileNames } = loadTsconfig(configPath);

const { options: rawCompilerOptions, fileNames } = tsConfigResult;

// The longest common path of all non-declaration input files.
// If composite is set, the default is instead the directory containing the tsconfig.json file.
// see https://www.typescriptlang.org/tsconfig/#rootDir
const rootDir =
rawCompilerOptions.rootDir ??
(rawCompilerOptions.composite
? dirname(configPath)
? dirname(tsconfigPath)
: await calcLongestCommonPath(
fileNames.filter((fileName) => !/\.d\.(ts|mts|cts)$/.test(fileName)),
)) ??
dirname(configPath);

const dtsEmitPath =
distPath ?? rawCompilerOptions.declarationDir ?? rootDistPath;
dirname(tsconfigPath);

const resolvedDtsEmitPath = normalize(
resolve(dirname(configPath), dtsEmitPath),
resolve(dirname(tsconfigPath), dtsEmitPath),
);

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

const declarationDir = bundle ? ensureTempDeclarationDir(cwd) : dtsEmitPath;
const declarationDir = bundle
? ensureTempDeclarationDir(cwd, name)
: dtsEmitPath;

const { name: entryName, path: entryPath } = dtsEntry;
let entry = '';
Expand Down Expand Up @@ -232,7 +222,8 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
{
name,
cwd,
configPath,
configPath: tsconfigPath,
tsConfigResult,
declarationDir,
dtsExtension,
banner,
Expand Down
66 changes: 54 additions & 12 deletions packages/plugin-dts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { fork } from 'node:child_process';
import { dirname, extname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { type RsbuildConfig, type RsbuildPlugin, logger } from '@rsbuild/core';
import { processSourceEntry } from './utils';
import ts from 'typescript';
import {
cleanDtsFiles,
cleanTsBuildInfoFile,
clearTempDeclarationDir,
loadTsconfig,
processSourceEntry,
} from './utils';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand Down Expand Up @@ -34,9 +41,10 @@ export type DtsGenOptions = PluginDtsOptions & {
cwd: string;
isWatch: boolean;
dtsEntry: DtsEntry;
rootDistPath: string;
dtsEmitPath: string;
build?: boolean;
tsconfigPath?: string;
tsconfigPath: string;
tsConfigResult: ts.ParsedCommandLine;
userExternals?: NonNullable<RsbuildConfig['output']>['externals'];
};

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

api.onBeforeEnvironmentCompile(
({ isWatch, isFirstCompile, environment }) => {
async ({ isWatch, isFirstCompile, environment }) => {
if (!isFirstCompile) {
return;
}

const { config } = environment;

const jsExtension = extname(__filename);
const childProcess = fork(join(__dirname, `./dts${jsExtension}`), [], {
stdio: 'inherit',
});

// TODO: @microsoft/api-extractor only support single entry to bundle DTS
// use first element of Record<string, string> type entry config
const dtsEntry = processSourceEntry(
options.bundle!,
config.source?.entry,
);

const cwd = api.context.rootPath;
const tsconfigPath = ts.findConfigFile(
cwd,
ts.sys.fileExists,
config.source.tsconfigPath,
);

if (!tsconfigPath) {
logger.error(`tsconfig.json not found in ${cwd}`);
throw new Error();
}

const tsConfigResult = loadTsconfig(tsconfigPath);
const dtsEmitPath =
options.distPath ??
tsConfigResult.options.declarationDir ??
config.output?.distPath?.root;

const jsExtension = extname(__filename);
const childProcess = fork(join(__dirname, `./dts${jsExtension}`), [], {
stdio: 'inherit',
});

const { cleanDistPath } = config.output;

// clean dts files
if (cleanDistPath !== false) {
await cleanDtsFiles(dtsEmitPath);
}

// clean .rslib temp folder
if (options.bundle) {
await clearTempDeclarationDir(cwd);
}

// clean tsbuildinfo file
await cleanTsBuildInfoFile(tsconfigPath, tsConfigResult);

const dtsGenOptions: DtsGenOptions = {
...options,
dtsEntry,
dtsEmitPath,
userExternals: config.output.externals,
rootDistPath: config.output?.distPath?.root,
tsconfigPath: config.source.tsconfigPath,
tsconfigPath,
tsConfigResult,
name: environment.name,
cwd: api.context.rootPath,
cwd,
isWatch,
};

Expand Down
32 changes: 16 additions & 16 deletions packages/plugin-dts/src/tsc.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { logger } from '@rsbuild/core';
import color from 'picocolors';
import ts from 'typescript';
import {
getFileLoc,
getTimeCost,
loadTsconfig,
processDtsFiles,
} from './utils';
import { getFileLoc, getTimeCost, processDtsFiles } from './utils';

export type EmitDtsOptions = {
name: string;
cwd: string;
configPath: string;
tsConfigResult: ts.ParsedCommandLine;
declarationDir: string;
dtsExtension: string;
banner?: string;
Expand Down Expand Up @@ -63,14 +59,20 @@ export async function emitDts(
build = false,
): Promise<void> {
const start = Date.now();
const { configPath, declarationDir, name, dtsExtension, banner, footer } =
options;
const configFileParseResult = loadTsconfig(configPath);
const {
configPath,
tsConfigResult,
declarationDir,
name,
dtsExtension,
banner,
footer,
} = options;
const {
options: rawCompilerOptions,
fileNames,
projectReferences,
} = configFileParseResult;
} = tsConfigResult;

const compilerOptions = {
...rawCompilerOptions,
Expand Down Expand Up @@ -160,9 +162,8 @@ export async function emitDts(
options: compilerOptions,
projectReferences,
host,
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
configFileParseResult,
),
configFileParsingDiagnostics:
ts.getConfigFileParsingDiagnostics(tsConfigResult),
});

const emitResult = program.emit();
Expand Down Expand Up @@ -190,9 +191,8 @@ export async function emitDts(
const program = ts.createIncrementalProgram({
rootNames: fileNames,
options: compilerOptions,
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(
configFileParseResult,
),
configFileParsingDiagnostics:
ts.getConfigFileParsingDiagnostics(tsConfigResult),
projectReferences,
host,
createProgram,
Expand Down
75 changes: 72 additions & 3 deletions packages/plugin-dts/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'node:fs';
import fsP from 'node:fs/promises';
import { platform } from 'node:os';
import path, { join } from 'node:path';
import path, { basename, dirname, join, relative, resolve } from 'node:path';
import { type RsbuildConfig, logger } from '@rsbuild/core';
import MagicString from 'magic-string';
import color from 'picocolors';
Expand All @@ -23,8 +23,8 @@ export function loadTsconfig(tsconfigPath: string): ts.ParsedCommandLine {
export const TEMP_FOLDER = '.rslib';
export const TEMP_DTS_DIR: string = `${TEMP_FOLDER}/declarations`;

export function ensureTempDeclarationDir(cwd: string): string {
const dirPath = path.join(cwd, TEMP_DTS_DIR);
export function ensureTempDeclarationDir(cwd: string, name: string): string {
const dirPath = path.join(cwd, TEMP_DTS_DIR, name);

if (fs.existsSync(dirPath)) {
return dirPath;
Expand All @@ -38,6 +38,37 @@ export function ensureTempDeclarationDir(cwd: string): string {
return dirPath;
}

export async function pathExists(path: string): Promise<boolean> {
return fs.promises
.access(path)
.then(() => true)
.catch(() => false);
}

export async function emptyDir(dir: string): Promise<void> {
if (!(await pathExists(dir))) {
return;
}

try {
for (const file of await fs.promises.readdir(dir)) {
await fs.promises.rm(path.resolve(dir, file), {
recursive: true,
force: true,
});
}
} catch (err) {
logger.debug(`Failed to empty dir: ${dir}`);
logger.debug(err);
}
}

export async function clearTempDeclarationDir(cwd: string): Promise<void> {
const dirPath = path.join(cwd, TEMP_DTS_DIR);

await emptyDir(dirPath);
}

export function getFileLoc(
diagnostic: ts.Diagnostic,
configPath: string,
Expand Down Expand Up @@ -196,3 +227,41 @@ export async function calcLongestCommonPath(

return lca;
}

export async function cleanDtsFiles(dir: string): Promise<void> {
const patterns = ['/**/*.d.ts', '/**/*.d.cts', '/**/*.d.mts'];
const files = await Promise.all(
patterns.map((pattern) =>
glob(convertPath(join(dir, pattern)), { absolute: true }),
),
);

const allFiles = files.flat();

await Promise.all(allFiles.map((file) => fsP.rm(file, { force: true })));
}

export async function cleanTsBuildInfoFile(
tsconfigPath: string,
tsConfigResult: ts.ParsedCommandLine,
): Promise<void> {
const tsconfigDir = dirname(tsconfigPath);
const { outDir, rootDir, tsBuildInfoFile } = tsConfigResult.options;
let tsbuildInfoFilePath = `${basename(
tsconfigPath,
'.json',
)}${tsBuildInfoFile ?? '.tsbuildinfo'}`;
if (outDir) {
if (rootDir) {
tsbuildInfoFilePath = join(
outDir,
relative(resolve(tsconfigDir, rootDir), tsconfigDir),
tsbuildInfoFilePath,
);
} else {
tsbuildInfoFilePath = join(outDir, tsbuildInfoFilePath);
}
}

await fsP.rm(tsbuildInfoFilePath, { force: true });
}
Loading
Loading