|
1 | 1 | import fs from 'node:fs'
|
2 | 2 | import path from 'node:path'
|
3 | 3 | import resolveFrom from 'resolve-from'
|
| 4 | +import type { InputOption } from 'rollup' |
4 | 5 | import strip from 'strip-json-comments'
|
5 | 6 | import { glob } from 'tinyglobby'
|
6 |
| -import type { Entry, Format } from './options' |
| 7 | +import type { |
| 8 | + Entry, |
| 9 | + Format, |
| 10 | + NormalizedExperimentalDtsConfig, |
| 11 | + NormalizedOptions, |
| 12 | + Options, |
| 13 | +} from './options' |
7 | 14 |
|
8 | 15 | export type MaybePromise<T> = T | Promise<T>
|
9 | 16 |
|
@@ -242,3 +249,176 @@ export function writeFileSync(filePath: string, content: string) {
|
242 | 249 | fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
243 | 250 | fs.writeFileSync(filePath, content)
|
244 | 251 | }
|
| 252 | + |
| 253 | +/** |
| 254 | + * Replaces TypeScript declaration file |
| 255 | + * extensions (`.d.ts`, `.d.mts`, `.d.cts`) |
| 256 | + * with their corresponding JavaScript variants (`.js`, `.mjs`, `.cjs`). |
| 257 | + * |
| 258 | + * @param dtsFilePath - The file path to be transformed. |
| 259 | + * @returns The updated file path with the JavaScript extension. |
| 260 | + * |
| 261 | + * @internal |
| 262 | + */ |
| 263 | +export function replaceDtsWithJsExtensions(dtsFilePath: string) { |
| 264 | + return dtsFilePath.replace( |
| 265 | + /\.d\.(ts|mts|cts)$/, |
| 266 | + (_, fileExtension: string) => { |
| 267 | + switch (fileExtension) { |
| 268 | + case 'ts': |
| 269 | + return '.js' |
| 270 | + case 'mts': |
| 271 | + return '.mjs' |
| 272 | + case 'cts': |
| 273 | + return '.cjs' |
| 274 | + default: |
| 275 | + return '' |
| 276 | + } |
| 277 | + }, |
| 278 | + ) |
| 279 | +} |
| 280 | + |
| 281 | +/** |
| 282 | + * Converts an array of {@link NormalizedOptions.entry | entry paths} |
| 283 | + * into an object where the keys represent the output |
| 284 | + * file names (without extensions) and the values |
| 285 | + * represent the corresponding input file paths. |
| 286 | + * |
| 287 | + * @param arrayOfEntries - An array of file path entries as strings. |
| 288 | + * @returns An object where the keys are the output file name and the values are the input file name. |
| 289 | + * |
| 290 | + * @example |
| 291 | + * |
| 292 | + * ```ts |
| 293 | + * import { defineConfig } from 'tsup' |
| 294 | + * |
| 295 | + * export default defineConfig({ |
| 296 | + * entry: ['src/index.ts', 'src/types.ts'], |
| 297 | + * // Becomes `{ index: 'src/index.ts', types: 'src/types.ts' }` |
| 298 | + * }) |
| 299 | + * ``` |
| 300 | + * |
| 301 | + * @internal |
| 302 | + */ |
| 303 | +const convertArrayEntriesToObjectEntries = (arrayOfEntries: string[]) => { |
| 304 | + const objectEntries = Object.fromEntries( |
| 305 | + arrayOfEntries.map( |
| 306 | + (entry) => |
| 307 | + [ |
| 308 | + path.posix.join( |
| 309 | + ...entry |
| 310 | + .split(path.posix.sep) |
| 311 | + .slice(1, -1) |
| 312 | + .concat(path.parse(entry).name), |
| 313 | + ), |
| 314 | + entry, |
| 315 | + ] as const, |
| 316 | + ), |
| 317 | + ) |
| 318 | + |
| 319 | + return objectEntries |
| 320 | +} |
| 321 | + |
| 322 | +/** |
| 323 | + * Resolves and standardizes entry paths into an object format. If the provided |
| 324 | + * entry is a string or an array of strings, it resolves any potential glob |
| 325 | + * patterns amd converts the result into an entry object. If the input is |
| 326 | + * already an object, it is returned as-is. |
| 327 | + * |
| 328 | + * @example |
| 329 | + * |
| 330 | + * ```ts |
| 331 | + * import { defineConfig } from 'tsup' |
| 332 | + * |
| 333 | + * export default defineConfig({ |
| 334 | + * entry: { index: 'src/index.ts' }, |
| 335 | + * format: ['esm', 'cjs'], |
| 336 | + * experimentalDts: { entry: 'src/**\/*.ts' }, |
| 337 | + * // becomes experimentalDts: { entry: { index: 'src/index.ts', types: 'src/types.ts } } |
| 338 | + * }) |
| 339 | + * ``` |
| 340 | + * |
| 341 | + * @internal |
| 342 | + */ |
| 343 | +const resolveEntryPaths = async (entryPaths: InputOption) => { |
| 344 | + const resolvedEntryPaths = |
| 345 | + typeof entryPaths === 'string' || Array.isArray(entryPaths) |
| 346 | + ? convertArrayEntriesToObjectEntries(await glob(entryPaths)) |
| 347 | + : entryPaths |
| 348 | + |
| 349 | + return resolvedEntryPaths |
| 350 | +} |
| 351 | + |
| 352 | +/** |
| 353 | + * Resolves the |
| 354 | + * {@link NormalizedExperimentalDtsConfig | experimental DTS config} by |
| 355 | + * resolving entry paths and merging the provided TypeScript configuration |
| 356 | + * options. |
| 357 | + * |
| 358 | + * @param options - The options containing entry points and experimental DTS |
| 359 | + * configuration. |
| 360 | + * @param tsconfig - The loaded TypeScript configuration data. |
| 361 | + * |
| 362 | + * @internal |
| 363 | + */ |
| 364 | +export const resolveExperimentalDtsConfig = async ( |
| 365 | + options: NormalizedOptions, |
| 366 | + tsconfig: any, |
| 367 | +): Promise<NormalizedExperimentalDtsConfig> => { |
| 368 | + const resolvedEntryPaths = await resolveEntryPaths( |
| 369 | + options.experimentalDts?.entry || options.entry, |
| 370 | + ) |
| 371 | + |
| 372 | + // Fallback to `options.entry` if we end up with an empty object. |
| 373 | + const experimentalDtsObjectEntry = |
| 374 | + Object.keys(resolvedEntryPaths).length === 0 |
| 375 | + ? Array.isArray(options.entry) |
| 376 | + ? convertArrayEntriesToObjectEntries(options.entry) |
| 377 | + : options.entry |
| 378 | + : resolvedEntryPaths |
| 379 | + |
| 380 | + const normalizedExperimentalDtsConfig: NormalizedExperimentalDtsConfig = { |
| 381 | + compilerOptions: { |
| 382 | + ...(tsconfig.data.compilerOptions || {}), |
| 383 | + ...(options.experimentalDts?.compilerOptions || {}), |
| 384 | + }, |
| 385 | + |
| 386 | + entry: experimentalDtsObjectEntry, |
| 387 | + } |
| 388 | + |
| 389 | + return normalizedExperimentalDtsConfig |
| 390 | +} |
| 391 | + |
| 392 | +/** |
| 393 | + * Resolves the initial experimental DTS configuration into a consistent |
| 394 | + * {@link NormalizedExperimentalDtsConfig} object. |
| 395 | + * |
| 396 | + * @internal |
| 397 | + */ |
| 398 | +export const resolveInitialExperimentalDtsConfig = async ( |
| 399 | + experimentalDts: Options['experimentalDts'], |
| 400 | +): Promise<NormalizedExperimentalDtsConfig | undefined> => { |
| 401 | + if (experimentalDts == null) { |
| 402 | + return |
| 403 | + } |
| 404 | + |
| 405 | + if (typeof experimentalDts === 'boolean') |
| 406 | + return experimentalDts ? { entry: {} } : undefined |
| 407 | + |
| 408 | + if (typeof experimentalDts === 'string') { |
| 409 | + // Treats the string as a glob pattern, resolving it to entry paths and |
| 410 | + // returning an object with the `entry` property. |
| 411 | + return { |
| 412 | + entry: convertArrayEntriesToObjectEntries(await glob(experimentalDts)), |
| 413 | + } |
| 414 | + } |
| 415 | + |
| 416 | + return { |
| 417 | + ...experimentalDts, |
| 418 | + |
| 419 | + entry: |
| 420 | + experimentalDts?.entry == null |
| 421 | + ? {} |
| 422 | + : await resolveEntryPaths(experimentalDts.entry), |
| 423 | + } |
| 424 | +} |
0 commit comments