Skip to content

Commit

Permalink
feat: support dts bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
Timeless0911 committed Jul 23, 2024
1 parent 49931fa commit 78b2401
Show file tree
Hide file tree
Showing 16 changed files with 505 additions and 70 deletions.
5 changes: 4 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ compiled
doc_build

# ignore all JS/TS files, use Biome
**/*[.js,.ts,.jsx,.tsx]
**/*.js
**/*.ts
**/*.jsx
**/*.tsx
6 changes: 6 additions & 0 deletions e2e/cases/dts/bundle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "dts-bundle-test",
"version": "1.0.0",
"private": true,
"type": "module"
}
23 changes: 23 additions & 0 deletions e2e/cases/dts/bundle/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from '@rslib/core';
import {
generateBundleCjsConfig,
generateBundleEsmConfig,
} from '../../../scripts/shared';

export default defineConfig({
lib: [
generateBundleEsmConfig(__dirname, {
dts: {
bundle: true,
},
}),
generateBundleCjsConfig(__dirname, {
dts: false,
}),
],
source: {
entry: {
main: './src/index.ts',
},
},
});
3 changes: 3 additions & 0 deletions e2e/cases/dts/bundle/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './utils/numbers';
export * from './utils/strings';
export * from './sum';
5 changes: 5 additions & 0 deletions e2e/cases/dts/bundle/src/sum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { num1, num2, num3 } from './utils/numbers';
import { str1, str2, str3 } from './utils/strings';

export const numSum = num1 + num2 + num3;
export const strSum = str1 + str2 + str3;
3 changes: 3 additions & 0 deletions e2e/cases/dts/bundle/src/utils/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const num1 = 1;
export const num2 = 2;
export const num3 = 3;
3 changes: 3 additions & 0 deletions e2e/cases/dts/bundle/src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const str1 = 'str1';
export const str2 = 'str2';
export const str3 = 'str3';
7 changes: 7 additions & 0 deletions e2e/cases/dts/bundle/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@rslib/tsconfig/base",
"compilerOptions": {
"baseUrl": "./"
},
"include": ["src"]
}
2 changes: 2 additions & 0 deletions e2e/cases/dts/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ import { expect, test } from 'vitest';
import { buildAndGetJsResults } from '#shared';

test.todo('dts when bundle: false', async () => {});

test.todo('dts when bundle: true', async () => {});
15 changes: 13 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
"bin": {
"rslib": "./bin/rslib.js"
},
"files": ["bin", "dist", "dist-types", "compiled"],
"files": [
"bin",
"dist",
"dist-types",
"compiled"
],
"scripts": {
"build": "modern build",
"dev": "modern build --watch",
Expand All @@ -40,16 +45,22 @@
"@rslib/tsconfig": "workspace:*",
"@types/fs-extra": "^11.0.4",
"commander": "^12.1.0",
"fs-extra": "^11.2.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"picocolors": "1.0.1",
"prebundle": "1.1.0",
"rslog": "^1.2.2",
"typescript": "^5.5.3"
},
"peerDependencies": {
"@microsoft/api-extractor": "^7",
"typescript": "^5"
},
"peerDependenciesMeta": {
"@microsoft/api-extractor": {
"optional": true
}
},
"engines": {
"node": ">=16.0.0"
},
Expand Down
15 changes: 12 additions & 3 deletions packages/plugin-dts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,28 @@
},
"main": "./dist/index.cjs",
"types": "./dist/index.d.ts",
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"build": "modern build",
"dev": "modern build --watch"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.47.2",
"@rsbuild/core": "1.0.1-beta.3",
"@rslib/tsconfig": "workspace:*",
"typescript": "^5.5.3"
},
"peerDependencies": {
"typescript": "^5",
"@rsbuild/core": "workspace:^1.0.1-beta.0"
"@microsoft/api-extractor": "^7",
"@rsbuild/core": "workspace:^1.0.1-beta.0",
"typescript": "^5"
},
"peerDependenciesMeta": {
"@microsoft/api-extractor": {
"optional": true
}
},
"engines": {
"node": ">=16.0.0"
Expand Down
43 changes: 43 additions & 0 deletions packages/plugin-dts/src/apiExtractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { join } from 'node:path';
import {
Extractor,
ExtractorConfig,
type ExtractorResult,
} from '@microsoft/api-extractor';
import { logger } from '@rsbuild/core';
import { ensureTempDeclarationDir } from './utils';

export function bundleDts(outDir: string, tsconfigPath = 'tsconfig.json') {
const cwd = process.cwd();
const internalConfig = {
// TODO: use source.entry.main
mainEntryPointFilePath: join(ensureTempDeclarationDir(), 'index.d.ts'),
// TODO: use !externals
// bundledPackages: [],
dtsRollup: {
enabled: true,
untrimmedFilePath: join(cwd, outDir, 'index.d.ts'),
},
compiler: {
tsconfigFilePath: join(cwd, tsconfigPath),
},
projectFolder: cwd,
};

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

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

if (!extractorResult.succeeded) {
throw new Error('API Extractor error');
}

logger.info('API Extractor rollup succeeded\n');
}
73 changes: 10 additions & 63 deletions packages/plugin-dts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { join } from 'node:path';
import { type RsbuildPlugin, logger } from '@rsbuild/core';
import * as ts from 'typescript';
import { loadTsconfig } from './utils';
import type { RsbuildPlugin } from '@rsbuild/core';
import { bundleDts } from './apiExtractor';
import { emitDts } from './tsc';

export type pluginDtsOptions = {
bundle: boolean;
Expand All @@ -14,75 +13,23 @@ export const PLUGIN_DTS_NAME = 'rsbuild:dts';
// use ts compiler API to generate bundleless dts
// TODO: use ts compiler API and api-extractor to generate dts bundle
// TODO: support watch mode
// TODO: support incremental build, to build one or more projects and their dependencies
// TODO: support autoExtension for dts files
// TODO: deal alias in dts
export const pluginDts = (
options: pluginDtsOptions = { bundle: false },
): RsbuildPlugin => ({
name: PLUGIN_DTS_NAME,

setup(api) {
const { tsconfigPath, distPath } = options;
const { tsconfigPath } = options;

api.onAfterBuild(() => {
const cwd = process.cwd();
const configPath = tsconfigPath ? join(cwd, tsconfigPath) : cwd;
const { options: rawCompilerOptions, fileNames } =
loadTsconfig(configPath);
api.onAfterBuild(async () => {
const { outDir } = emitDts(options);

const compilerOptions = {
...rawCompilerOptions,
noEmit: false,
declaration: true,
declarationDir: distPath ? distPath : rawCompilerOptions.declarationDir,
emitDeclarationOnly: true,
};

const host: ts.CompilerHost = ts.createCompilerHost(compilerOptions);

const program: ts.Program = ts.createProgram(
fileNames,
compilerOptions,
host,
);

const emitResult = program.emit();

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

const diagnosticMessages: string[] = [];

for (const diagnostic of allDiagnostics) {
if (diagnostic.file) {
const { line, character } = ts.getLineAndCharacterOfPosition(
diagnostic.file,
diagnostic.start!,
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
diagnosticMessages.push(
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,
);
} else {
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
diagnosticMessages.push(message);
}
}

if (diagnosticMessages.length) {
logger.error(
`Failed to emit declaration files.\n${diagnosticMessages.join('\n')}\n`,
);
throw new Error('TypeScript compilation failed');
if (options.bundle === true) {
bundleDts(outDir, tsconfigPath);
}

logger.info('TypeScript compilation succeeded\n');
});
},
});
83 changes: 83 additions & 0 deletions packages/plugin-dts/src/tsc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { join } from 'node:path';
import { logger } from '@rsbuild/core';
import type { pluginDtsOptions } from 'src';
import * as ts from 'typescript';
import { ensureTempDeclarationDir, loadTsconfig } from './utils';

export function emitDts(options: pluginDtsOptions): {
outDir: string;
} {
const { tsconfigPath, distPath, bundle } = options;
const cwd = process.cwd();
const configPath = tsconfigPath ? join(cwd, tsconfigPath) : cwd;
const { options: rawCompilerOptions, fileNames } = loadTsconfig(configPath);

const getDeclarationDir = (bundle: boolean, distPath?: string) => {
if (bundle) {
return ensureTempDeclarationDir();
}

return distPath ? distPath : rawCompilerOptions.declarationDir;
};

const outDir = distPath ? distPath : rawCompilerOptions.declarationDir;

const compilerOptions = {
...rawCompilerOptions,
noEmit: false,
declaration: true,
declarationDir: getDeclarationDir(bundle, distPath),
emitDeclarationOnly: true,
};

const host: ts.CompilerHost = ts.createCompilerHost(compilerOptions);

const program: ts.Program = ts.createProgram(
fileNames,
compilerOptions,
host,
);

const emitResult = program.emit();

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

const diagnosticMessages: string[] = [];

for (const diagnostic of allDiagnostics) {
if (diagnostic.file) {
const { line, character } = ts.getLineAndCharacterOfPosition(
diagnostic.file,
diagnostic.start!,
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
diagnosticMessages.push(
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`,
);
} else {
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
diagnosticMessages.push(message);
}
}

if (diagnosticMessages.length) {
logger.error(
`Failed to emit declaration files.\n${diagnosticMessages.join('\n')}\n`,
);
throw new Error('TypeScript compilation failed');
}

logger.info('TypeScript compilation succeeded\n');

return {
outDir: outDir || './dist',
};
}
21 changes: 20 additions & 1 deletion packages/plugin-dts/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from 'node:fs';
import fs, { writeFileSync } from 'node:fs';
import path from 'node:path';
import * as ts from 'typescript';

Expand All @@ -18,3 +18,22 @@ export function loadTsconfig(directoryPath: string): ts.ParsedCommandLine {

throw new Error(`tsconfig.json not found in the ${directoryPath}`);
}

export const TEMP_FOLDER = '.rslib';
export const TEMP_DTS_DIR = `${TEMP_FOLDER}/declarations`;

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

if (fs.existsSync(dirPath)) {
return dirPath;
}

fs.mkdirSync(dirPath, { recursive: true });

const gitIgnorePath = path.join(cwd, `${TEMP_FOLDER}/.gitignore`);
writeFileSync(gitIgnorePath, '**/*\n');

return dirPath;
}
Loading

0 comments on commit 78b2401

Please sign in to comment.