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

"TypeError: Cannot create proxy with a non-object as target or handler" when importing node module with a Proxy #6115

Closed
6 tasks done
JoshuaKGoldberg opened this issue Jul 12, 2024 · 5 comments
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@JoshuaKGoldberg
Copy link
Contributor

Describe the bug

When importing from an installed npm package such as not-a-log that uses a Proxy, Vitest crashes with:

 FAIL  index.test.js [ index.test.js ]
TypeError: Cannot create proxy with a non-object as target or handler
 ❯ Object.get node_modules/not-a-log/not-a-log.js:9:12
 ❯ index.test.js:1:1
      1| import logger from "not-a-log";
       | ^
      2|

Reproduction

https://stackblitz.com/edit/vitest-dev-vitest-keoxtv

import logger from "not-a-log";

I also set up a full reproduction here: https://github.com/JoshuaKGoldberg/repros/tree/vitest-proxy-not-a-log

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.20.3 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    @vitest/ui: latest => 2.0.2 
    vite: latest => 5.3.3 
    vitest: latest => 2.0.2

Used Package Manager

npm

Validations

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Jul 23, 2024

Thanks for the reproduction. It looks like this is because "then" property access is send to not-a-log's proxy due to interop.

return new Proxy(mod, {
get(mod, prop) {
if (prop === 'default') {
return defaultExport
}
return mod[prop] ?? defaultExport?.[prop]
},

First of all, "then" access shouldn't happen for static import, but Vitest (Vite SSR) rewrite's all imports as dynamic import internally, so that part cannot be probably fixed (cf. #5122).

Technically, the same error can happen in innocent-looking async/await code outside of Vitest, so maybe not-a-log should be robust about this use case. For example, a following code causes same error on node:

// node repro.mjs

async function main() {
  // "await" implicitly checks `mod.default.then`
  await import("not-a-log").then(mod => mod.default);
}

main();

// or maybe this looks more innocent
async function main2() {
  const { default: logger } = await import("not-a-log");
  return logger;
}

// "await" implicitly checks `logger.then`
const logger = await main2();

One way to save error this on Vitest is to fix this TODO

// never interop ESM modules
// TODO: should also skip for `.js` with `type="module"`
return !path.endsWith('.mjs') && 'default' in mod

or you can try disabling this "interop" entirely by test.deps.interopDefault: false, which would preserve node import semantics.

export default defineConfig({
  test: {
    deps: {
      interopDefault: false,
    }
  },
})

@hi-ogawa hi-ogawa added p3-minor-bug An edge case that only affects very specific usage (priority) and removed pending triage labels Jul 23, 2024
@JoshuaKGoldberg
Copy link
Contributor Author

Thanks for the deep dive & explanation! I filed an issue upstream: jimmywarting/not-a-log#2.

@sheremet-va
Copy link
Member

I am not sure if this is a bug in Vitest. I don't think it's possible to support this case because then on an object is always called even if you return then from a module:

// mod.ts
export const then = () => {}

// index.ts
await import('./mod.ts')

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Aug 1, 2024

Named export of then would be impossible (which is the case of actor library in #5122), but this issue is slightly different since Vitest is accessing then on default export object due to cjs interop.

Since not-a-log has type: "module", cjs interop should not be necessary, but Vitest does it currently due to this heuristics:

// never interop ESM modules
// TODO: should also skip for `.js` with `type="module"`
return !path.endsWith('.mjs') && 'default' in mod

actor library crashes on Vite SSR, but not-a-log works on Vite SSR.

@hi-ogawa
Copy link
Contributor

I'm closing this for the same reason as #5122 and upstream vitejs/vite#18328

@hi-ogawa hi-ogawa closed this as not planned Won't fix, can't repro, duplicate, stale Oct 11, 2024
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)
Projects
None yet
Development

No branches or pull requests

3 participants