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

Support for moduleInfo.meta in resolveId hook still not equivalent in vite dev and vite build #6766

Open
7 tasks done
joaotavora opened this issue Feb 5, 2022 · 10 comments · May be fixed by #7477
Open
7 tasks done
Labels
p3-minor-bug An edge case that only affects very specific usage (priority) rollup plugin compat

Comments

@joaotavora
Copy link

joaotavora commented Feb 5, 2022

Describe the bug

In #5465 support for moduleInfo.meta is added to the dev server. This is a very welcome change that I look forward to using. Unforunately, behavioral parity between vite build and vite dev is not quite there yet. In the attached reproduction recipe the former succeeds while the latter fails. By "failing" I mean the transform hook is never called and doesn't inject an important transformation into foo.js. Here are relevant parts of the files.

//main.js
import fn from './foo.js?xoption=21'
document.querySelector('#app').innerHTML = `<h1>Hello Vite: ${fn()}</h1>`
//foo.js
export default function () {return 21 + window.THE_X;}
//vite.config.js
import path from 'path';
export default {
    plugins: [
        {
            enforce: 'pre',
            async resolveId(id) {
                let [file, query] = id.split('?');
                const probe = query && query.match(/xoption=([^&]+)/)
                const x = probe && Number(probe[1])
                if (x) return {id: path.resolve(file), meta: {x}}
            },
            async transform(blob, id) {
                const x = this.getModuleInfo(id)?.meta?.x
                if (x) return `window.THE_X = ${x}\n` + blob;
            }
        }
    ]
}

To be clear, this particular solution "never" worked with Vite before. However, before #5465, a workaround/hack (not shown) could be devised, because getModuleInfo called in he resolveId hook would return a persistent empty object that could be used to fill in the shortcomings that #5465 hopes to address. Ironically, that workaround is now not possible anymore.

If this is accepted as a bug, I'd be happy to provide a PR to fix this.

Reproduction

https://github.com/joaotavora/bug-vite-return-meta-from-resolve-id

System Info

System:
    OS: Linux 5.15 Arch Linux
    CPU: (4) x64 Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
    Memory: 3.21 GB / 15.39 GB
    Container: Yes
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.9.0 - ~/.nvm/versions/node/v16.9.0/bin/node
    Yarn: 1.22.11 - ~/.nvm/versions/node/v16.9.0/bin/yarn
    npm: 7.21.1 - ~/.nvm/versions/node/v16.9.0/bin/npm
  Browsers:
    Chromium: 96.0.4664.45
  npmPackages:
    vite: ^2.7.2 => 2.8.0-beta.5

Used Package Manager

npm

Logs

No response

Validations

@aleclarson
Copy link
Member

It's your own bug :)

The id property you return from resolveId is not a real file. You have to remove the leading slash before passing file to path.resolve or else Vite won't be able to load the file from disk.

@joaotavora
Copy link
Author

joaotavora commented Feb 6, 2022

The id property you return from resolveId is not a real file.

You're misreading the code. Did you try the minimal reproducible repo I constructed and attached?

Here is a change dversion of vite.config.js that prints out exactly the file being returned from the resolveId hook. It always exists. It's easy to check, just npx vite dev -d with the repro repo I sent you.

import path from 'path';
export default {
    plugins: [
        {
            enforce: 'pre',
            async resolveId(id) {
                let [file, query] = id.split('?');
                const probe = query && query.match(/xoption=([^&]+)/)
                const x = probe && Number(probe[1])
                if (x) {
                    const realone = path.resolve(file);
                    console.log('IS THIS FILE REAL ON THE FS?', realone);
                    return {id: realone, meta: {x}}
                }
            },
            async transform(blob, id) {
                const x = this.getModuleInfo(id)?.meta?.x
                if (x) return `window.THE_X = ${x}\n` + blob;
            }
        }
    ]
}  

logs

vite:load 10.95ms [fs] /main.js +9ms
IS THIS FILE REAL ON THE FS? /home/capitaomorte/Source/Javascript/bug-vite-return-meta-from-resolve-id/foo.js
vite:resolve 0.22ms ./foo.js?xoption=21 -> /home/capitaomorte/Source/Javascript/bug-vite-return-meta-from-resolve-id/foo.js +6ms
vite:resolve 0.15ms /foo.js -> /home/capitaomorte/Source/Javascript/bug-vite-return-meta-from-resolve-id/foo.js +0ms
vite:import-analysis 2.53ms [1 imports rewritten] main.js +6ms

Also, why would this work with npx vite build?

@joaotavora
Copy link
Author

joaotavora commented Feb 6, 2022

Hi @aleclarson. I hope you can appreciate this report again. I've dug down to the root of the problem. Here is a possible fix (but not the cleanest one, I'll present that cleaner one later as a PR).

The comments should hint as to what goes wrong and how this fixes it.

diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts
index 953304e0..4a5c5281 100644
--- a/packages/vite/src/node/plugins/importAnalysis.ts
+++ b/packages/vite/src/node/plugins/importAnalysis.ts
@@ -178,6 +178,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
         url: string,
         pos: number
       ): Promise<[string, string]> => {
+        const uncleanUrl = url;
         if (base !== '/' && url.startsWith(base)) {
           url = url.replace(base, '/')
         }
@@ -259,7 +260,9 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
           // its last updated timestamp to force the browser to fetch the most
           // up-to-date version of this module.
           try {
-            const depModule = await moduleGraph.ensureEntryFromUrl(url, ssr)
+            // make sure we feed the original "unclean" URL to  ensureEntryFromUrl
+            // so as to call the very same user plugins as in build mode.
+            const depModule = await moduleGraph.ensureEntryFromUrl(uncleanUrl, ssr)
             if (depModule.lastHMRTimestamp > 0) {
               url = injectQuery(url, `t=${depModule.lastHMRTimestamp}`)
             }
diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts
index f09e741f..75d0bcf7 100644
--- a/packages/vite/src/node/server/moduleGraph.ts
+++ b/packages/vite/src/node/server/moduleGraph.ts
@@ -160,7 +160,10 @@ export class ModuleGraph {
     let mod = this.urlToModuleMap.get(url)
     if (!mod) {
       mod = new ModuleNode(url)
-      if (meta) mod.meta = meta
+      // A module with the same 'resolveId' might already exist under
+      // a different URL. Make sure we inherit its 'meta'.
+      const existing = this.idToModuleMap.get(resolvedId)
+      mod.meta = {...existing?.meta, ...meta}
       this.urlToModuleMap.set(url, mod)
       mod.id = resolvedId
       this.idToModuleMap.set(resolvedId, mod)

I'm not a fan of this fix, but it does fix it and hopefully illustrate the problem. It will fail if the moduleGraph.ensureEntryFromUrl is ever moved outside normalizeUrl.

joaotavora added a commit to joaotavora/vite that referenced this issue Feb 6, 2022
The normalizeUrl helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix, it won't be registered correctly in the module graph.

This change makes it so that the first call doesn't ignore the meta
reply.

This makes Vite's dev server match Rollup's plugin-calling behaviour
and fixes vitejs#6766

Fix: vitejs#6766

* packages/vite/src/node/importGlob.ts (ResolvedUrl): Import it.
(transformImportGlob): Use it.

* packages/vite/src/node/plugins/importAnalysis.ts
(ResolvedUrl): Import it.
(normalizeUrl): Use it. Always return meta.
(transform): Ensure moduleGraph is correctly built.
joaotavora added a commit to joaotavora/vite that referenced this issue Feb 6, 2022
The normalizeUrl helper calls user plugins's resolveId() twice.

* Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

* Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix, it won't be registered correctly in the module graph.

This change makes it so that the second resolution call is actually
passed the query suffix.  Then when updating the moduleGraph for the
correct queryless URL, the meta reply obtained before is preserved.

This makes Vite's dev server match Rollup's plugin-calling behaviour
and fixes vitejs#6766

Fix: vitejs#6766
@aleclarson
Copy link
Member

aleclarson commented Feb 6, 2022

Did you try the minimal reproducible repo I constructed and attached?

I tested it in a Github codespace. I believe this will fix your issue:

diff --git a/plugin.js b/plugin.js
index 20ac4f7..c4f07c2 100644
--- a/plugin.js
+++ b/plugin.js
@@ -4,11 +4,14 @@ export default {
     plugins: [
         {
             enforce: 'pre',
-            async resolveId(id) {
+            async resolveId(id, importer) {
                 let [file, query] = id.split('?');
-                const probe = query && query.match(/xoption=([^&]+)/)
-                const x = probe && Number(probe[1])
-                if (x) return {id: path.resolve(file), meta: {x}}
+                const probe = query && query.match(/xoption=([^&]+)/);
+                const x = probe && Number(probe[1]);
+                if (isNaN(x)) return;
+                const resolved = await this.resolve(file, importer, {skipSelf: true});
+                if (!resolved) return;
+                return {...resolved, meta: { ...resolved.meta, x }};
             },
             async transform(blob, id) {
                 const x = this.getModuleInfo(id)?.meta?.x

@joaotavora
Copy link
Author

joaotavora commented Feb 6, 2022 via email

@joaotavora
Copy link
Author

@aleclarson the minimal reproduction recipe I attached is just a proof of the bug.

Yes, know I can use this.resolve to work around it (you seem to have
abandonedyour earlier "just remove the leading slash" idea, which is good).

But applying the this.resolve complexity to work around much larger
problem I have in my codebase (which is not the MRE, of course) , doesn't
change the fact that vite dev behaves differently from vite build.

The bug in Vite is fixed by either one of those two commits (the first one
being preferable). Of course there are multiple ways to skin a cat, so it
you come up with a third fix, that's fine too!

Thanks for your time and work.
João

@joaotavora
Copy link
Author

joaotavora commented Feb 6, 2022

Just wanted to add that the solution that I recommend,
joaotavora@6e6a27f
needs a small refactor commit that immediately precedes it.

I hope you can consider what is a very small fix to a blind spot
of #5465 (which, as I've said, is a very welcome fix otherwise).
As far as I can tell my commits don't break any tests and it's
easy to tell what's going on from the diff.

@aleclarson
Copy link
Member

Please open a PR and we can get Evan's feedback

joaotavora added a commit to joaotavora/vite that referenced this issue Feb 8, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling behaviour
and fixes vitejs#6766.

Fix: vitejs#6766

* packages/vite/src/node/importGlob.ts (ResolvedUrl): Import it.
(transformImportGlob): Use it.

* packages/vite/src/node/plugins/importAnalysis.ts
(ResolvedUrl): Import it.
(normalizeUrl): Use it. Always return meta.
(transform): Ensure moduleGraph is correctly built.
@joaotavora
Copy link
Author

Please open a PR and we can get Evan's feedback

Done. See. #6811

Shouldn't this issue be reopened so that that that PR (or any other PR...) can subsequentely close it?

@patak-dev patak-dev reopened this Feb 8, 2022
@joaotavora
Copy link
Author

@patak-dev Just noticed there's a "rollup plugin compat" label, maybe add it here?

joaotavora added a commit to joaotavora/vite that referenced this issue Feb 16, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling behaviour
and fixes vitejs#6766.

Fix: vitejs#6766

* packages/vite/src/node/importGlob.ts (ResolvedUrl): Import it.
(transformImportGlob): Use it.

* packages/vite/src/node/plugins/importAnalysis.ts
(ResolvedUrl): Import it.
(normalizeUrl): Use it. Always return meta.
(transform): Ensure moduleGraph is correctly built.
aleclarson pushed a commit to aleclarson/vite that referenced this issue Mar 26, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling behaviour
and fixes vitejs#6766.

Fix: vitejs#6766

* packages/vite/src/node/importGlob.ts (ResolvedUrl): Import it.
(transformImportGlob): Use it.

* packages/vite/src/node/plugins/importAnalysis.ts
(ResolvedUrl): Import it.
(normalizeUrl): Use it. Always return meta.
(transform): Ensure moduleGraph is correctly built.
aleclarson added a commit to aleclarson/vite that referenced this issue Oct 20, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling
behaviour.

Fixes vitejs#6766
@aleclarson aleclarson linked a pull request Oct 20, 2022 that will close this issue
aleclarson added a commit to aleclarson/vite that referenced this issue Oct 20, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling
behaviour.

Fixes vitejs#6766
aleclarson added a commit to aleclarson/vite that referenced this issue Nov 16, 2022
This commit improves the parity of behaviour between 'vite build' and
'vite dev' when Rollup plugins retuning non-null 'meta' properties
from resolveId() hooks are used.

If one takes the Rollup behaviour to be any kind of reference in these
cases, this is fixing a small bug in Vite, which was in a very large
part already fixed by PR vitejs#5465.

Here's an explanation of the faulty behaviour:

The normalizeUrl() helper calls user plugins's resolveId() twice.

- Once with the URL string containing a query suffix.  Here it
  _ignores_ the meta reply.

- Again, with the URL no longer containing said suffix.  Here it
  doesn't ignore the meta reply.  It is stored in the moduleGraph
  node.

As can be seen, if the user's plugin meta reply depends on the query
suffix being passed in the imported URL, that meta reply won't be
registered correctly in the module graph and is not retrievable via
getModuleInfo() later on.

This change makes it so that the first call doesn't ignore the meta
reply. This makes Vite's dev server match Rollup's plugin-calling
behaviour.

Fixes vitejs#6766
@bluwy bluwy added pending triage p3-minor-bug An edge case that only affects very specific usage (priority) and removed pending triage labels Sep 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p3-minor-bug An edge case that only affects very specific usage (priority) rollup plugin compat
Projects
None yet
5 participants