Skip to main content

vitest-migration

Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established. Use when asked to "move <pkg> to vitest", "migrate <pkg> tests off jest", or "run <pkg> unit tests with vitest".

معلومات المصدر

المستودع
nrwl/nx
آخر نشاط في المصدر
٢٥ سبتمبر ٢٠٢٦ في ١٣:٢٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٩٬٣٨٠
التفرعات
٣٬٠٠٧

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
vitest-migration
description
Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established. Use when asked to "move <pkg> to vitest", "migrate <pkg> tests off jest", or "run <pkg> unit tests with vitest".
allowed-tools
Read, Glob, Grep, Agent, Edit(*), Write(*), Bash(pnpm nx *), Bash(npx nx *), Bash(nx *), Bash(git *), Bash(ls *), Bash(cat *), Bash(head *), Bash(tail *), Bash(sed *), Bash(grep *), Bash(rg *), Bash(find *), Bash(wc *), Bash(echo *), Bash(mkdir *), Bash(rm *), Bash(mv *), Bash(node *), Bash(npx oxfmt *), Bash(gh pr view *), Bash(gh pr diff *)
# Migrate a package's unit tests to Vitest Move `packages/<name>`'s unit tests from Jest to Vitest 4, inferred through the `@nx/vitest` plugin. **Start from `packages/workspace`, not `packages/nx`.** The shared machinery a sibling package needs already exists — read these first and reuse them as-is: - `tools/vitest/setup.mts` — the port of `scripts/unit-test-setup.js`; every migrated package loads it as its `setupFiles` - `tools/vitest/nx-source-resolver.mts` — resolves `nx` / `@nx/*` to this repo's source, for both vite and node - `tools/vitest/tsconfig.json` — a leaf tsconfig whose only job is to stop vite's tsconfig lookup. **Do not move these files to the workspace root.** With no tsconfig beside them, the nearest one is the root solution file, and vite walks its `references` — reading all ~114 project tsconfigs on every run, which lands as a sandbox violation. `tools/vitest` is deliberately a plain directory, not an Nx project: adding `project.json` makes `@nx/js:typescript-sync` demand a project reference to a test-only tool from each consuming package's _published_ `tsconfig.lib.json` (`composite: false` does not suppress it) - `packages/workspace/vitest.config.mts` — the config those two plug into - `packages/workspace/project.json` — `test.inputs` naming the shared scripts `packages/nx` (PR #36754, commit `32dd3fb533`) is the _original_ migration but a poor template: it is the one package that imports almost no siblings, so it needs neither the source resolver nor the CJS-channel mocks. Consult it only for `vitest-write-guard.cjs` and `src/internal-testing-utils/cjs-mock.ts`. `packages/angular-rspack/vitest.config.mts` is the simple end of the spectrum (no nx source at all). ## Argument The package name (e.g. `js`, `devkit`, `workspace`). The package lives at `packages/<name>/`. ## Why this is not a find-and-replace Jest and Vitest disagree on module semantics, not just API names. The mechanical `jest.*` → `vi.*` rename is maybe 80% of the diff and 20% of the work. The rest is: which _channel_ a mock reaches (ESM graph vs CJS `require()`), whether a namespace is frozen, and what `resetAllMocks` does to a spy. Budget for hand-fixing specs after the codemod. --- ## Step 0 — Survey the package Run these and write the answers into `tmp/notes/vitest-migration-<name>.md` before touching anything: ```bash ls packages/<name>/jest.config.cts packages/<name>/jest*.js 2>/dev/null cat packages/<name>/jest.config.cts cat packages/<name>/tsconfig.spec.json grep -rl "\.spec\.ts" -c packages/<name>/src | wc -l # rough spec count pnpm nx show project <name> --json | head -40 ``` Capture: 1. **Spec count and current runtime.** Run `pnpm nx test <name> --skip-nx-cache` once and record the reported test count and wall time. That number is the parity target in Step 6 — you cannot verify the migration without it. 2. **Jest config specials** — anything beyond `displayName`/`preset`/ `moduleFileExtensions` is behavior you must reproduce: - `setupFiles` (e.g. `packages/devkit/jest-setup-nx-workspace-data-dir.js`) - `moduleNameMapper` (path shims; also `identity-obj-proxy` for CSS) - `testEnvironment: 'jsdom'` → needs `environment: 'jsdom'` and the `jsdom` dep - `modulePathIgnorePatterns` / `testPathIgnorePatterns` → `exclude` - `resolver` → `resolve.conditions` (see Step 2) 3. **Inherited preset behavior** (`jest.preset.js`) that Vitest does _not_ get for free: - `setupFiles: ['../../scripts/unit-test-setup.js']` — the workspace-wide project-graph / workspace-context / native guards. **This must be ported** (Step 3). - `resolver: '../../scripts/patched-jest-resolver.js'` — maps `@nx/*` and `nx/*` onto `packages/*` source, **and** sets `NX_WORKSPACE_ROOT_PATH=<repo>/tmp/unit` as a side effect. Both are reproduced by the shared scripts (Steps 2 and 3). - `moduleNameMapper` ESM shims (`@clack/prompts`, `ora`, `chalk`, `yargs-parser`, `prettier`, `magic-string`, `oxfmt`). Most are pure ESM interop Vitest does not need — but check each for _behavior_ before dropping it. `@clack/prompts` is load-bearing: the stub answers `undefined` where the real library drives a **synchronous** prompt, and a generator that asks a question blocks the worker forever with no test timeout. `tools/vitest/setup.mts` already keeps that one. `prettier`'s stub also pins `resolveConfig: () => null`, which matters if the package snapshots formatted output. - `maxWorkers: 1` — Vitest runs files in parallel. Any spec relying on cross-file ordering or a shared mutable temp dir will now fail. This is the main source of "it passed under jest" flakes. 4. **Native bindings** — does the package load `nx/src/native` or a `.node` file? If yes you need `pool: 'forks'` and the native shim plugin from `packages/nx/vitest.config.mts`. 5. **Lazy `require()` of TS source** — `grep -rn "require(" packages/<name>/src --include=*.ts | grep -v "^.*spec"`. Every bare `require()` of a local `.ts` file needs `@swc-node/register` (Step 2) and can only be mocked through `mockCjsModule` (Step 4). --- ## Step 1 — Target inference `@nx/vitest` is already registered in `nx.json` for `packages/**/*`, so a `vitest.config.mts` at the package root is enough to infer `<name>:test`. Verify the plugin block still reads: ```json { "plugin": "@nx/vitest", "options": { "testTargetName": "test" }, "include": ["packages/**/*"], "exclude": ["**/out-tsc/**"] } ``` `@nx/jest` infers `test` from `jest.config.*` presence. **Both plugins would claim `test`**, so `jest.config.cts` must be deleted in the same change, not left behind "just in case". Also delete any `jest-resolver.js` and drop `project.json` target overrides that reference jest inputs (see the `packages/nx` diff — a `"test": { "inputs": [..., "patched-jest-resolver.js"] }` block was removed). --- ## Step 2 — Write `packages/<name>/vitest.config.mts` Start from `packages/nx/vitest.config.mts` and keep only what the survey justified. The load-bearing pieces and why: ```ts export default defineConfig({ root: import.meta.dirname, cacheDir: '../../node_modules/.vite/<name>/unit', test: { watch: false, globals: true, // specs use bare describe/it/expect/vi environment: 'node', // or 'jsdom' if the jest config said so include: ['**/*.spec.ts'], exclude: ['**/node_modules/**'], setupFiles: ['./vitest.setup.mts'], testTimeout: 35000, // matches jest.preset.js pool: 'forks', // ONLY if native .node bindings are loaded; // they are not thread-safe across workers teardownTimeout: 60_000, // specs holding native contexts exit slowly; // the jest setup hid this behind --forceExit execArgv: ['--conditions=@nx/nx-source'], server: { deps: { external: [/\.node$/] } }, }, resolve: { conditions: ['@nx/nx-source'], }, plugins: [nxSourceResolver()], // tools/vitest/nx-source-resolver.mts }); ``` Rules for resolution — the part that most looks solved and isn't: - **`conditions: ['@nx/nx-source']` does NOT replace the jest resolver.** `node_modules/nx` and `node_modules/@nx/*` are the _published_ tarballs (dist only, no source), and their exports maps advertise `@nx/nx-source` entries pointing at `./src/index.ts` files the tarball does not ship — so the condition resolves to a file that isn't there. Use `nxSourceResolver()` from `tools/vitest/nx-source-resolver.mts`, which maps `nx` / `@nx/*` through the _local_ `packages/<pkg>/package.json`, with a file fallback for deep imports no exports entry covers (`@nx/workspace/src/...`). - **`execArgv: ['--conditions=@nx/nx-source']` on its own actively breaks node resolution**, for the same reason: a lazy `require('@nx/js')` dies with `Cannot find module '.../node_modules/@nx/js/src/index.ts'`. Keep the flag, but `tools/vitest/setup.mts` must also patch `Module._resolveFilename` with the same mapping so both channels agree. - **Aliases use regex, not strings.** Vite string aliases do prefix matching, so `'@nx/devkit'` would rewrite `@nx/devkit/internal` too. Use `{ find: /^@nx\/devkit$/, replacement: ... }`. - `packages/nx` predates the shared resolver and hard-codes `nx/src/*` and `nx/bin/*` aliases instead. Don't copy that — the resolver covers it. - If the package imports `yargs` with CJS-namespace style (`yargs.terminalWidth()`), alias it to `node_modules/yargs/index.cjs`. - If the package loads `nx/src/native`, copy the `nx-native-shim` plugin verbatim — `src/native/index.js` requires TS files and cannot run outside a transform, so it must be routed to the generated `native-bindings.js` and externalized. --- ## Step 3 — Wire up the shared setup Point the config at the shared file; do not write a per-package copy: ```ts setupFiles: ['../../tools/vitest/setup.mts'], ``` `tools/vitest/setup.mts` is the port of `scripts/unit-test-setup.js` (which is jest-only — `jest.doMock` — so it can never be imported from vitest). Read it before assuming anything is missing; it already does all of the following, and each line is there because its absence broke `packages/workspace`: - `NX_DAEMON=false`, `npm_config_user_agent` deleted, `FORCE_COLOR` deleted and `NO_COLOR=1` (snapshots are recorded colorless). - `NX_WORKSPACE_ROOT_PATH` under `tmp/unit/<pid>` — **per worker process**, unlike jest. The jest resolver set a single `tmp/unit` as a side effect; with vitest's parallel workers one shared root makes every worker queue on the same lock ("Waiting for graph construction in another process to complete", 35s timeouts). - `NX_ISOLATE_PLUGINS=false`. Otherwise plugin isolation spawns a worker subprocess per plugin that is never torn down, and the spec file stalls to its timeout. Two `packages/nx` specs already carry this same note. - `@swc-node/register`, with `Error.prepareStackTrace` **restored immediately after**: the hook installs source-map-support, which mis-maps vite-transformed frames and breaks error locations _and_ inline-snapshot updates. - `Module._resolveFilename` patched with the source mapping (Step 2), plus `@clack/prompts` → `scripts/jest-mocks/clack-prompts.js`. - `vi.doMock` graph/workspace-context/native guards, keyed by **absolute physical path** — mocking the `nx/src/...` specifier routes through the pnpm symlink and keys as a different module, so the mock silently never applies. - **The same graph mocks again, on the CJS channel**, via a `Module._load` patch. This is the one most easily missed and the most expensive to debug: generators reach graph builders through lazy `require()`, which `vi.mock` cannot see, and the unmocked `createProjectGraphAsync` takes `project-graph.lock` and **deadlocks the worker** — no output, and no test timeout fires, because the main thread is blocked in a futex. - Pass-through helpers are plain functions, not `vi.fn()`, so a suite's `vi.resetAllMocks()` cannot wipe them into `() => undefined`. Add to the shared file (not a package-local one) if the package needs a guard nothing else does, and say so in the PR — every migrated package loads it. Two more rules: 1. **Do not alias a `jest` global in the setup.** A stray `jest.mock` would not be hoisted by vitest's transform and would silently fail to intercept. Let it throw. 2. If the package's specs can write repo files, copy `packages/nx/vitest-write-guard.cjs` and load it through `execArgv: ['--require', ...]`. It must be `execArgv`, not `setupFiles`: node snapshots a module's ESM named exports on first import, so a patch applied from a setup file is invisible to `import { writeFile } from 'fs'`. (The `packages/nx` migration found a spec that had been overwriting the repo's real `nx.json`.) Finally, name the shared files in the package's `project.json` so the cache sees them — they live outside `{projectRoot}`, so nothing else invalidates on an edit: ```json "test": { "inputs": [ "...", "{workspaceRoot}/tools/vitest/**/*", "{workspaceRoot}/scripts/jest-mocks/clack-prompts.js" ] } ``` These are not optional bookkeeping. `@nx/vitest` infers `setup.mts` and its tsconfig (nx#36920), but nothing infers the resolver the config imports or the clack mock the setup loads by path — `default` is project-scoped — so leaving them off does not fail loudly; it serves a **stale cache hit** the next time someone edits them. Keep the whole `tools/vitest/**/*` glob rather than naming the resolver alone, so a helper added there later is covered too. --- ## Step 4 — `tsconfig.spec.json` ```jsonc { "compilerOptions": { "types": ["vitest/globals", "node"], // was ["jest", "node"] }, "include": [ // ... "vitest.config.mts", // replaces "jest.config.ts" "vitest.setup.mts", ], } ``` Drop `@types/jest` from the package's `devDependencies` only if no other project in the repo still needs it there. --- ## Step 5 — Codemod the specs Apply mechanically, then hand-fix. Prefer one script over 200 manual edits, and commit the codemod pass separately from the hand fixes so review can follow. Two rules before you run anything: - **Never codemod the whole package blindly.** Some files contain `jest.*` in _strings_, not calls — a spec for a codemod that rewrites `jest.mock(...)`
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub