Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Importing a .js typescript resource from a typescript file fails #3040

Closed
6 tasks done
Avocher opened this issue Apr 18, 2021 · 21 comments · Fixed by #5510
Closed
6 tasks done

Importing a .js typescript resource from a typescript file fails #3040

Avocher opened this issue Apr 18, 2021 · 21 comments · Fixed by #5510
Labels
enhancement New feature or request has workaround p2-nice-to-have Not breaking anything but nice to have (priority)

Comments

@Avocher
Copy link

Avocher commented Apr 18, 2021

Describe the bug

Importing a .js TypeScript resource from a TypeScript file fails with: Internal server error: Failed to resolve import

TypeScript wants users to write the same module specifiers as are used in the produced output if they want to use ES Modules. An extensive discussion about the topic can be found here: microsoft/TypeScript#16577. As a result esbuild supports the same feature. Rollup also supports this if you add extensions: ['.js', '.ts'] to the resolve plugin config. Thus I would expect the same code to work in Vite.

I personally would like to be able to run my .ts files through either vite or tsc as needed in a "type": "module" repository.

Reproduction

hello.ts

export const sayHello = () => console.log('Hello');

index.ts

import { sayHello } from "./hello.js";

System Info

Output of npx envinfo --system --npmPackages vite --binaries --browsers:

System:
    OS: Linux 5.11 Arch Linux
    CPU: (4) x64 Intel(R) Core(TM) i5-4690K CPU @ 3.50GHz
    Memory: 477.69 MB / 15.57 GB
    Container: Yes
    Shell: 5.8 - /usr/bin/zsh
  Binaries:
    Node: 14.16.0 - /usr/bin/node
    Yarn: 1.22.10 - /usr/bin/yarn
    npm: 7.10.0 - /usr/bin/npm
  Browsers:
    Brave Browser: 90.1.23.71
    Firefox: 87.0

Used package manager: yarn

Logs

[vite] Internal server error: Failed to resolve import "./hello.js" from "./index.ts". Does the file exist?
  Plugin: vite:import-analysis

Before submitting the issue, please make sure you do the following

@haoqunjiang haoqunjiang added cannot reproduce The bug cannot be reproduced and removed pending triage labels May 18, 2021
@haoqunjiang
Copy link
Member

I'm sorry, but I can't reproduce this issue on my local machine.
Would you please provide a full project instead of code samples for us to investigate?

@Avocher
Copy link
Author

Avocher commented May 21, 2021

Ofcourse, here you go: https://github.com/Avocher/vite-bug

@Shinigami92 Shinigami92 added pending triage and removed cannot reproduce The bug cannot be reproduced labels May 21, 2021
@danielemesh
Copy link

@sodatea here's a code sandbox with the issue: https://codesandbox.io/s/fancy-fire-epcgr?file=/src/main.tsx

@danielemesh
Copy link

seems like it can be resolved via the vite.config file as follows:

import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: [
      {
        find:/^(.*)\.js$/,
        replacement: '$1',
      }
    ]
  },
  plugins: [reactRefresh()],
})

@BrianHung
Copy link

seems like it can be resolved via the vite.config file as follows:

Works in development fine with vite, but with vite build that snippet results in

[load-fallback] Could not load commonjsHelpers (imported by node_modules/react/index.js): The argument 'path' must be a string or Uint8Array without null bytes. Received '\x00commonjsHelpers'
error during build:
TypeError [ERR_INVALID_ARG_VALUE]: The argument 'path' must be a string or Uint8Array without null bytes. Received '\x00commonjsHelpers'

@Avocher
Copy link
Author

Avocher commented Sep 16, 2021

I added the rollup resolve plugin to the config as a temporary fix:

import { defineConfig } from 'vite'
import resolve from '@rollup/plugin-node-resolve'

export default defineConfig({
    plugins: [resolve({
        extensions: ['.js', '.ts']
    })]
})

It works for both development and building

@haoqunjiang
Copy link
Member

haoqunjiang commented Sep 23, 2021

I've just read several threads in the TypeScript repository to have a better understanding of this issue.

The best one that summarized the underlying concerns is this one: microsoft/TypeScript#16577 (comment)


In my opinion, I don't think vite should support this particular pattern:
that is, import { sayHello } from "./hello.js"; while what you actually have on the disk is hello.ts.

It is because unlike tsc, vite doesn't output compilation products in the form of .js files. Browsers would just request for hello.ts from the vite server, and get the compiled JavaScript content in the response, with the text/javascript MIME type.

So conceptually, hello.js just doesn't exist. It would be confusing to import from that path.


I believe that extensionless imports in TypeScript should be preferred.

As for this use case:

I personally would like to be able to run my .ts files through either vite or tsc as needed in a "type": "module" repository.

Try the --experimental-specifier-resolution= node option in node: https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm

@danielemesh
Copy link

danielemesh commented Sep 23, 2021

@sodatea I think it's a TS thing, if you want to work ESM you must specify the file extension, and TS requires you to use a '.js' extension in TS files.
correct me if I'm wrong cause I'm not totally sure of that.

@haoqunjiang
Copy link
Member

haoqunjiang commented Sep 23, 2021

if you want to work ESM you must specify the file extension

@danielemesh No, you don't.

  • You don't have to specify the extension in vite dev server;
  • You don't have to specify the extension when serving the application in production, because the outputs of vite build would have the full path resolved for you, with the correct extensions;
  • You don't have to specify the extension in ts-node;
  • You don't have to specify the extension in node, because there's the --experimental-specifier-resolution=node option, as said above.

@Avocher
Copy link
Author

Avocher commented Sep 23, 2021

It is because unlike tsc, vite doesn't output compilation products in the form of .js files. Browsers would just request for hello.ts from the vite server, and get the compiled JavaScript content in the response, with the text/javascript MIME type.

So conceptually, hello.js just doesn't exist. It would be confusing to import from that path.

That's a very solid point, You'd have to know the innerworkings of Vite to understand that argument though (which I do now).

--experimental-specifier-resolution=node looks interesting, but it is experimental and not something we'd like to run in production on our servers. I'm fine with the little workaround I found, and it's frankly easier to have that in the config than convincing the other team members that they should use the experimental api.

I'm a bit uncertain what the --experimental-specifier-resolution=node is suppose to mean for the eco system, examples online and the node.js documentation suggest you should use file extensions with type: module, are they backtracking on that?

@haoqunjiang
Copy link
Member

I think Node folks are undecided yet nodejs/modules#323

@haoqunjiang
Copy link
Member

On the usage of the experimental feature:
I don't know the exact reason that you need your .ts files to be able to run by either vite or tsc.

Because in production environments, you would have run vite build first, so it becomes a non-issue.

Then, if you run tsc for development usage, then using an experimental feature seems somehow acceptable.

@Avocher
Copy link
Author

Avocher commented Sep 24, 2021

I don't know the exact reason that you need your .ts files to be able to run by either vite or tsc.

My bad, I should probably have started by saying: we have a quite large library of code that we share between frontend and backend, that's the reason why we need both vite and tsc.

@giltayar
Copy link

My 2 cents, as someone who works with both Vite, TSC, and Node.js, on the same frontend code. We test extensively, and part of the test suite is running the frontend code using JSDOM in Node.js. For that reason, we use tsc to run our Node.js tests for the frontend, and of course vite to run it on the browser.

Given that we are using Node.js ESM, and type: module in our packages, we MUST specify an extension. The --experimental-specifier-resolution=node is not something we want to rely on for all our code (as a former member of the Node.js ESM working group, this option was added in the early days of the working group as a compromise between those that wanted an "extensionless" ESM and those that wanted to force extension use. It may be deprecated in the future, and even if not, the Node.js ecosystem has decided and is going with extensions).

Given that other bundlers are adding functionality to enable this (admittedly) weird behavior in TS (whereby you specify a .js file even if you want to import a .ts file), I believe it is in Vite's interest to add this in some way, and would gladly help in implementing it if needed.

@haoqunjiang
Copy link
Member

@Avocher As a workaround, you can run tsc --watch on the shared codebase and vite dev for the rest of the frontend code.


@giltayar Thanks for the information. I'll bring this issue to the next team meeting and discuss it with other teammates.

@benjamind
Copy link

Would just like to add one more request for this.

Tried to use Vite this weekend for the first time and encountered this problem. The need to write my TS in a form that is compatible with Vite makes it a no-go choice for me. I do not like to lock-in my code to specific bundler patterns.

I should be able to remove Vite from my codebase without having to change every import across it. Given that other bundlers support this pattern in resolution Vite really needs to follow suit and I won't be adopting it until it can, which is a shame because it otherwise seemed great for my small side projects.

@haoqunjiang
Copy link
Member

FYI, in the latest team meeting, we've decided to add support for this feature:

  • For import requests initiated from a TS source file, if the module specifier ends with .js/jsx/.mjs/.cjs but the file doesn't actually exist, Vite will try to import from its corresponding TypeScript file too.

@haoqunjiang haoqunjiang added enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority) and removed discussion labels Oct 12, 2021
@benjamind
Copy link

Fantastic thank you and the team. Looking forward to trying it out!

@danielemesh
Copy link

great news!
thank you and the team for this  😇

@oscarmarina
Copy link

Thank you @sodatea, @Niputi and Vitejs 👍

@Avocher
Copy link
Author

Avocher commented Oct 13, 2021

Great, thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request has workaround p2-nice-to-have Not breaking anything but nice to have (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants