From 726dcc7f7d69877a9ea340f49da572d27a3e8309 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Sat, 5 Aug 2023 20:45:22 +0800 Subject: [PATCH 1/4] test(typescript): add test case for implicitly included declarations --- packages/typescript/test/declarations.ts | 30 +++++++++++++++++++ .../custom-types.ts | 1 + .../main.ts | 1 + .../tsconfig.json | 3 ++ 4 files changed, 35 insertions(+) create mode 100644 packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts create mode 100644 packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts create mode 100644 packages/typescript/test/fixtures/implicitly-included-type-only-file/tsconfig.json diff --git a/packages/typescript/test/declarations.ts b/packages/typescript/test/declarations.ts index 36ccdc85c..07847cc98 100644 --- a/packages/typescript/test/declarations.ts +++ b/packages/typescript/test/declarations.ts @@ -117,6 +117,36 @@ test.serial('supports creating declaration files for interface only source file' t.true(declaration.includes('//# sourceMappingURL=interface.d.ts.map'), declaration); }); +test.serial( + 'supports creating declaration files for type-only source files that are implicitly included', + async (t) => { + const bundle = await rollup({ + input: 'fixtures/implicitly-included-type-only-file/main.ts', + plugins: [ + typescript({ + tsconfig: 'fixtures/implicitly-included-type-only-file/tsconfig.json', + declarationDir: 'fixtures/implicitly-included-type-only-file/dist/types', + declaration: true + }), + onwarn + ] + }); + const output = await getCode( + bundle, + { format: 'es', dir: 'fixtures/implicitly-included-type-only-file/dist' }, + true + ); + const declaration = output[1].source as string; + + t.deepEqual( + output.map((out) => out.fileName), + ['main.js', 'types/main.d.ts', 'types/custom-types.d.ts'] + ); + + t.true(declaration.includes('export type MyNumber = number;'), declaration); + } +); + test.serial('supports creating declaration files in declarationDir', async (t) => { const bundle = await rollup({ input: 'fixtures/basic/main.ts', diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts b/packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts new file mode 100644 index 000000000..f6a07df9b --- /dev/null +++ b/packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts @@ -0,0 +1 @@ +export type MyNumber = number; diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts b/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts new file mode 100644 index 000000000..cd5bc51f6 --- /dev/null +++ b/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts @@ -0,0 +1 @@ +export { MyNumber } from './custom-types'; diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/tsconfig.json b/packages/typescript/test/fixtures/implicitly-included-type-only-file/tsconfig.json new file mode 100644 index 000000000..346e6cea9 --- /dev/null +++ b/packages/typescript/test/fixtures/implicitly-included-type-only-file/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["main.ts"] +} From 5369e67cd39845e3c324b8b6062f0e0f4192e694 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Sat, 5 Aug 2023 23:14:27 +0800 Subject: [PATCH 2/4] fix(typescript): emit declarations for (implicit) type-only source files --- packages/typescript/src/index.ts | 77 +++++++++++++++------------ packages/typescript/src/outputFile.ts | 15 ++++-- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index be243d23b..293e341d9 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -12,7 +12,13 @@ import createModuleResolver from './moduleResolution'; import { getPluginOptions } from './options/plugin'; import { emitParsedOptionsErrors, parseTypescriptConfig } from './options/tsconfig'; import { validatePaths, validateSourceMap } from './options/validate'; -import findTypescriptOutput, { getEmittedFile, normalizePath, emitFile } from './outputFile'; +import findTypescriptOutput, { + getEmittedFile, + normalizePath, + emitFile, + isDeclarationOutputFile, + isMapOutputFile +} from './outputFile'; import { preflight } from './preflight'; import createWatchProgram, { WatchProgramHelper } from './watchProgram'; import TSCache from './tscache'; @@ -150,40 +156,41 @@ export default function typescript(options: RollupTypescriptOptions = {}): Plugi }, async generateBundle(outputOptions) { - parsedOptions.fileNames.forEach((fileName) => { - const output = findTypescriptOutput(ts, parsedOptions, fileName, emittedFiles, tsCache); - output.declarations.forEach((id) => { - const code = getEmittedFile(id, emittedFiles, tsCache); - if (!code || !parsedOptions.options.declaration) { - return; - } - - let baseDir: string | undefined; - if (outputOptions.dir) { - baseDir = outputOptions.dir; - } else if (outputOptions.file) { - // find common path of output.file and configured declation output - const outputDir = path.dirname(outputOptions.file); - const configured = path.resolve( - parsedOptions.options.declarationDir || - parsedOptions.options.outDir || - tsconfig || - process.cwd() - ); - const backwards = path - .relative(outputDir, configured) - .split(path.sep) - .filter((v) => v === '..') - .join(path.sep); - baseDir = path.normalize(`${outputDir}/${backwards}`); - } - if (!baseDir) return; - - this.emitFile({ - type: 'asset', - fileName: normalizePath(path.relative(baseDir, id)), - source: code - }); + const declarationAndMapFiles = [...emittedFiles.keys()].filter( + (fileName) => isDeclarationOutputFile(fileName) || isMapOutputFile(fileName) + ); + + declarationAndMapFiles.forEach((id) => { + const code = getEmittedFile(id, emittedFiles, tsCache); + if (!code || !parsedOptions.options.declaration) { + return; + } + + let baseDir: string | undefined; + if (outputOptions.dir) { + baseDir = outputOptions.dir; + } else if (outputOptions.file) { + // find common path of output.file and configured declation output + const outputDir = path.dirname(outputOptions.file); + const configured = path.resolve( + parsedOptions.options.declarationDir || + parsedOptions.options.outDir || + tsconfig || + process.cwd() + ); + const backwards = path + .relative(outputDir, configured) + .split(path.sep) + .filter((v) => v === '..') + .join(path.sep); + baseDir = path.normalize(`${outputDir}/${backwards}`); + } + if (!baseDir) return; + + this.emitFile({ + type: 'asset', + fileName: normalizePath(path.relative(baseDir, id)), + source: code }); }); diff --git a/packages/typescript/src/outputFile.ts b/packages/typescript/src/outputFile.ts index e2ff67220..4dc0de04d 100644 --- a/packages/typescript/src/outputFile.ts +++ b/packages/typescript/src/outputFile.ts @@ -16,15 +16,22 @@ export interface TypescriptSourceDescription extends Partial /** * Checks if the given OutputFile represents some code */ -function isCodeOutputFile(name: string): boolean { - return !isMapOutputFile(name) && !name.endsWith('.d.ts'); +export function isCodeOutputFile(name: string): boolean { + return !isMapOutputFile(name) && !isDeclarationOutputFile(name); } /** * Checks if the given OutputFile represents some source map */ -function isMapOutputFile(name: string): boolean { - return name.endsWith('.map'); +export function isMapOutputFile(name: string): boolean { + return name.endsWith('ts.map'); +} + +/** + * Checks if the given OutputFile represents some declaration + */ +export function isDeclarationOutputFile(name: string): boolean { + return /\.d\.[cm]?ts$/.test(name); } /** From 31c8d84ecbf0a7d82431fe4b7baad32d45758350 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Sat, 5 Aug 2023 23:15:48 +0800 Subject: [PATCH 3/4] test(typescript): update declaration tests output file order --- packages/typescript/test/declarations.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/typescript/test/declarations.ts b/packages/typescript/test/declarations.ts index 07847cc98..d59fa4b5d 100644 --- a/packages/typescript/test/declarations.ts +++ b/packages/typescript/test/declarations.ts @@ -48,11 +48,11 @@ test.serial('supports creating declaration files in subfolder', async (t) => { onwarn }); const output = await getCode(bundle, { format: 'es', dir: 'fixtures/basic/dist' }, true); - const declaration = output[1].source as string; + const declaration = output[2].source as string; t.deepEqual( output.map((out) => out.fileName), - ['main.js', 'types/main.d.ts', 'types/main.d.ts.map'] + ['main.js', 'types/main.d.ts.map', 'types/main.d.ts'] ); t.true(declaration.includes('declare const answer = 42;'), declaration); @@ -100,16 +100,16 @@ test.serial('supports creating declaration files for interface only source file' { format: 'es', dir: 'fixtures/export-interface-only/dist' }, true ); - const declaration = output[1].source as string; + const declaration = output[2].source as string; t.deepEqual( output.map((out) => out.fileName), [ 'main.js', - 'types/interface.d.ts', 'types/interface.d.ts.map', - 'types/main.d.ts', - 'types/main.d.ts.map' + 'types/interface.d.ts', + 'types/main.d.ts.map', + 'types/main.d.ts' ] ); @@ -140,10 +140,10 @@ test.serial( t.deepEqual( output.map((out) => out.fileName), - ['main.js', 'types/main.d.ts', 'types/custom-types.d.ts'] + ['main.js', 'types/custom-types.d.ts', 'types/main.d.ts'] ); - t.true(declaration.includes('export type MyNumber = number;'), declaration); + t.true(declaration.includes('export declare type MyNumber = number;'), declaration); } ); From 6eff0dd89cc44ef3ed29af212fc17c02d3b2993a Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Sat, 5 Aug 2023 23:59:34 +0800 Subject: [PATCH 4/4] test(typescript): verify that only necessary declarations are emitted --- packages/typescript/test/declarations.ts | 3 ++- .../test/fixtures/implicitly-included-type-only-file/main.ts | 2 +- .../{custom-types.ts => should-be-emitted-types.ts} | 0 .../should-not-be-emitted-types.ts | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) rename packages/typescript/test/fixtures/implicitly-included-type-only-file/{custom-types.ts => should-be-emitted-types.ts} (100%) create mode 100644 packages/typescript/test/fixtures/implicitly-included-type-only-file/should-not-be-emitted-types.ts diff --git a/packages/typescript/test/declarations.ts b/packages/typescript/test/declarations.ts index d59fa4b5d..6b24d00c1 100644 --- a/packages/typescript/test/declarations.ts +++ b/packages/typescript/test/declarations.ts @@ -140,7 +140,8 @@ test.serial( t.deepEqual( output.map((out) => out.fileName), - ['main.js', 'types/custom-types.d.ts', 'types/main.d.ts'] + // 'types/should-not-be-emitted-types.d.ts' should not be emitted because 'main.ts' does not import/export from it. + ['main.js', 'types/should-be-emitted-types.d.ts', 'types/main.d.ts'] ); t.true(declaration.includes('export declare type MyNumber = number;'), declaration); diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts b/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts index cd5bc51f6..fc12ba62f 100644 --- a/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts +++ b/packages/typescript/test/fixtures/implicitly-included-type-only-file/main.ts @@ -1 +1 @@ -export { MyNumber } from './custom-types'; +export { MyNumber } from './should-be-emitted-types'; diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts b/packages/typescript/test/fixtures/implicitly-included-type-only-file/should-be-emitted-types.ts similarity index 100% rename from packages/typescript/test/fixtures/implicitly-included-type-only-file/custom-types.ts rename to packages/typescript/test/fixtures/implicitly-included-type-only-file/should-be-emitted-types.ts diff --git a/packages/typescript/test/fixtures/implicitly-included-type-only-file/should-not-be-emitted-types.ts b/packages/typescript/test/fixtures/implicitly-included-type-only-file/should-not-be-emitted-types.ts new file mode 100644 index 000000000..9a173ddd5 --- /dev/null +++ b/packages/typescript/test/fixtures/implicitly-included-type-only-file/should-not-be-emitted-types.ts @@ -0,0 +1,2 @@ +// This file is intentionally not imported from. +export type MyString = string;