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

fix: don't store temporary vite config file in node_modules if deno #18823

Merged

Conversation

kazushisan
Copy link
Contributor

@kazushisan kazushisan commented Nov 28, 2024

Description

fixes #18822

This fixes an issue where Vite v6 does not work on Deno when import statements with the specifier npm: is used in the config file.

Vite will create a bundled version of vite.config.ts under node_modules/.vite-temp if node_modules/ exits. Import statements like the following will be kept in the generated output.

import { defineConfig } from 'npm:vite@6'

When this generated file is import() ed by Vite running on Deno, Deno will throw an error TypeError: [ERR_UNSUPPORTED_ESM_URL_SCHEME]. This is because Deno assumes that modules under node_modules/ is a valid file for executing with Node.js and considers the npm: prefix invalid.

This patch will check if the the environment is Deno and will prevent from storing the temp file under node_modules/ if its Deno.

@@ -1859,18 +1859,20 @@ async function loadConfigFromBundledFile(
// write it to disk, load it with native Node ESM, then delete the file.
if (isESM) {
const nodeModulesDir = findNearestNodeModules(path.dirname(fileName))
if (nodeModulesDir) {
const isDeno = !!(globalThis as any).Deno
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that ideally this condition should be checking if the env is Node.js or not but there doesn't seem to be a way to reliably detect that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is special behavior on the Deno side and correct to branch on Deno.

Copy link
Contributor Author

@kazushisan kazushisan Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, assuming that arbitrary files (not npm packages) placed in node_modules/ will work regardless of the runtime is quite a leap compared to storing files meant to be consumed in the browser in node_modules/.vite.

I believe that placing application files under node_modules/ and executing them with a runtime is not something that a runtime (or package manager) would anticipate. Therefore, I would argue that the current design relies on the specific behavior of Node.js and should not be assumed to work universally across all runtimes.

(I understand it's not my call and it's definitely out of scope but just wanted to share my thoughts 💭)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is just a matter of perspective. The reason Vite works with deno is because deno is fairly compatible enough with Node, not because Vite tries to support all existing runtimes (not saying deno isn't supported, it's just how it currently works). From that point of view, deno lacks Node.js compatibility here, and Vite applies a patch for deno (that said, I personally think that behaving differently in node_modules is a valid choice on deno's side).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, it is a matter of perspective but I was just curious if this fairly uncommon approach (even from node's standards) to store modules that are executed by the host runtime in node_modules/ is worth the benefits compared to the potential breaks that it may introduce for other runtimes.

Anyway, thanks for elaborating 👍

Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that we cannot use node_modules to write the temporary file. I don't come up with other ways. I think we should go with this one.

packages/vite/src/node/config.ts Outdated Show resolved Hide resolved
packages/vite/src/node/config.ts Outdated Show resolved Hide resolved
@@ -1859,18 +1859,20 @@ async function loadConfigFromBundledFile(
// write it to disk, load it with native Node ESM, then delete the file.
if (isESM) {
const nodeModulesDir = findNearestNodeModules(path.dirname(fileName))
if (nodeModulesDir) {
const isDeno = !!(globalThis as any).Deno
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is special behavior on the Deno side and correct to branch on Deno.

@@ -1859,18 +1859,20 @@ async function loadConfigFromBundledFile(
// write it to disk, load it with native Node ESM, then delete the file.
if (isESM) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const hasNpmPrefixExternalImport = Object.values(result.metafile.outputs)
    .flatMap((output) => output.imports)
    .filter((imp) => imp.external && imp.path.startsWith('npm:'))

Does passing this value from bundleConfigFile and only avoid writing to node_modules in that case work? If I understand correctly, the problem only happens if the config file has an import with npm: prefix.

If you have a code like below, it won't work. But this is already not supported, so it should be fine.

const dynImport = (str: string) => import(str)

dynImport('npm:foo')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the problem only happens if the config file has an import with npm: prefix.

Yes, you're right! Here's a break down of what's going to happen for clarity.

  1. Vite removes the npm: prefix from import statements
  2. bundled file is executed with Deno
  3. Deno's module resolution expects an npm: prefix when we want to do something like import { defineConfig } from 'vite';
  4. However since this bundled file is placed in node_modules/, Deno will consider the file as an Node.js compatible module and resolve import statements accordingly
  5. import works!

So your solution should work, but I think it make thing too unpredictable and fragile because it's like trying to implement a transformation layer that voids Deno's behavior difference for modules inside/outside of node_modules/

Since the bundled file is going to be executed by Deno not Node, I'd say that keeping the import statements as-is is a better solution.

@kazushisan
Copy link
Contributor Author

@sapphi-red thanks for the prompt review! I've addressed your comments!

Copy link
Member

@sapphi-red sapphi-red left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@patak-dev patak-dev merged commit a20267b into vitejs:main Nov 29, 2024
15 checks passed
@kazushisan kazushisan deleted the fix/dont-store-in-node-modules-if-deno branch November 29, 2024 07:55
renovate bot added a commit to andrei-picus-tink/auto-renovate that referenced this pull request Dec 3, 2024
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| npm        | vite    | 6.0.1 | 6.0.2 |


## [v6.0.2](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small602-2024-12-02-small)

-   chore: run typecheck in unit tests ([#18858](vitejs/vite#18858)) ([49f20bb](vitejs/vite@49f20bb)), closes [#18858](vitejs/vite#18858)
-   chore: update broken links in changelog ([#18802](vitejs/vite#18802)) ([cb754f8](vitejs/vite@cb754f8)), closes [#18802](vitejs/vite#18802)
-   chore: update broken links in changelog ([#18804](vitejs/vite#18804)) ([47ec49f](vitejs/vite@47ec49f)), closes [#18804](vitejs/vite#18804)
-   fix: don't store temporary vite config file in `node_modules` if deno ([#18823](vitejs/vite#18823)) ([a20267b](vitejs/vite@a20267b)), closes [#18823](vitejs/vite#18823)
-   fix(css): referencing aliased svg asset with lightningcss enabled errored ([#18819](vitejs/vite#18819)) ([ae68958](vitejs/vite@ae68958)), closes [#18819](vitejs/vite#18819)
-   fix(manifest): use `style.css` as a key for the style file for `cssCodesplit: false` ([#18820](vitejs/vite#18820)) ([ec51115](vitejs/vite@ec51115)), closes [#18820](vitejs/vite#18820)
-   fix(optimizer): resolve all promises when cancelled ([#18826](vitejs/vite#18826)) ([d6e6194](vitejs/vite@d6e6194)), closes [#18826](vitejs/vite#18826)
-   fix(resolve): don't set builtinModules to `external` by default ([#18821](vitejs/vite#18821)) ([2250ffa](vitejs/vite@2250ffa)), closes [#18821](vitejs/vite#18821)
-   fix(ssr): set `ssr.target: 'webworker'` defaults as fallback ([#18827](vitejs/vite#18827)) ([b39e696](vitejs/vite@b39e696)), closes [#18827](vitejs/vite#18827)
-   feat(css): format lightningcss error ([#18818](vitejs/vite#18818)) ([dac7992](vitejs/vite@dac7992)), closes [#18818](vitejs/vite#18818)
-   refactor: make properties of ResolvedServerOptions and ResolvedPreviewOptions required ([#18796](vitejs/vite#18796)) ([51a5569](vitejs/vite@51a5569)), closes [#18796](vitejs/vite#18796)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

vite 6 breaks deno support when npm: specifer is used in config file
3 participants