diff --git a/.prettierignore b/.prettierignore index 5bf3522c..1d593aca 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,4 @@ compiled doc_build # ignore all JS/TS files, use Biome -**/*[.js,.ts,.jsx,.tsx.json,.json5] \ No newline at end of file +**/*[.js,.ts,.jsx,.tsx] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 96c3481e..9b868538 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,10 +24,10 @@ "editor.defaultFormatter": "biomejs.biome" }, "[json5]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[jsonc]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/e2e/cases/dts/bundle-false/rslib.config.ts b/e2e/cases/dts/bundle-false/rslib.config.ts new file mode 100644 index 00000000..241d2c27 --- /dev/null +++ b/e2e/cases/dts/bundle-false/rslib.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from '#shared'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig(__dirname, { + bundle: false, + dts: { + bundle: false, + }, + }), + generateBundleCjsConfig(__dirname, { + bundle: false, + dts: false, + }), + ], + source: { + entry: { + main: ['./src/**'], + }, + }, +}); diff --git a/e2e/cases/dts/bundle-false/src/index.ts b/e2e/cases/dts/bundle-false/src/index.ts new file mode 100644 index 00000000..6eb6b6fb --- /dev/null +++ b/e2e/cases/dts/bundle-false/src/index.ts @@ -0,0 +1,3 @@ +export * from './utils/numbers'; +export * from './utils/strings'; +export * from './sum'; diff --git a/e2e/cases/dts/bundle-false/src/sum.ts b/e2e/cases/dts/bundle-false/src/sum.ts new file mode 100644 index 00000000..2759c882 --- /dev/null +++ b/e2e/cases/dts/bundle-false/src/sum.ts @@ -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; diff --git a/e2e/cases/dts/bundle-false/src/utils/numbers.ts b/e2e/cases/dts/bundle-false/src/utils/numbers.ts new file mode 100644 index 00000000..c1d2a8cd --- /dev/null +++ b/e2e/cases/dts/bundle-false/src/utils/numbers.ts @@ -0,0 +1,3 @@ +export const num1 = 1; +export const num2 = 2; +export const num3 = 3; diff --git a/e2e/cases/dts/bundle-false/src/utils/strings.ts b/e2e/cases/dts/bundle-false/src/utils/strings.ts new file mode 100644 index 00000000..d35b5a60 --- /dev/null +++ b/e2e/cases/dts/bundle-false/src/utils/strings.ts @@ -0,0 +1,3 @@ +export const str1 = 'str1'; +export const str2 = 'str2'; +export const str3 = 'str3'; diff --git a/e2e/cases/dts/bundle-false/tsconfig.json b/e2e/cases/dts/bundle-false/tsconfig.json new file mode 100644 index 00000000..888d3e46 --- /dev/null +++ b/e2e/cases/dts/bundle-false/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["src"] +} diff --git a/e2e/cases/dts/index.test.ts b/e2e/cases/dts/index.test.ts new file mode 100644 index 00000000..26ef2344 --- /dev/null +++ b/e2e/cases/dts/index.test.ts @@ -0,0 +1,5 @@ +import { join } from 'node:path'; +import { expect, test } from 'vitest'; +import { buildAndGetJsResults } from '#shared'; + +test.todo('dts when bundle: false', async () => {}); diff --git a/e2e/package.json b/e2e/package.json index 0687dc1b..6dcdebc3 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@playwright/test": "1.43.1", - "@rsbuild/core": "1.0.0-alpha.9", + "@rsbuild/core": "1.0.1-beta.3", "@rslib/core": "workspace:*", "@rslib/tsconfig": "workspace:*", "@types/fs-extra": "^11.0.4", diff --git a/package.json b/package.json index 74b635a6..4a3a7ee2 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "pre-commit": "npx nano-staged" }, "nano-staged": { - "*.{md,mdx,css,less,scss}": "prettier --write", - "*.{js,jsx,ts,tsx,mjs,cjs,json,jsonc,json5}": ["biome check --write"], + "*.{md,mdx,css,less,scss,json,jsonc,json5}": "prettier --write", + "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "biome check --write" + ], "package.json": "pnpm run check-dependency-version" }, "devDependencies": { diff --git a/packages/core/modern.config.ts b/packages/core/modern.config.ts index d624df4f..3bc30c42 100644 --- a/packages/core/modern.config.ts +++ b/packages/core/modern.config.ts @@ -1,7 +1,12 @@ import { defineConfig, moduleTools } from '@modern-js/module-tools'; import prebundleConfig from './prebundle.config.mjs'; -const externals = ['@rsbuild/core', /[\\/]compiled[\\/]/, /node:/]; +const externals = [ + '@rsbuild/core', + /[\\/]compiled[\\/]/, + /node:/, + 'typescript', +]; const define = { RSLIB_VERSION: require('./package.json').version, }; diff --git a/packages/core/package.json b/packages/core/package.json index 7b740ad9..2c611e66 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,19 +33,23 @@ "prebundle": "prebundle" }, "dependencies": { - "@rsbuild/core": "1.0.0-alpha.9" + "@rsbuild/core": "1.0.1-beta.3", + "rsbuild-plugin-dts": "workspace:*" }, "devDependencies": { "@rslib/tsconfig": "workspace:*", "@types/fs-extra": "^11.0.4", - "fast-glob": "^3.3.2", "commander": "^12.1.0", "fs-extra": "^11.2.0", + "fast-glob": "^3.3.2", "picocolors": "1.0.1", "prebundle": "1.1.0", "rslog": "^1.2.2", "typescript": "^5.5.3" }, + "peerDependencies": { + "typescript": "^5" + }, "engines": { "node": ">=16.0.0" }, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 253db3e5..51eaffd1 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,5 +1,4 @@ import fs from 'node:fs'; - import path, { dirname, isAbsolute, join } from 'node:path'; import { type RsbuildConfig, @@ -8,8 +7,10 @@ import { mergeRsbuildConfig, } from '@rsbuild/core'; import glob from 'fast-glob'; +import { pluginDts } from 'rsbuild-plugin-dts'; import { DEFAULT_CONFIG_NAME, DEFAULT_EXTENSIONS } from './constant'; import type { + Dts, Format, LibConfig, RslibConfig, @@ -291,6 +292,19 @@ const getBundleConfig = (bundle = true): RsbuildConfig => { }; }; +const getDefaultDtsConfig = (dts?: Dts, bundle = true): RsbuildConfig => { + if (dts === false || dts === undefined) return {}; + + return { + plugins: [ + pluginDts({ + bundle: dts?.bundle ?? bundle, + distPath: dts?.distPath ?? './dist', + }), + ], + }; +}; + export function convertLibConfigToRsbuildConfig( libConfig: LibConfig, configPath: string, @@ -305,12 +319,14 @@ export function convertLibConfigToRsbuildConfig( ); const syntaxConfig = getDefaultSyntaxConfig(libConfig.output?.syntax); const bundleConfig = getBundleConfig(libConfig.bundle); + const dtsConfig = getDefaultDtsConfig(libConfig.dts, libConfig.bundle); return mergeRsbuildConfig( formatConfig, autoExtensionConfig, syntaxConfig, bundleConfig, + dtsConfig, ); } @@ -413,6 +429,11 @@ export async function initRsbuild(rslibConfig: RslibConfig) { return createRsbuild({ rsbuildConfig: { environments: rsbuildConfigObject, + // TODO: temporarily inject the plugin externally + ...getDefaultDtsConfig( + rslibConfig.lib[0]?.dts, + rslibConfig.lib[0]?.bundle, + ), }, }); } diff --git a/packages/core/src/types/config/index.ts b/packages/core/src/types/config/index.ts index b57a89d7..7451ef72 100644 --- a/packages/core/src/types/config/index.ts +++ b/packages/core/src/types/config/index.ts @@ -23,6 +23,14 @@ export type Syntax = // Support inline browserslist query, like defined in package.json | string[]; +export type Dts = + | { + bundle: boolean; + distPath?: string; + tsconfigPath?: string; + } + | false; + export interface LibConfig extends RsbuildConfig { bundle?: boolean; format?: Format; @@ -31,6 +39,7 @@ export interface LibConfig extends RsbuildConfig { /** Support esX and browserslist query */ syntax?: Syntax; }; + dts?: Dts; } export interface RslibConfig extends RsbuildConfig { diff --git a/packages/core/tests/helper.test.ts b/packages/core/tests/helper.test.ts index f01c1dab..71123853 100644 --- a/packages/core/tests/helper.test.ts +++ b/packages/core/tests/helper.test.ts @@ -1,4 +1,3 @@ -import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { calcLongestCommonPath } from '../src/utils/helper'; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 7cdecd6e..b0fa3dfa 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -15,5 +15,10 @@ } }, "include": ["src"], - "exclude": ["**/node_modules"] + "exclude": ["**/node_modules"], + "references": [ + { + "path": "../plugin-dts" + } + ] } diff --git a/packages/plugin-dts/modern.config.ts b/packages/plugin-dts/modern.config.ts new file mode 100644 index 00000000..6ebe5f69 --- /dev/null +++ b/packages/plugin-dts/modern.config.ts @@ -0,0 +1,36 @@ +import { defineConfig, moduleTools } from '@modern-js/module-tools'; + +const externals = ['@rsbuild/core', /[\\/]compiled[\\/]/, /node:/]; +const define = { + RSLIB_VERSION: require('../core/package.json').version, +}; + +export default defineConfig({ + plugins: [moduleTools()], + buildConfig: [ + { + format: 'cjs', + target: 'es2020', + buildType: 'bundle', + autoExtension: true, + externals, + dts: false, + define, + }, + { + format: 'esm', + target: 'es2020', + buildType: 'bundle', + autoExtension: true, + externals, + dts: false, + define, + }, + { + buildType: 'bundleless', + dts: { + only: true, + }, + }, + ], +}); diff --git a/packages/plugin-dts/package.json b/packages/plugin-dts/package.json new file mode 100644 index 00000000..a4e1a63f --- /dev/null +++ b/packages/plugin-dts/package.json @@ -0,0 +1,47 @@ +{ + "name": "rsbuild-plugin-dts", + "version": "0.0.0", + "description": "Dts plugin for Rsbuild", + "homepage": "https://rslib.dev", + "bugs": { + "url": "https://github.com/web-infra-dev/rslib/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/web-infra-dev/rslib", + "directory": "packages/plugin-dts" + }, + "license": "MIT", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": ["dist"], + "scripts": { + "build": "modern build", + "dev": "modern build --watch" + }, + "devDependencies": { + "@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" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/plugin-dts/src/index.ts b/packages/plugin-dts/src/index.ts new file mode 100644 index 00000000..509d862e --- /dev/null +++ b/packages/plugin-dts/src/index.ts @@ -0,0 +1,88 @@ +import { join } from 'node:path'; +import { type RsbuildPlugin, logger } from '@rsbuild/core'; +import * as ts from 'typescript'; +import { loadTsconfig } from './utils'; + +export type pluginDtsOptions = { + bundle: boolean; + distPath?: string; + tsconfigPath?: string; +}; + +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 autoExtension for dts files +export const pluginDts = ( + options: pluginDtsOptions = { bundle: false }, +): RsbuildPlugin => ({ + name: PLUGIN_DTS_NAME, + + setup(api) { + const { tsconfigPath, distPath } = options; + + api.onAfterBuild(() => { + const cwd = process.cwd(); + const configPath = tsconfigPath ? join(cwd, tsconfigPath) : cwd; + const { options: rawCompilerOptions, fileNames } = + loadTsconfig(configPath); + + 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'); + } + + logger.info('TypeScript compilation succeeded\n'); + }); + }, +}); diff --git a/packages/plugin-dts/src/utils.ts b/packages/plugin-dts/src/utils.ts new file mode 100644 index 00000000..08054f58 --- /dev/null +++ b/packages/plugin-dts/src/utils.ts @@ -0,0 +1,20 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import * as ts from 'typescript'; + +export function loadTsconfig(directoryPath: string): ts.ParsedCommandLine { + const tsconfigPath = path.resolve(directoryPath, 'tsconfig.json'); + + if (fs.existsSync(tsconfigPath)) { + const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + const configFileContent = ts.parseJsonConfigFileContent( + configFile.config, + ts.sys, + path.dirname(tsconfigPath), + ); + + return configFileContent; + } + + throw new Error(`tsconfig.json not found in the ${directoryPath}`); +} diff --git a/packages/plugin-dts/tsconfig.json b/packages/plugin-dts/tsconfig.json new file mode 100644 index 00000000..6cee48b0 --- /dev/null +++ b/packages/plugin-dts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "outDir": "./dist", + "baseUrl": "./", + "rootDir": "src", + "declaration": true, + "composite": true, + "module": "ESNext", + "moduleResolution": "Bundler" + }, + "include": ["src"], + "exclude": ["**/node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9d28f8c..ee206ea6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,8 +67,8 @@ importers: specifier: 1.43.1 version: 1.43.1 '@rsbuild/core': - specifier: 1.0.0-alpha.9 - version: 1.0.0-alpha.9 + specifier: 1.0.1-beta.3 + version: 1.0.1-beta.3 '@rslib/core': specifier: workspace:* version: link:../packages/core @@ -104,8 +104,11 @@ importers: packages/core: dependencies: '@rsbuild/core': - specifier: 1.0.0-alpha.9 - version: 1.0.0-alpha.9 + specifier: 1.0.1-beta.3 + version: 1.0.1-beta.3 + rsbuild-plugin-dts: + specifier: workspace:* + version: link:../plugin-dts devDependencies: '@rslib/tsconfig': specifier: workspace:* @@ -135,6 +138,18 @@ importers: specifier: ^5.5.3 version: 5.5.3 + packages/plugin-dts: + devDependencies: + '@rsbuild/core': + specifier: 1.0.1-beta.3 + version: 1.0.1-beta.3 + '@rslib/tsconfig': + specifier: workspace:* + version: link:../../scripts/tsconfig + typescript: + specifier: ^5.5.3 + version: 5.5.3 + scripts/tsconfig: dependencies: '@tsconfig/strictest': @@ -1120,8 +1135,8 @@ packages: cpu: [x64] os: [win32] - '@rsbuild/core@1.0.0-alpha.9': - resolution: {integrity: sha512-NiwBqW6sxoacX6MLy45aeNKjHtKW7wZR3hy0X1Eg/IpUTNSJJkKX9TG92SVcj6RyR8CO+76AXfdEs585Iw4FWg==} + '@rsbuild/core@1.0.1-beta.3': + resolution: {integrity: sha512-/jgx/bWfFu+dNzskpz+M/BLUrXz7bD5ShsXWUZVzUstC871nVqQpCnHn+sEL3W6FrusHYgL7uuUXjLp+nkc+kg==} engines: {node: '>=16.7.0'} hasBin: true @@ -1186,8 +1201,8 @@ packages: resolution: {integrity: sha512-rTGAkDCbq7pyVV2BhkYx0xgK65XEhv4VAkZB5HBhbs2HeB2qkP0yT8NZEgkAtMg5R6Q54dndeZGLFboqYtlF5w==} engines: {node: '>=16.0.0'} - '@rspack/lite-tapable@1.0.0-alpha.3': - resolution: {integrity: sha512-oQJ1iYxfBHcuutAva2HP1dqi9Aka/70PB3Vbq4nI+iAhHErtzaRslI/OcqhEbbmBgYf+Xu6g5vvN6Gxfq69gag==} + '@rspack/lite-tapable@1.0.0-alpha.5': + resolution: {integrity: sha512-B1fNL3en1ohK+QybgjM45PpqcmAmr2LTRUhGvarwouNcj845vjq5clYPqUfFVC0goLmsqx+pt7r+TvpP0Yk67A==} engines: {node: '>=16.0.0'} '@sinclair/typebox@0.27.8': @@ -1358,6 +1373,9 @@ packages: caniuse-lite@1.0.30001641: resolution: {integrity: sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==} + caniuse-lite@1.0.30001643: + resolution: {integrity: sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==} + chai@5.1.1: resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} engines: {node: '>=12'} @@ -3545,12 +3563,12 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.1': optional: true - '@rsbuild/core@1.0.0-alpha.9': + '@rsbuild/core@1.0.1-beta.3': dependencies: '@rspack/core': '@rspack/core-canary@1.0.0-canary-d77b591-20240718094414(@swc/helpers@0.5.11)' - '@rspack/lite-tapable': 1.0.0-alpha.3 + '@rspack/lite-tapable': 1.0.0-alpha.5 '@swc/helpers': 0.5.11 - caniuse-lite: 1.0.30001641 + caniuse-lite: 1.0.30001643 core-js: 3.37.1 postcss: 8.4.39 optionalDependencies: @@ -3600,13 +3618,13 @@ snapshots: '@module-federation/runtime-tools': 0.2.3 '@rspack/binding': '@rspack/binding-canary@1.0.0-canary-d77b591-20240718094414' '@rspack/lite-tapable': '@rspack/lite-tapable-canary@1.0.0-canary-d77b591-20240718094414' - caniuse-lite: 1.0.30001641 + caniuse-lite: 1.0.30001643 optionalDependencies: '@swc/helpers': 0.5.11 '@rspack/lite-tapable-canary@1.0.0-canary-d77b591-20240718094414': {} - '@rspack/lite-tapable@1.0.0-alpha.3': {} + '@rspack/lite-tapable@1.0.0-alpha.5': {} '@sinclair/typebox@0.27.8': {} @@ -3786,6 +3804,8 @@ snapshots: caniuse-lite@1.0.30001641: {} + caniuse-lite@1.0.30001643: {} + chai@5.1.1: dependencies: assertion-error: 2.0.1