Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual Studio 2022 Build Errors for TypeScript Modules with File Extensions

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.

like image 666
Gup3rSuR4c Avatar asked Jun 23 '26 07:06

Gup3rSuR4c


2 Answers

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.

like image 112
T.J. Crowder Avatar answered Jun 24 '26 20:06

T.J. Crowder


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.

like image 44
Gup3rSuR4c Avatar answered Jun 24 '26 21:06

Gup3rSuR4c



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!