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

Imported dependency file executed twice #5404

Closed
6 tasks done
pt7892 opened this issue Mar 19, 2024 · 3 comments · Fixed by #5416
Closed
6 tasks done

Imported dependency file executed twice #5404

pt7892 opened this issue Mar 19, 2024 · 3 comments · Fixed by #5416

Comments

@pt7892
Copy link

pt7892 commented Mar 19, 2024

Describe the bug

My code is using libraries @rainbow-me/rainbowkit, which has wagmi as peerDependency. These two work fine in development using vite, but trying to test same code with latest vitest fails. I tried to debug this, and found out that node_modules/wagmi/dist/esm/context.js file is executed 2 times - once when my test code imports WagmiProvider, and second time when @rainbow-me/rainbowkit imports something from wagmi. This creates an issue because export const WagmiContext = createContext(undefined); is executed twice, creates two different contexts, and error is throw during test: WagmiProviderNotFoundError: useConfig must be used within WagmiProvider

If I downgrade vitest to 0.34.6, same code works, and node_modules/wagmi/dist/esm/context.js is executed only once

My test code:

import { describe, expect, it } from "vitest";
import { render } from "@testing-library/react";
import { WagmiProvider, createConfig, useConfig } from "wagmi";
import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { mainnet } from "viem/chains";
import { http } from "viem";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

describe("Works", () => {
  it("should work", () => {
    render(
      <WagmiProvider
        config={createConfig({
          chains: [mainnet],
          transports: { [mainnet.id]: http() },
        })}
      >
        <QueryClientProvider client={queryClient}>
          <TestComponent />

          {/* error thrown here */}
          <RainbowKitProvider>
            <p>Hello World</p>
          </RainbowKitProvider>
        </QueryClientProvider>
      </WagmiProvider>
    );

    expect(1 + 1).toBe(2);
  });
});

const TestComponent = () => {
  // this works as expected, error is not thrown
  useConfig();

  return null;
};

node_modules/wagmi/dist/esm/context.js which is executed twice

'use client';
import {} from '@wagmi/core';
import { createContext, createElement } from 'react';
import { Hydrate } from './hydrate.js';
export const WagmiContext = createContext(undefined);
export function WagmiProvider(parameters) {
    const { children, config } = parameters;
    const props = { value: config };
    return createElement(Hydrate, parameters, createElement(WagmiContext.Provider, props, children));
}
//# sourceMappingURL=context.js.map

package.json

{
  "name": "wagmi-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@rainbow-me/rainbowkit": "^2.0.2",
    "@tanstack/react-query": "^5.28.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "viem": "^2.8.11",
    "wagmi": "^2.5.11"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.4.2",
    "@testing-library/react": "^14.2.1",
    "@types/react": "^18.2.67",
    "@types/react-dom": "^18.2.22",
    "jsdom": "^24.0.0",
    "typescript": "^5.4.2",
    "vitest": "1.4.0"
  },
  "packageManager": "yarn@4.1.1"
}

vitest.config.ts

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "jsdom",
  },
});

Reproduction

Here is small reproduction example. This one uses vitest version 1.4.0. To make it work, downgrade it to 0.34.6

https://github.com/pt7892/vitest-repro

System Info

System:
    OS: macOS 14.3.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 1.26 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.11.0 - ~/.proto/shims/node
    Yarn: 4.1.1 - ~/.asdf/shims/yarn
    npm: 10.2.4 - ~/.proto/shims/npm
    pnpm: 8.15.2 - ~/.proto/shims/pnpm
    bun: 1.0.4 - ~/.asdf/shims/bun
    Watchman: 2023.04.10.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 122.0.6261.129
    Safari: 17.3.1
  npmPackages:
    vitest: 0.34.6 => 1.4.0

Used Package Manager

yarn

Validations

@hi-ogawa
Copy link
Contributor

I've seen a similar issue recently and your case is somewhat similar where wagmi is somehow inlined (i.e. processed vite/vitest transform).

I thought there might be some packaging issue of wagmi, but they are all green on publint, so there might be a problem with Vitest's externalization heuristics.

For the time being, I think it should be safe to explicitly set server.deps.external, so that wagmi always ends up with import("wagmi"), meaning there is only a single copy managed by NodeJs.

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "jsdom",
    server: {
      deps: {
        external: ["wagmi"]
      }
    }
  },
});

I'll need to look into further to see where the issue lies.

@pt7892
Copy link
Author

pt7892 commented Mar 19, 2024

Thank you for help, I can confirm that this workaround/fix works!

@hi-ogawa
Copy link
Contributor

Vitest's externalization heuristic is based on isValidNodeImport and this might be the one having an issue.

if (/\.(\w+-)?esm?(-\w+)?\.js$|\/(esm?)\//.test(id))
return false
id = id.replace('file:///', '')
const package_ = await findNearestPackageData(dirname(id))
if (package_.type === 'module')
return true

As mentioned in the comment, isValidNodeImport seems to be copied from mlly, but the original one actually has a flipped condition where it checks pkg.type === "module" before .../esm/...js:

https://github.com/unjs/mlly/blob/c5bcca0cda175921344fd6de1bc0c499e73e5dac/src/syntax.ts#L82-L90

  const package_ = await readPackageJSON(resolvedPath).catch(() => {});
  // @ts-ignore
  if (package_?.type === "module") {
    return true;
  }

  if (/\.(\w+-)?esm?(-\w+)?\.js$|\/(esm?)\//.test(resolvedPath)) {
    return false;
  }

wagmi is resolved to ./dist/esm/exports/index.js, so the regex check hits first and isValidNodeImport is returning false on Vitest.

I wonder if Vitest introduced this flipped version on purpose or it may be a simple oversight. The PR #4368 is quite big, so I haven't gone through all the context.

@github-actions github-actions bot locked and limited conversation to collaborators Apr 11, 2024
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants