diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 9797d9f96cc66..c596372563ff4 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1176,6 +1176,15 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [ defaultValueDescription: false, transpileOptionValue: undefined, }, + { + name: "rewriteRelativeImportExtensions", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Modules, + description: Diagnostics.Rewrite_ts_tsx_mts_and_cts_file_extensions_in_relative_import_paths_to_their_JavaScript_equivalent_in_output_files, + defaultValueDescription: false, + }, { name: "resolvePackageJsonExports", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 6f5d7ada6c028..24344f564b0cd 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -5934,6 +5934,10 @@ "category": "Message", "code": 6420 }, + "Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files.": { + "category": "Message", + "code": 6421 + }, "The expected type comes from property '{0}' which is declared here on type '{1}'": { "category": "Message", diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 850a8a7f381cb..35ea6c970f7e6 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -36,6 +36,7 @@ import { forEach, forEachAncestorDirectory, formatMessage, + getAllowImportingTsExtensions, getAllowJSCompilerOption, getAnyExtensionFromPath, getBaseFileName, @@ -3303,7 +3304,7 @@ function resolveFromTypeRoot(moduleName: string, state: ModuleResolutionState) { // so this function doesn't check them to avoid propagating errors. /** @internal */ export function shouldAllowImportingTsExtension(compilerOptions: CompilerOptions, fromFileName?: string) { - return !!compilerOptions.allowImportingTsExtensions || fromFileName && isDeclarationFileName(fromFileName); + return getAllowImportingTsExtensions(compilerOptions) || fromFileName && isDeclarationFileName(fromFileName); } /** diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 198dfa0975457..2808587f7ca49 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -4523,7 +4523,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg } } - if (options.allowImportingTsExtensions && !(options.noEmit || options.emitDeclarationOnly)) { + if (options.allowImportingTsExtensions && !(options.noEmit || options.emitDeclarationOnly || options.rewriteRelativeImportExtensions)) { createOptionValueDiagnostic("allowImportingTsExtensions", Diagnostics.Option_allowImportingTsExtensions_can_only_be_used_when_either_noEmit_or_emitDeclarationOnly_is_set); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 036b8d60f9f27..d21ec42f16911 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -12,6 +12,7 @@ import { Bundle, CallExpression, CaseBlock, + changeExtension, childIsDecorated, ClassDeclaration, ClassElement, @@ -55,6 +56,7 @@ import { getInitializedVariables, getIsolatedModules, getOriginalNode, + getOutputExtension, getParseTreeNode, getProperties, getStrictOptionValue, @@ -81,6 +83,7 @@ import { isClassElement, isClassLike, isComputedPropertyName, + isDeclarationFileName, isDecorator, isElementAccessExpression, isEntityName, @@ -122,6 +125,7 @@ import { isSimpleInlineableExpression, isSourceFile, isStatement, + isStringLiteral, isTemplateLiteral, isTryStatement, JsxOpeningElement, @@ -155,6 +159,7 @@ import { parameterIsThisKeyword, ParameterPropertyDeclaration, ParenthesizedExpression, + pathIsRelative, PropertyAccessExpression, PropertyDeclaration, PropertyName, @@ -2269,12 +2274,22 @@ export function transformTypeScript(context: TransformationContext) { node, /*modifiers*/ undefined, importClause, - node.moduleSpecifier, + rewriteModuleSpecifier(node.moduleSpecifier), node.attributes, ) : undefined; } + function rewriteModuleSpecifier(node: Expression): Expression; + function rewriteModuleSpecifier(node: Expression | undefined): Expression | undefined; + function rewriteModuleSpecifier(node: Expression | undefined): Expression | undefined { + if (!node || !compilerOptions.rewriteRelativeImportExtensions || !isStringLiteral(node) || !pathIsRelative(node.text) || isDeclarationFileName(node.text)) { + return node; + } + const updatedText = changeExtension(node.text, getOutputExtension(node.text, compilerOptions)); + return updatedText !== node.text ? setOriginalNode(setTextRange(factory.createStringLiteral(updatedText, node.singleQuote), node), node) : node; + } + /** * Visits an import clause, eliding it if its `name` and `namedBindings` may both be elided. * @@ -2342,7 +2357,14 @@ export function transformTypeScript(context: TransformationContext) { // never elide `export from ` declarations - // they should be kept for sideffects/untyped exports, even when the // type checker doesn't know about any exports - return node; + return factory.updateExportDeclaration( + node, + node.modifiers, + node.isTypeOnly, + node.exportClause, + rewriteModuleSpecifier(node.moduleSpecifier), + node.attributes, + ); } // Elide the export declaration if all of its named exports are elided. @@ -2359,7 +2381,7 @@ export function transformTypeScript(context: TransformationContext) { /*modifiers*/ undefined, node.isTypeOnly, exportClause, - node.moduleSpecifier, + rewriteModuleSpecifier(node.moduleSpecifier), node.attributes, ) : undefined; @@ -2421,8 +2443,24 @@ export function transformTypeScript(context: TransformationContext) { } if (isExternalModuleImportEqualsDeclaration(node)) { - const isReferenced = shouldEmitAliasDeclaration(node); - return isReferenced ? visitEachChild(node, visitor, context) : undefined; + if (!shouldEmitAliasDeclaration(node)) { + return undefined; + } + const updatedModuleSpecifier = rewriteModuleSpecifier(node.moduleReference.expression); + if (updatedModuleSpecifier !== node.moduleReference.expression) { + return visitEachChild( + factory.updateImportEqualsDeclaration( + node, + node.modifiers, + node.isTypeOnly, + node.name, + factory.updateExternalModuleReference(node.moduleReference, updatedModuleSpecifier), + ), + visitor, + context, + ); + } + return visitEachChild(node, visitor, context); } if (!shouldEmitImportEqualsDeclaration(node)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ec5aa60564eac..8d11378287a57 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7443,6 +7443,7 @@ export interface CompilerOptions { removeComments?: boolean; resolvePackageJsonExports?: boolean; resolvePackageJsonImports?: boolean; + rewriteRelativeImportExtensions?: boolean; rootDir?: string; rootDirs?: string[]; skipLibCheck?: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fb4da5887538d..cc9308f0cf6e6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -8821,6 +8821,12 @@ function createComputedCompilerOptions { + return !!(compilerOptions.allowImportingTsExtensions || compilerOptions.rewriteRelativeImportExtensions); + }, + }, target: { dependencies: ["module"], computeValue: compilerOptions => { @@ -9046,6 +9052,8 @@ export const computedOptions = createComputedCompilerOptions({ }, }); +/** @internal */ +export const getAllowImportingTsExtensions = computedOptions.allowImportingTsExtensions.computeValue; /** @internal */ export const getEmitScriptTarget = computedOptions.target.computeValue; /** @internal */