Skip to content

Commit

Permalink
Add experimentalDts.only
Browse files Browse the repository at this point in the history
  • Loading branch information
aryaemami59 committed Oct 22, 2024
1 parent bf2aaf2 commit b3893b5
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 44 deletions.
180 changes: 179 additions & 1 deletion src/api-extractor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import path from 'node:path'
import type { InputOption } from 'rollup'
import { glob } from 'tinyglobby'
import { handleError } from './errors'
import {
type ExportDeclaration,
Expand All @@ -15,7 +17,12 @@ import {
toAbsolutePath,
writeFileSync,
} from './utils'
import type { Format, NormalizedOptions } from './options'
import type {
ExperimentalDtsConfig,
Format,
NormalizedExperimentalDtsConfig,
NormalizedOptions,
} from './options'
import type {
ExtractorResult,
IConfigFile,
Expand Down Expand Up @@ -167,3 +174,174 @@ export async function runDtsRollup(
logger.error('dts', 'Build error')
}
}

/**
* Converts an array of {@linkcode NormalizedOptions.entry | entry paths}
* into an object where the keys represent the output
* file names (without extensions) and the values
* represent the corresponding input file paths.
*
* @param arrayOfEntries - An array of file path entries as strings.
* @returns An object where the keys are the output file name and the values are the input file name.
*
* @example
* <caption>### Convert an array of entry points to an entry point object</caption>
*
* ```ts
* import { defineConfig } from 'tsup'
*
* export default defineConfig({
* entry: ['src/index.ts', 'src/types.ts'], // Becomes `{ index: 'src/index.ts', types: 'src/types.ts' }`
* format: ['esm', 'cjs'],
* })
* ```
*
* @internal
*/
const convertArrayEntriesToObjectEntries = (arrayOfEntries: string[]) => {
const objectEntries = Object.fromEntries(
arrayOfEntries.map(
(entry) =>
[
path.posix.join(
...entry
.split(path.posix.sep)
.slice(1, -1)
.concat(path.parse(entry).name),
),
entry,
] as const,
),
)

return objectEntries
}

/**
* Resolves and standardizes entry paths into an object format.
* If the provided entry is a string or an array of strings, it resolves
* any potential glob patterns using
* {@linkcode glob | tiny-glob's glob function}
* and converts the result into an entry object.
* If the input is already an object, it is returned as-is.
*
* @param entryPaths - The entry paths to resolve. Can be a string, an array of strings, or an object.
* @returns A {@linkcode Promise} that resolves to the standardized entry paths in object format.
*
* @example
*
* ```ts
* import { defineConfig } from 'tsup'
*
* export default defineConfig({
* entry: { index: 'src/index.ts' },
* format: ['esm', 'cjs'],
* experimentalDts: { entry: 'src/**\/*.ts' },
* // experimentalDts: { entry: 'src/**\/*.ts' }
* // becomes experimentalDts: { entry: { index: 'src/index.ts', types: 'src/types.ts } }
* })
* ```
*
* @internal
*/
const resolveEntryPaths = async (entryPaths: InputOption) => {
const resolvedEntryPaths =
typeof entryPaths === 'string' || Array.isArray(entryPaths)
? convertArrayEntriesToObjectEntries(await glob(entryPaths))
: entryPaths

return resolvedEntryPaths
}

/**
* Normalizes the
* {@linkcode NormalizedExperimentalDtsConfig | experimental DTS options}
* by resolving entry paths and merging the provided
* TypeScript configuration options.
*
* @param options - The options containing entry points and experimental DTS configuration.
* @param tsconfig - The loaded TypeScript configuration data.
* @returns The normalized experimental DTS configuration.
*
* @internal
*/
export const normalizeExperimentalDtsOptions = async (
options: Partial<NormalizedOptions>,
tsconfig: any,
) => {
if (!options.entry || !options.experimentalDts) {
return
}

const resolvedEntryPaths = await resolveEntryPaths(
options.experimentalDts.entry || options.entry,
)

// Fallback to `options.entry` if we end up with an empty object.
const experimentalDtsObjectEntry =
Object.keys(resolvedEntryPaths).length === 0
? Array.isArray(options.entry)
? convertArrayEntriesToObjectEntries(options.entry)
: options.entry
: resolvedEntryPaths

const normalizedExperimentalDtsOptions: NormalizedExperimentalDtsConfig = {
only: options.experimentalDts?.only || false,

compilerOptions: {
...(tsconfig.data.compilerOptions || {}),
...(options.experimentalDts?.compilerOptions || {}),
},

entry: experimentalDtsObjectEntry,
}

return normalizedExperimentalDtsOptions
}

/**
* Normalizes the initial experimental DTS configuration
* into a consistent {@linkcode NormalizedExperimentalDtsConfig | experimentalDts config object}.
*
* This function handles different types of
* {@linkcode NormalizedExperimentalDtsConfig | experimentalDts} inputs:
* - If {@linkcode experimentalDts} is a `boolean`, it returns a default object with an empty entry (`{ entry: {} }`) if `true`, or `undefined` if `false`.
* - If {@linkcode experimentalDts} is a `string`, it returns an object with the string as the `entry` property.
* - If {@linkcode experimentalDts} is already an object ({@linkcode NormalizedExperimentalDtsConfig}), it returns the object as is.
*
* The function focuses specifically on normalizing the **initial** {@linkcode NormalizedExperimentalDtsConfig | experimentalDts configuration}.
*
* @param experimentalDts - The {@linkcode NormalizedExperimentalDtsConfig | experimentalDts} value, which can be a `boolean`, `string`, `object`, or `undefined`.
* @returns A normalized {@linkcode NormalizedExperimentalDtsConfig | experimentalDts config object}, or `undefined` if input was `false` or `undefined`.
*
* @internal
*/
export const normalizeInitialExperimentalDtsOptions = async (
experimentalDts: boolean | string | ExperimentalDtsConfig | undefined,
): Promise<NormalizedExperimentalDtsConfig | undefined> => {
if (experimentalDts == null) {
return
}

if (typeof experimentalDts === 'boolean')
return experimentalDts ? { only: false, entry: {} } : undefined

if (typeof experimentalDts === 'string') {
return {
only: false,

entry: convertArrayEntriesToObjectEntries(await glob(experimentalDts)),
}
}

return {
...experimentalDts,

only: experimentalDts?.only || false,

entry:
experimentalDts?.entry == null
? {}
: await resolveEntryPaths(experimentalDts.entry),
}
}
19 changes: 19 additions & 0 deletions src/cli-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export async function main(options: Options = {}) {
'--experimental-dts [entry]',
'Generate declaration file (experimental)',
)
.option(
'--experimental-dts-only',
'Emit declaration files only (experimental)',
)
.alias('--experimentalDts-only')
.alias('--experimentalDtsOnly')
.option(
'--sourcemap [inline]',
'Generate external sourcemap, or inline source: --sourcemap inline',
Expand Down Expand Up @@ -133,6 +139,19 @@ export async function main(options: Options = {}) {
options.dts.only = true
}
}

if (flags.experimentalDts || flags.experimentalDtsOnly) {
options.experimentalDts = {}

if (typeof flags.experimentalDts === 'string') {
options.experimentalDts.entry = flags.experimentalDts
}

if (flags.experimentalDtsOnly) {
options.experimentalDts.only = true
}
}

if (flags.inject) {
const inject = ensureArray(flags.inject)
options.inject = inject
Expand Down
50 changes: 16 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ import kill from 'tree-kill'
import { version } from '../package.json'
import { PrettyError, handleError } from './errors'
import { getAllDepsHash, loadTsupConfig } from './load'
import {
type MaybePromise,
debouncePromise,
removeFiles,
slash,
toObjectEntry,
} from './utils'
import { type MaybePromise, debouncePromise, removeFiles, slash } from './utils'
import { createLogger, setSilent } from './log'
import { runEsbuild } from './esbuild'
import { shebang } from './plugins/shebang'
Expand All @@ -26,7 +20,11 @@ import { treeShakingPlugin } from './plugins/tree-shaking'
import { copyPublicDir, isInPublicDir } from './lib/public-dir'
import { terserPlugin } from './plugins/terser'
import { runTypeScriptCompiler } from './tsc'
import { runDtsRollup } from './api-extractor'
import {
normalizeExperimentalDtsOptions,
normalizeInitialExperimentalDtsOptions,
runDtsRollup,
} from './api-extractor'
import { cjsInterop } from './plugins/cjs-interop'
import type { Format, KILL_SIGNAL, NormalizedOptions, Options } from './options'

Expand Down Expand Up @@ -92,20 +90,9 @@ const normalizeOptions = async (
: typeof _options.dts === 'string'
? { entry: _options.dts }
: _options.dts,
experimentalDts: _options.experimentalDts
? typeof _options.experimentalDts === 'boolean'
? _options.experimentalDts
? { entry: {} }
: undefined
: typeof _options.experimentalDts === 'string'
? {
entry: toObjectEntry(_options.experimentalDts),
}
: {
..._options.experimentalDts,
entry: toObjectEntry(_options.experimentalDts.entry || {}),
}
: undefined,
experimentalDts: await normalizeInitialExperimentalDtsOptions(
_options.experimentalDts,
),
}

setSilent(options.silent)
Expand Down Expand Up @@ -151,17 +138,12 @@ const normalizeOptions = async (
...(options.dts.compilerOptions || {}),
}
}
if (options.experimentalDts) {
options.experimentalDts.compilerOptions = {
...(tsconfig.data.compilerOptions || {}),
...(options.experimentalDts.compilerOptions || {}),
}
options.experimentalDts.entry = toObjectEntry(
Object.keys(options.experimentalDts.entry).length > 0
? options.experimentalDts.entry
: options.entry,
)
}

options.experimentalDts = await normalizeExperimentalDtsOptions(
options,
tsconfig,
)

if (!options.target) {
options.target = tsconfig.data?.compilerOptions?.target?.toLowerCase()
}
Expand Down Expand Up @@ -270,7 +252,7 @@ export async function build(_options: Options) {
}

const mainTasks = async () => {
if (!options.dts?.only) {
if (!options.experimentalDts?.only && !options.dts?.only) {
let onSuccessProcess: ExecChild | undefined
let onSuccessCleanup: (() => any) | undefined | void
/** Files imported by the entry */
Expand Down
37 changes: 36 additions & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,40 @@ export type DtsConfig = {
}

export type ExperimentalDtsConfig = {
/**
* When set to `true`, this option will emit only
* TypeScript declaration (`.d.ts`) files and skip generating the
* corresponding JavaScript files during the build process.
*
* @example
* <caption>#### Generate only TypeScript declaration files</caption>
*
* ```ts
* import { defineConfig } from 'tsup'
*
* export default defineConfig({
* entry: { index: 'src/index.ts' },
* format: ['esm', 'cjs'],
* experimentalDts: { only: true },
* })
* ```
*
* @default false
*
* @since 8.4.0
*
* #### CLI Equivalent:
* You can use the following CLI commands to achieve the same result:
*
* ```bash
* tsup src/index.ts --experimental-dts-only
* # or
* tsup src/index.ts --experimentalDts-only
* # or
* tsup src/index.ts --experimentalDtsOnly
* ```
*/
only?: boolean
entry?: InputOption
/**
* Overrides `compilerOptions`
Expand Down Expand Up @@ -258,7 +292,8 @@ export type Options = {
removeNodeProtocol?: boolean
}

export interface NormalizedExperimentalDtsConfig {
export interface NormalizedExperimentalDtsConfig extends ExperimentalDtsConfig {
only: boolean
entry: { [entryAlias: string]: string }
compilerOptions?: any
}
Expand Down
Loading

0 comments on commit b3893b5

Please sign in to comment.