From 2d4f6b06044815dd9cdda3750feb5c178c046f71 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Tue, 23 Nov 2021 22:46:08 +0800 Subject: [PATCH] feat: add `--dts-only` flag to emit declarations only --- src/cli-main.ts | 6 +- src/index.ts | 220 +++++++++++++++++++++++---------------------- src/options.ts | 17 ++-- src/rollup.ts | 8 +- test/index.test.ts | 30 +++++-- 5 files changed, 153 insertions(+), 128 deletions(-) diff --git a/src/cli-main.ts b/src/cli-main.ts index a8d0e726..73f9380a 100644 --- a/src/cli-main.ts +++ b/src/cli-main.ts @@ -38,6 +38,7 @@ export async function main(options: Options = {}) { ) .option('--dts [entry]', 'Generate declaration file') .option('--dts-resolve', 'Resolve externals types used for d.ts files') + .option('--dts-only', 'Emit declaration files only') .option( '--sourcemap [option]', 'Generate sourcemap, "external", "inline", "both"' @@ -99,7 +100,7 @@ export async function main(options: Options = {}) { const external = ensureArray(flags.external) options.external = external } - if (flags.dts || flags.dtsResolve) { + if (flags.dts || flags.dtsResolve || flags.dtsOnly) { options.dts = {} if (typeof flags.dts === 'string') { options.dts.entry = flags.dts @@ -107,6 +108,9 @@ export async function main(options: Options = {}) { if (flags.dtsResolve) { options.dts.resolve = flags.dtsResolve } + if (flags.dtsOnly) { + options.dts.only = true + } } if (flags.inject) { const inject = ensureArray(flags.inject) diff --git a/src/index.ts b/src/index.ts index e3fa5de6..af68344e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import path from 'path' import fs from 'fs' import { Worker } from 'worker_threads' -import type { MarkRequired, Buildable } from 'ts-essentials' +import type { MarkRequired } from 'ts-essentials' import { removeFiles, debouncePromise } from './utils' import { loadTsupConfig, resolveTsConfig } from './load' import glob from 'globby' @@ -13,15 +13,17 @@ import execa from 'execa' import kill from 'tree-kill' import { version } from '../package.json' import { createLogger, setSilent } from './log' -import { Format, Options } from './options' +import { DtsConfig, Format, Options } from './options' import { runEsbuild } from './esbuild' export type { Format, Options } -export type NormalizedOptions = MarkRequired< - Options, - 'entryPoints' | 'format' | 'outDir' -> +export type NormalizedOptions = Omit< + MarkRequired, + 'dts' +> & { + dts?: DtsConfig +} export const defineConfig = ( options: @@ -49,7 +51,7 @@ const normalizeOptions = async ( optionsFromConfigFile: Options | undefined, optionsOverride: Options ) => { - const options: Buildable = { + const options: Options = { ...optionsFromConfigFile, ...optionsOverride, } @@ -102,6 +104,12 @@ const normalizeOptions = async ( options.target = 'node12' } + if (options.dts === true) { + options.dts = {} + } else if (typeof options.dts === 'string') { + options.dts = { entry: options.dts } + } + return options as NormalizedOptions } @@ -128,117 +136,119 @@ export async function build(_options: Options) { logger.info('CLI', 'Running in watch mode') } - let existingOnSuccess: ChildProcess | undefined + if (!options.dts?.only) { + let existingOnSuccess: ChildProcess | undefined - async function killPreviousProcess() { - if (existingOnSuccess) { - await killProcess({ - pid: existingOnSuccess.pid, - }) - existingOnSuccess = undefined + async function killPreviousProcess() { + if (existingOnSuccess) { + await killProcess({ + pid: existingOnSuccess.pid, + }) + existingOnSuccess = undefined + } + } + + const debouncedBuildAll = debouncePromise( + () => { + return buildAll() + }, + 100, + handleError + ) + + const buildAll = async () => { + const killPromise = killPreviousProcess() + + if (options.clean) { + const extraPatterns = Array.isArray(options.clean) + ? options.clean + : [] + await removeFiles( + ['**/*', '!**/*.d.ts', ...extraPatterns], + options.outDir + ) + logger.info('CLI', 'Cleaning output folder') + } + + const css: Map = new Map() + await Promise.all([ + ...options.format.map((format, index) => + runEsbuild(options, { + format, + css: index === 0 ? css : undefined, + logger, + }) + ), + ]) + await killPromise + if (options.onSuccess) { + const parts = parseArgsStringToArgv(options.onSuccess) + const exec = parts[0] + const args = parts.splice(1) + existingOnSuccess = execa(exec, args, { + stdio: 'inherit', + }) + } } - } - const debouncedBuildAll = debouncePromise( - () => { - return buildAll() - }, - 100, - handleError - ) + const startWatcher = async () => { + if (!options.watch) return - const buildAll = async () => { - const killPromise = killPreviousProcess() + const { watch } = await import('chokidar') - if (options.clean) { - const extraPatterns = Array.isArray(options.clean) - ? options.clean + const customIgnores = options.ignoreWatch + ? Array.isArray(options.ignoreWatch) + ? options.ignoreWatch + : [options.ignoreWatch] : [] - await removeFiles( - ['**/*', '!**/*.d.ts', ...extraPatterns], - options.outDir + + const ignored = [ + '**/{.git,node_modules}/**', + options.outDir, + ...customIgnores, + ] + + const watchPaths = + typeof options.watch === 'boolean' + ? '.' + : Array.isArray(options.watch) + ? options.watch.filter( + (path): path is string => typeof path === 'string' + ) + : options.watch + + logger.info( + 'CLI', + `Watching for changes in ${ + Array.isArray(watchPaths) + ? watchPaths.map((v) => '"' + v + '"').join(' | ') + : '"' + watchPaths + '"' + }` + ) + logger.info( + 'CLI', + `Ignoring changes in ${ignored + .map((v) => '"' + v + '"') + .join(' | ')}` ) - logger.info('CLI', 'Cleaning output folder') - } - const css: Map = new Map() - await Promise.all([ - ...options.format.map((format, index) => - runEsbuild(options, { - format, - css: index === 0 ? css : undefined, - logger, - }) - ), - ]) - await killPromise - if (options.onSuccess) { - const parts = parseArgsStringToArgv(options.onSuccess) - const exec = parts[0] - const args = parts.splice(1) - existingOnSuccess = execa(exec, args, { - stdio: 'inherit', + const watcher = watch(watchPaths, { + ignoreInitial: true, + ignorePermissionErrors: true, + ignored, + }) + watcher.on('all', async (type, file) => { + logger.info('CLI', `Change detected: ${type} ${file}`) + debouncedBuildAll() }) } - } - const startWatcher = async () => { - if (!options.watch) return - - const { watch } = await import('chokidar') - - const customIgnores = options.ignoreWatch - ? Array.isArray(options.ignoreWatch) - ? options.ignoreWatch - : [options.ignoreWatch] - : [] - - const ignored = [ - '**/{.git,node_modules}/**', - options.outDir, - ...customIgnores, - ] - - const watchPaths = - typeof options.watch === 'boolean' - ? '.' - : Array.isArray(options.watch) - ? options.watch.filter( - (path): path is string => typeof path === 'string' - ) - : options.watch - - logger.info( - 'CLI', - `Watching for changes in ${ - Array.isArray(watchPaths) - ? watchPaths.map((v) => '"' + v + '"').join(' | ') - : '"' + watchPaths + '"' - }` - ) - logger.info( - 'CLI', - `Ignoring changes in ${ignored - .map((v) => '"' + v + '"') - .join(' | ')}` - ) + logger.info('CLI', `Target: ${options.target}`) - const watcher = watch(watchPaths, { - ignoreInitial: true, - ignorePermissionErrors: true, - ignored, - }) - watcher.on('all', async (type, file) => { - logger.info('CLI', `Change detected: ${type} ${file}`) - debouncedBuildAll() - }) - } + await buildAll() - logger.info('CLI', `Target: ${options.target}`) - - await buildAll() - - startWatcher() + startWatcher() + } if (options.dts) { const hasTypescript = resolveFrom.silent(process.cwd(), 'typescript') diff --git a/src/options.ts b/src/options.ts index 9ed803b5..6df9a767 100644 --- a/src/options.ts +++ b/src/options.ts @@ -3,6 +3,14 @@ import type { InputOption } from 'rollup' export type Format = 'cjs' | 'esm' | 'iife' +export type DtsConfig = { + entry?: InputOption + /** Resolve external types used in dts files from node_modules */ + resolve?: boolean | (string | RegExp)[] + /** Emit declaration files only */ + only?: boolean +} + /** * The options available in tsup.config.ts * Not all of them are available from CLI flags @@ -40,14 +48,7 @@ export type Options = { define?: { [k: string]: string } - dts?: - | boolean - | string - | { - entry?: InputOption - /** Resolve external types used in dts files from node_modules */ - resolve?: boolean | (string | RegExp)[] - } + dts?: boolean | string | DtsConfig sourcemap?: BuildOptions['sourcemap'] /** Don't bundle these packages */ external?: (string | RegExp)[] diff --git a/src/rollup.ts b/src/rollup.ts index dbc4f2ed..66a17774 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -83,12 +83,8 @@ const getRollupConfig = async ( const compilerOptions = loadCompilerOptions(options.tsconfig) - const dtsOptions = - typeof options.dts === 'string' - ? { entry: options.dts } - : options.dts === true - ? { entry: options.entryPoints } - : { entry: options.entryPoints, ...options.dts } + const dtsOptions = options.dts || {} + dtsOptions.entry = dtsOptions.entry || options.entryPoints if (Array.isArray(dtsOptions.entry) && dtsOptions.entry.length > 1) { dtsOptions.entry = toObjectEntry(dtsOptions.entry) diff --git a/test/index.test.ts b/test/index.test.ts index 06ab72bc..4aa89814 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -769,21 +769,18 @@ test('bundle svelte', async () => { }) test('bundle svelte without styles', async () => { - const { outFiles } = await run( - getTestName(), - { - 'input.ts': `import App from './App.svelte' + const { outFiles } = await run(getTestName(), { + 'input.ts': `import App from './App.svelte' export { App } `, - 'App.svelte': ` + 'App.svelte': ` {msg} `, - } - ) + }) expect(outFiles).toMatchInlineSnapshot(` Array [ @@ -1105,7 +1102,7 @@ test('multiple entry with the same base name', async () => { `) }) -test('backslash in entry', async () => { +test('windows: backslash in entry', async () => { const { outFiles } = await run( getTestName(), { 'src/input.ts': `export const foo = 1` }, @@ -1119,3 +1116,20 @@ test('backslash in entry', async () => { ] `) }) + +test('emit declaration files only', async () => { + const { outFiles } = await run( + getTestName(), + { + 'input.ts': `export const foo = 1`, + }, + { + flags: ['--dts-only'], + } + ) + expect(outFiles).toMatchInlineSnapshot(` + Array [ + "input.d.ts", + ] + `) +})