molefrog / wouter

🥢 A minimalist-friendly ~2.1KB routing for React and Preact
https://npm.im/wouter
The Unlicense
6.41k stars 146 forks source link

How to use V3 with Jest? #415

Closed justingrant closed 2 months ago

justingrant commented 5 months ago

I'm porting a project from wouter V2 to wouter V3. The new version only ships ESM. Has anyone figured out how to get V3 working with Jest? Currently I'm getting this error:

Must use import to load ES Module:  <redacted path>/node_modules/wouter/esm/index.js

I'm using NODE_OPTIONS=--experimental-vm-modules when running Jest, as recommended by the Jest docs.

But I couldn't figure out the magic set of options that would allow Jest to import wouter.

stanlemon commented 5 months ago

Add this to your jest.config.js

transformIgnorePatterns: [`/node_modules/(?!wouter)`],
justingrant commented 4 months ago

@stanlemon - This config change didn't change the errors we get when trying to run jest on a project that uses wounter V3.

If we run NODE_OPTIONS=--experimental-vm-modules jest, then the error we get is Must use import to load ES Module: /<my root dir here>/node_modules/wouter/esm/index.js.

But if we just run jest, without NODE_OPTIONS=--experimental-vm-modules then I get a Jest encountered an unexpected token error on the first line of /node_modules/wouter/esm/index.js. Which makes sense because it's expecting CJS but got ESM import.

Adding "transformIgnorePatterns": ["/node_modules/(?!wouter)"] doesn't seem to change this result at all.

Any idea what we should try next? It'd be great to see a working example from another project. Do you know of any TypeScript project in GitHub, CodeSandbox, etc. that uses wouter V3 and Jest successfully?

Note that the rest of our tests run fine with or without --experimental-vm-modules arg and with or without transformIgnorePatterns. It's only wouter V3 (or presumably any other ESM-only library used by Jest) that will fail.

If it's helpful to spot what's going wrong, here's our jest config:

  "jest": {
    "moduleNameMapper": {
      "^_/(.*).(woff|woff2)$": "<rootDir>/assets/test/fileMock.ts",
      "^_/(.*)$": "<rootDir>/src/$1",
      "^wouter$": "<rootDir>/node_modules/wouter/esm",
      "^uuid$": "<rootDir>/node_modules/uuid/dist/index.js",
      "^react-markdown$": "<rootDir>/node_modules/react-markdown/react-markdown.min.js"
    },
    "setupFilesAfterEnv": [
      "<rootDir>/jest-setup.ts"
    ],
    "transformIgnorePatterns": ["/node_modules/(?!wouter)"],
    "snapshotResolver": "<rootDir>/snapshot-resolver.ts",
    "testEnvironment": "jsdom"
  }

Here's our dependencies, in case it's helpful:

  "dependencies": {
    "@lingui/core": "^4.7.0",
    "@lingui/macro": "^4.7.0",
    "@radix-ui/react-slider": "^1.1.2",
    "@react-three/drei": "^9.97.5",
    "@react-three/fiber": "^8.15.16",
    "@tanstack/react-table": "^8.11.8",
    "@types/d3-color": "^3.1.3",
    "@types/d3-scale-chromatic": "^3.0.3",
    "@visx/visx": "^3.8.0",
    "axios": "^1.6.7",
    "boring-avatars": "^1.10.1",
    "d3-color": "^3.1.0",
    "d3-scale-chromatic": "^3.0.0",
    "dayjs": "^1.11.10",
    "jotai": "^2.6.4",
    "path-to-regexp": "^6.2.1",
    "polished": "^4.3.1",
    "ramda": "^0.29.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-dropzone": "^14.2.3",
    "react-hook-form": "^7.50.1",
    "react-icons": "^5.0.1",
    "react-markdown": "^9.0.1",
    "react-query": "^3.39.3",
    "react-select": "^5.8.0",
    "rehype-highlight": "^7.0.0",
    "remark-gfm": "^4.0.0",
    "styled-components": "^6.1.8",
    "three": "^0.161.0",
    "uplot": "^1.6.30",
    "uplot-react": "^1.1.5",
    "uuid": "^9.0.1",
    "wouter": "^3.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.23.9",
    "@babel/plugin-transform-runtime": "^7.23.9",
    "@babel/preset-env": "^7.23.9",
    "@babel/preset-react": "^7.23.3",
    "@babel/preset-typescript": "^7.23.3",
    "@lingui/babel-plugin-extract-messages": "^4.7.0",
    "@lingui/babel-preset-react": "^2.9.2",
    "@lingui/cli": "^4.7.0",
    "@mdx-js/mdx": "^3.0.0",
    "@storybook/addon-actions": "^7.6.14",
    "@storybook/addon-essentials": "^7.6.14",
    "@storybook/addon-interactions": "^7.6.14",
    "@storybook/addon-links": "^7.6.14",
    "@storybook/react": "^7.6.14",
    "@storybook/react-webpack5": "^7.6.14",
    "@storybook/testing-library": "^0.2.2",
    "@testing-library/dom": "^9.3.4",
    "@testing-library/jest-dom": "^6.4.2",
    "@testing-library/react": "^14.2.1",
    "@testing-library/user-event": "^14.5.2",
    "@types/jest": "^29.5.12",
    "@types/ramda": "^0.29.10",
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "@types/three": "^0.161.2",
    "@types/uuid": "^9.0.8",
    "@types/webpack": "^5.28.5",
    "@typescript-eslint/eslint-plugin": "^7.0.1",
    "@typescript-eslint/parser": "^7.0.1",
    "babel-loader": "^9.1.3",
    "babel-plugin-macros": "^3.1.0",
    "babel-plugin-styled-components": "^2.1.4",
    "babel-runtime": "^6.26.0",
    "copy-webpack-plugin": "^12.0.2",
    "core-js": "^3.35.1",
    "css-loader": "^6.10.0",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-json": "^3.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-storybook": "^0.6.15",
    "file-loader": "^6.2.0",
    "fork-ts-checker-webpack-plugin": "^9.0.2",
    "html-webpack-plugin": "^5.6.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-styled-components": "^7.2.0",
    "knip": "^5.0.0",
    "msw": "^1.3.2",
    "prettier": "^3.2.5",
    "shader-loader": "^1.3.1",
    "storybook": "^7.6.14",
    "style-loader": "^3.3.4",
    "ts-loader": "^9.5.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.2.2",
    "webpack-cli": "^5.1.4",
    "worker-loader": "^3.0.8"
  },

And here's our tsconfig.json:

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "esModuleInterop": true,
    "jsx": "react",
    "module": "es6",
    "lib": ["dom", "es2022"],
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noImplicitReturns": true,
    "outDir": "./build",
    "paths": {
      "_/*": ["src/*"]
    },
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "target": "ES2022"
  },
  "exclude": ["node_modules", "build"]
}
stanlemon commented 4 months ago

@justingrant Take a look at https://github.com/stanlemon/javascript/tree/main/apps/template for a working example of wouter + jest. You can find my jest and babel configs in that same repo.

eggcllnt commented 1 month ago

In case somebody using pnpm, you should set

transformIgnorePatterns: [`/node_modules/.pnpm/(?!wouter)`],