Visual Studio 2022 Build Errors for TypeScript Modules with File Extensions
Asked Answered
C

3

8

I'm rebuilding my Gulp implementation for compiling, minifying, and compressing TypeScript files into a single bundle using the latest version of Rollup. The new implementation is complete and working and I can trigger it from the Task Runner or using Gulp's file system watcher, and it does exactly what I need it to do.

The problem I'm running into is that in order to get Rollup to see module imports, I had to append the ".ts" extension to the import:

import { something } from "./Module.ts";

Which caused Visual Studio to give this error:

TS2691: (TS) An import path cannot end with a '.ts' extension. Consider importing './Module.js' instead.

The TypeScript compiler seems to ignore the error because when I run the Gulp task, it compiles the TypeScript files as expected. Reading through the GitHub discussions about the ".ts" extension, it seems the recommended solution with the most recent versions of TypeScript is to add a couple of properties to the tsconfig.json file:

{
  "allowImportingTsExtensions": true,
  "moduleResolution": "bundler",
  "noEmit": true
}

Which caused Visual Studio to give even more errors:

(TS) Unknown compiler option 'allowImportingTsExtensions'.

(TS) Argument for '--moduleResolution' option must be: 'node', 'classic', 'node16', 'nodenext'.

All of this culminates into me not being able to build the project at all. Right now, I'm just in an experimental project I'm going to throw away after I get the new Gulp implementation figured out, but if I apply these changes to my real projects, then I'll never be able to compile them.

What do I need to do to resolve these errors? I tried suppressing TS2691 in the project properties, but it had no effect. I also tried switching from the NuGet TypeScript package, to the npm TypeScript package, and it also had no effect. For reference, I'm using Visual Studio 2022, TypeScript 4.9.5, and Rollup 3.17.3.

Chappie answered 27/2, 2023 at 16:26 Comment(0)
C
5

Update after TypeScript 5 Release

After TypeScript 5 was released, I decided to try a clean solution without my cheats, and it now works as expected. I had to set the following in the tsconfig.json:

{
  "compilerOptions": {
    "allowImportingTsExtensions": true,
    "moduleResolution": "bundler"
  }
}

I also removed gulp-replace and reduced the gulp task to:

gulp.task(taskTsRollup, () => rollup.rollup({
    input: inputTs,
    plugins: [
        rollupTypeScript,
        rollupTerser()
    ]
}).then(
    _ => _.write({
        file: targetJs,
        format: "iife",
        sourcemap: false
    })));

More info in the Announcing TypeScript 5.0 post.


In the end I cheated my way around it. Since Rollup wants the extension and TypeScript doesn't want the extension, I decided to give them both what they needed.

To do that, I first have a gulp task that makes a copy of the input file, so from Default.ts to Default.ts-copy. Then using gulp-replace I used a regular expression to match on the import pattern and rewrite the value to include the extension. Then the copied and modified file is passed to Rollup, it does it's thing, and in the end I delete the copy file.

This way I keep TypeScript happy about no extensions, and Rollup gets fed a modified file with the extensions. All are happy. Here's the final Gulp task:

gulp.task(taskTsRollup, gulp.series(
    () => gulp
        .src(inputTs)
        .pipe(gulpRename(`${inputTs}-copy`))
        .pipe(gulpReplace(/from\s+\".\/([a-zA-Z0-9_-]+)\"\;/g, "from \".\/$1.ts\";"))
        .pipe(gulp.dest(`${rootResources}/Scripts`)),
    () => rollup.rollup({
        input: `${inputTs}-copy`,
        plugins: [
            rollupTypeScript,
            rollupTerser()
        ]
    }).then(
        _ => _.write({
            file: targetJs,
            format: "iife",
            sourcemap: false
        })),
    () => del([
        `${rootResources}/Scripts/${inputTs}-copy`
    ])
));

Maybe in the future the TypeScript language service will follow in the compiler's footsteps and support all the configuration options.

Chappie answered 1/3, 2023 at 17:23 Comment(1)
I still get error TS5096: Option 'allowImportingTsExtensions' can only be used when either 'noEmit' or 'emitDeclarationOnly' is set. (tsc v5.1.6)Gallic
D
7

If you're using a modern bundler, remove allowImportingTsExtensions, set moduleResolution to "node", and leave the file extensions off the imports. Modern bundlers understand the file extension not being present.


If you weren't using a bundler, the answer would be (bizarrely, in my view) that you had to use .js on your import statements, even though your source files are .ts files. That's because TypeScript doesn't rewrite module specifiers (as a policy decision). So if you have index.ts importing from mod.ts, you have to write import { something } from "./mod.js"; so that it's like that in the JavaScript produced by the compiler and the browser can process it correctly.

Dorothi answered 27/2, 2023 at 16:31 Comment(4)
Rollup doesn't. I started off without the extension, and Rollup threw errors in the Task Runner console: RollupError: Could not resolve "./Module" from "Resources/Scripts/Default.ts". The errors resolved when I added the extension. I've also tried using .js to trick TypeScript, but Rollup threw errors again: RollupError: Could not resolve "./Module.js" from "Resources/Scripts/Default.ts". I'm using Rollup 3.17.3.Chappie
@Chappie - Sounds like you need to set a configuration option (or an extension) in Rollup. I use Vite, which uses Rollup under the covers, and it handles the lack of extensions perfectly. Unfortunately, I can't point you to the option, as I've never configured Rollup directly.Dorothi
This answer is great for building/bundling. But how can I run the project with nodemon/ts-node with ESM?Serrulate
@Serrulate - As I said in the second half of the answer, TypeScript doesn't allow you to use .ts extensions. I don't use ts-node so I don't know if it has options to work around that limitation. FWIW, Deno (the "next-generation JavaScript runtime" created by the same guy who created Node.js) handles TypeScript natively and is perfectly happy with .ts extensions, and it has very good Node.js emulation and npm support (good, not perfect).Dorothi
C
5

Update after TypeScript 5 Release

After TypeScript 5 was released, I decided to try a clean solution without my cheats, and it now works as expected. I had to set the following in the tsconfig.json:

{
  "compilerOptions": {
    "allowImportingTsExtensions": true,
    "moduleResolution": "bundler"
  }
}

I also removed gulp-replace and reduced the gulp task to:

gulp.task(taskTsRollup, () => rollup.rollup({
    input: inputTs,
    plugins: [
        rollupTypeScript,
        rollupTerser()
    ]
}).then(
    _ => _.write({
        file: targetJs,
        format: "iife",
        sourcemap: false
    })));

More info in the Announcing TypeScript 5.0 post.


In the end I cheated my way around it. Since Rollup wants the extension and TypeScript doesn't want the extension, I decided to give them both what they needed.

To do that, I first have a gulp task that makes a copy of the input file, so from Default.ts to Default.ts-copy. Then using gulp-replace I used a regular expression to match on the import pattern and rewrite the value to include the extension. Then the copied and modified file is passed to Rollup, it does it's thing, and in the end I delete the copy file.

This way I keep TypeScript happy about no extensions, and Rollup gets fed a modified file with the extensions. All are happy. Here's the final Gulp task:

gulp.task(taskTsRollup, gulp.series(
    () => gulp
        .src(inputTs)
        .pipe(gulpRename(`${inputTs}-copy`))
        .pipe(gulpReplace(/from\s+\".\/([a-zA-Z0-9_-]+)\"\;/g, "from \".\/$1.ts\";"))
        .pipe(gulp.dest(`${rootResources}/Scripts`)),
    () => rollup.rollup({
        input: `${inputTs}-copy`,
        plugins: [
            rollupTypeScript,
            rollupTerser()
        ]
    }).then(
        _ => _.write({
            file: targetJs,
            format: "iife",
            sourcemap: false
        })),
    () => del([
        `${rootResources}/Scripts/${inputTs}-copy`
    ])
));

Maybe in the future the TypeScript language service will follow in the compiler's footsteps and support all the configuration options.

Chappie answered 1/3, 2023 at 17:23 Comment(1)
I still get error TS5096: Option 'allowImportingTsExtensions' can only be used when either 'noEmit' or 'emitDeclarationOnly' is set. (tsc v5.1.6)Gallic
C
-3

I don't think that this is a problem, it's more annoying than a problem because you see an error in your file and you have to just leave it there. In my opinion I think that you should just leave it because it's not affect the performance or the code itself. It's up to you!!. Good luck!.

Cassidy answered 18/6, 2023 at 14:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.