agentsclimarketplace

Cra to vite

Skill Siddharth00/agent-revamp-skills/skills/04-migrate/frontend/cra-to-vite

Production-grade migration skills for AI coding agents. Revamp any product, module, or feature from one stack to another.

Install
npx -y skills add Siddharth00/agent-revamp-skills --skill cra-to-vite

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Migrates a Create React App project to Vite 5.x + Vitest using a strangler-fig approach: both build tools run in parallel until Vite is proven equivalent, then CRA is removed.

SKILL.md

25.3 KB, as published. Nobody here has run it

1. Purpose

This skill replaces a Create React App setup (react-scripts, Webpack 5) with Vite 5.x and Vitest using a strangler-fig approach: Vite is introduced alongside the existing CRA toolchain, both build pipelines run in CI simultaneously, and traffic is switched via a feature flag once Vite output is proven equivalent. A frontend engineer or build-systems engineer runs this skill once per application — it is not file-by-file; it operates at the toolchain level. The migration is non-trivial because CRA abstracts Webpack configuration that Vite does not replicate automatically: environment variable prefixes change (REACT_APP_VITE_), absolute import resolution must be re-expressed as Vite aliases, CSS Modules defaults differ in edge cases, and the Jest test suite must be ported to Vitest, whose API is compatible but whose module mocking semantics diverge in specific patterns. The rollback path keeps react-scripts in package.json and the original CI build script intact throughout the transition; Vite is only promoted to primary when all equivalence gates pass in staging.


2. Trigger Conditions

Use when:

  • The application's build tooling is react-scripts (CRA), confirmed by "react-scripts" in package.json scripts.build.
  • The Phase 3 preparation is complete: the repo has a staging environment, a CI pipeline that can run two build scripts in parallel, and a feature flag system that can toggle which build artifact is served.
  • The full Jest suite passes on the current commit (npm test -- --watchAll=false exits 0).
  • The application has no CRA eject history — package.json must not contain a "webpack" direct dependency or a config/webpack.config.js file. Ejected CRAs require a different skill.
  • A Lighthouse baseline for the staging URL has been recorded (needed for the regression gate).

Do NOT use when:

  • The application has been ejected from CRA (config/webpack.config.js exists) — the Webpack config must be ported manually before this skill applies.
  • The application uses react-app-rewired or craco — custom Webpack overrides need to be inventoried and mapped to Vite plugin equivalents before running this skill.
  • The Jest suite is currently failing — do not migrate the test runner on top of a broken suite.
  • The team is mid-way through a TypeScript migration (skills/04-migrate/frontend/js-to-typescript/) — finish the TS migration first; mixed .js/.ts files in CRA behave differently under Vite's default config.
  • There is no staging environment — the Lighthouse regression gate cannot be validated without one.

3. Inputs

Required:

InputTypeDescription
repo_rootfile-pathAbsolute path to the repository root (where package.json lives). All commands run from here unless noted.
app_namestringValue of name in package.json. Used to name output artifacts and identify the correct package.json when the repo is a monorepo.
staging_urlstringFull URL of the staging environment (e.g. https://staging.example.com). Used to run Lighthouse before and after the Vite build is wired in.
lighthouse_baselinefile-pathPath to a Lighthouse JSON report captured before this skill runs. Generate with npx lighthouse <staging_url> --output json --output-path <path>. If absent, halt and ask the user to generate it.
feature_flag_keystringThe feature flag key (in the team's flag system) that controls which build artifact CI serves to staging. The flag must exist and default to false (CRA artifact) before this skill runs.

Optional:

InputTypeDefaultDescription
vite_portinteger3001Local dev port for the Vite dev server. CRA defaults to 3000; use a different port so both can run simultaneously during parallel validation.
lighthouse_budget_pointsinteger5Maximum allowed Lighthouse score regression (Performance category) before the equivalence gate fails. Raise to 10 only with explicit sign-off; >10 is the hard rollback trigger.
css_modules_local_identstring[local]_[hash:5]CSS Modules generateScopedName pattern for Vite. CRA uses [name]__[local]--[hash:base64:5]; set this to match if server-rendered class names are hardcoded anywhere.
vitest_setup_filefile-pathsrc/setupTests.tsPath to the test setup file to pass to Vitest's setupFiles. Maps directly from CRA's setupTests convention.
<!-- STOP CONDITIONS: - If `package.json` does not contain `"react-scripts"` in `scripts.build`, halt: "This skill requires a CRA project. Found <actual build command> instead of react-scripts." - If `lighthouse_baseline` file does not exist at the given path, halt: "I need a Lighthouse baseline JSON at <lighthouse_baseline>. Run: npx lighthouse <staging_url> --output json --output-path <path>" - If `feature_flag_key` is not provided, halt: "I need a feature flag key to control which build artifact staging serves. Provide it or confirm a flag has been created before running this skill." -->

4. Steps

  1. Read <repo_root>/package.json. Confirm scripts.build contains react-scripts build. Record the current versions of react-scripts, react, and react-dom as cra_react_version. Confirm no config/webpack.config.js exists — if it does, STOP: "This project has been ejected from CRA. This skill does not apply."

  2. Read every file matching <repo_root>/src/**/*.{js,ts,jsx,tsx} and grep for process.env.REACT_APP_. Collect each unique variable name (e.g. REACT_APP_API_URL, REACT_APP_FEATURE_X). Write the full list to output/cra-to-vite-env-map-<timestamp>.json as an array of { "old": "REACT_APP_FOO", "new": "VITE_FOO" } objects.

    • Also grep for process.env.NODE_ENV — these do not need renaming but must be confirmed to work under Vite (Vite exposes import.meta.env.MODE, not process.env.NODE_ENV). Flag each occurrence for Step 10.
  3. Read <repo_root>/tsconfig.json (or jsconfig.json). Extract every path alias under compilerOptions.paths (e.g. "@components/*": ["src/components/*"]). Write the alias list to output/cra-to-vite-alias-map-<timestamp>.json as { "find": "@components", "replacement": "/src/components" } objects. These become resolve.alias entries in vite.config.ts.

  4. → Hand off to code-archaeologist (see Section 5. Agent Handoffs) to inventory CSS Modules usage patterns. Wait for output/cra-to-vite-css-inventory-<timestamp>.md before continuing.

  5. Install Vite and its dependencies without removing react-scripts. Run from <repo_root>:

    npm install --save-dev \
      vite@^5 \
      @vitejs/plugin-react@^4 \
      vitest@^1 \
      @vitest/ui@^1 \
      @testing-library/jest-dom@^6 \
      jsdom@^24
    
    • If this fails: check for peer dependency conflicts with react@<cra_react_version>. The most common conflict is @testing-library/react version pinned by CRA. Resolve by adding --legacy-peer-deps and log the override in the migration log.
  6. Create <repo_root>/vite.config.ts with the following content, substituting the alias list from Step 3 and the port from vite_port:

    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    import { resolve } from 'path';
    
    // Aliases derived from tsconfig.json compilerOptions.paths — keep in sync.
    // Generated by skills/04-migrate/frontend/cra-to-vite on <timestamp>.
    const aliases = [
      // <<INSERT alias objects from output/cra-to-vite-alias-map-<timestamp>.json>>
      // Example: { find: '@components', replacement: resolve(__dirname, 'src/components') }
    ];
    
    export default defineConfig(({ mode }) => ({
      plugins: [react()],
    
      resolve: {
        alias: aliases,
      },
    
      server: {
        port: <vite_port>,       // CRA uses 3000; keep Vite on a different port during parallel run.
        open: false,             // Don't auto-open browser — CI environments will fail.
        strictPort: true,        // Fail fast if the port is taken rather than silently rebinding.
      },
    
      build: {
        outDir: 'build-vite',   // Deliberately NOT 'build' — CRA still owns 'build/' during transition.
        sourcemap: true,
        rollupOptions: {
          output: {
            // Stable chunk names for diffing against CRA output.
            entryFileNames: 'static/js/[name].[hash].js',
            chunkFileNames: 'static/js/[name].[hash].js',
            assetFileNames: 'static/media/[name].[hash][extname]',
          },
        },
      },
    
      css: {
        modules: {
          // CRA default: [name]__[local]--[hash:base64:5]
          // Adjust generateScopedName to match if class names are referenced in tests or snapshots.
          generateScopedName: '<css_modules_local_ident>',
        },
      },
    
      test: {
        globals: true,           // Allows `describe`, `it`, `expect` without imports — matches Jest API.
        environment: 'jsdom',
        setupFiles: ['<vitest_setup_file>'],
        css: true,               // Process CSS Modules in tests — CRA's Jest config does this via identity-obj-proxy.
        coverage: {
          provider: 'v8',
          reporter: ['text', 'lcov'],
        },
      },
    
      // Vite does not inject process.env. Replace REACT_APP_ vars and NODE_ENV shims.
      define: {
        // <<INSERT: one entry per REACT_APP_ variable found in Step 2, using import.meta.env>>
        // Example: 'process.env.REACT_APP_API_URL': 'import.meta.env.VITE_API_URL',
        // NODE_ENV shim — remove once all process.env.NODE_ENV references are updated to import.meta.env.MODE.
        'process.env.NODE_ENV': JSON.stringify(mode === 'production' ? 'production' : 'development'),
      },
    }));
    
    • After writing the file, run npx tsc --noEmit -p tsconfig.json to confirm the config parses without errors. If it fails, check that "moduleResolution": "bundler" or "node" is set in tsconfig and that vite/client types are available.
  7. Create <repo_root>/index.html in the repo root (Vite requires the HTML entry point at root, not public/index.html). Copy public/index.html, then:

    • Remove the %PUBLIC_URL% prefix from all asset paths (Vite serves from root; %PUBLIC_URL% is a CRA-ism).
    • Add <script type="module" src="/src/index.tsx"></script> (or .jsx/.js — match the actual entry file) before </body>.
    • Confirm <link rel="icon"> and <link rel="manifest"> still reference valid paths under public/.
    • If this fails: check that public/index.html exists and that the entry file is src/index.tsx (or equivalent). Log the actual entry file path used.
  8. Add Vite scripts to package.json without removing the CRA scripts. The CRA scripts remain untouched until Section 9 gates pass:

    {
      "scripts": {
        "start":       "react-scripts start",
        "build":       "react-scripts build",
        "test":        "react-scripts test --watchAll=false",
        "start:vite":  "vite --port <vite_port>",
        "build:vite":  "vite build",
        "test:vite":   "vitest run",
        "preview:vite":"vite preview --port <vite_port>"
      }
    }
    
  9. Rename all environment variables in source files: for each entry in output/cra-to-vite-env-map-<timestamp>.json, replace process.env.<OLD> with import.meta.env.<NEW> across all files in <repo_root>/src/. Write each replacement to the migration log (file path, line number, old → new).

    • Also update any .env, .env.development, .env.production, .env.test files: rename each REACT_APP_FOO=value line to VITE_FOO=value.
    • Flag any process.env.NODE_ENV occurrences found in Step 2 with an inline comment: /* TODO(cra-to-vite): replace with import.meta.env.MODE */. Do not replace them automatically — MODE and NODE_ENV have slightly different value sets.
  10. Address CSS Modules incompatibilities surfaced in the inventory from Step 4:

    • If the inventory reports any :local() or :global() pseudo-selectors: confirm Vite's CSS Modules implementation handles them identically. Log any divergence.
    • If any test file imports a .module.css and asserts on class name strings (e.g. expect(el.className).toBe('Button__root--abc12')): update the expected string to match css_modules_local_ident pattern, or convert the assertion to toHaveClass which is class-name-format-agnostic.
  11. Port the Jest test configuration to Vitest. Read <repo_root>/package.json jest key (or jest.config.js if present). For each Jest configuration field, apply the Vitest equivalent:

    • moduleNameMapperresolve.alias in vite.config.ts (already handled for path aliases in Step 6); CSS module mappers (identity-obj-proxy) are replaced by css: true in the Vitest config.
    • transform → handled by Vite's plugin pipeline; remove unless there is a non-standard transform.
    • setupFilesAfterFramework / setupFiles → already mapped to vitest_setup_file in vite.config.ts.
    • testEnvironment: 'jsdom' → already set in vite.config.ts.
    • globals mock patterns using jest.mock(...): audit for patterns that use jest.fn() or jest.spyOn() — these work unchanged in Vitest with globals: true. Patterns that use jest.resetModules() or jest.isolateModules() require vi.resetModules() / vi.isolateModules() — find and replace.
    • Write a diff of every Jest→Vitest config change to the migration log.
  12. Run the Vitest suite: npx vitest run from <repo_root>. Record full output and exit code as vitest_result.

    • If exit code is non-zero: read the failure output. For each failing test:
      • If failure is vi.fn is not a function or similar: the test uses a Jest global not shimmed — add import { vi } from 'vitest' at the top of that test file.
      • If failure is a CSS class name assertion mismatch: update the assertion per Step 10 guidance.
      • If failure is a module resolution error (Cannot find module '@/...'): the alias from Step 3 is missing or malformed in vite.config.ts — re-read the alias map and fix.
      • Fix the minimum required to make the suite pass. Do not refactor test logic.
    • Do not proceed until vitest_result exit code is 0.
  13. Run the Vite build: npm run build:vite from <repo_root>. Confirm it exits 0 and writes artifacts to build-vite/.

    • If this fails: read the Rollup error. Common causes:
      • require is not defined — a CommonJS dependency is not pre-bundled; add it to optimizeDeps.include in vite.config.ts.
      • Dynamic require() calls in source — replace with await import().
      • Missing index.html entry reference — re-check Step 7.
    • Run npm run build (CRA) in the same CI step to produce the baseline build/ artifact. Both must succeed before Step 14.
  14. Compare bundle output sizes. Run from <repo_root>:

    du -sh build/ build-vite/
    find build/static/js -name '*.js' | xargs wc -c | sort -n
    find build-vite/static/js -name '*.js' | xargs wc -c | sort -n
    

    Write the comparison to the migration log. A Vite bundle that is more than 20% larger than the CRA bundle (after gzip) warrants investigation before cut-over — log it as a warning, not a hard failure, unless the team has set a size budget.

  15. Deploy the Vite build artifact (build-vite/) to staging behind the feature flag (feature_flag_key = true). Run Lighthouse against <staging_url> with the Vite artifact serving:

    npx lighthouse <staging_url> --output json --output-path output/cra-to-vite-lighthouse-post-<timestamp>.json
    

    Compare the categories.performance.score value against the baseline in <lighthouse_baseline>. Compute the delta as (post_score - baseline_score) * 100 (Lighthouse scores are 0–1).

    • If delta < −lighthouse_budget_points: log a warning and continue — this is a soft gate.
    • If delta < −10: this is the hard rollback trigger. Set feature_flag_key = false, write the regression to the migration log, STOP: "Lighthouse performance regressed <N> points (threshold: 10). Vite artifact rolled back. Investigate bundle size and code splitting before retrying."
  16. Write all outputs declared in Section 7. Run every Equivalence Test in Section 6 and record results in output/cra-to-vite-equiv-<timestamp>.md. Evaluate every item in Section 9 Done Criteria; report pass/fail inline, then print the final verdict.


5. Agent Handoffs

code-archaeologist

  • File: agents/code-archaeologist.md
  • Triggered by: Step 4
  • Prompt template:
    TASK: Inventory all CSS Modules usage in the repository. For each .module.css or
          .module.scss file found in SCOPE, report:
            - File path and number of class names defined
            - Whether any class names are referenced as string literals in test files
              (e.g. expect(el.className).toBe('...')) — list file path and line number
            - Whether :local() or :global() pseudo-selectors are used anywhere
            - Whether the file is imported in a .tsx/.jsx file that also has Snapshot tests
              (class name changes will break snapshots)
          Separately, list every file that imports 'identity-obj-proxy' — these are Jest
          CSS mock configurations that need to be removed under Vitest.
    REPO_ROOT:   <repo_root>
    SCOPE:       <repo_root>/src
    OUTPUT_FILE: output/cra-to-vite-css-inventory-<timestamp>.md
    FORMAT:      markdown
    

6. Equivalence Tests

<!-- Tests are run in Step 16. Results written to output/cra-to-vite-equiv-<timestamp>.md. "CRA baseline" refers to the build artifact in build/ and the Jest result from the pre-migration test run. -->
Test NameInputExpected OutputTool
vitest-suitenpx vitest run from <repo_root> (Step 12 result)Exit code 0. Passed/failed/skipped counts are identical to the pre-migration Jest run (npm test -- --watchAll=false). A count divergence is a fail even if exit code is 0.Bash — captured in Step 12 as vitest_result.
vite-build-exits-0npm run build:vite from <repo_root> (Step 13 result)Exit code 0; build-vite/index.html exists and references at least one JS chunk in build-vite/static/js/.Bash — captured in Step 13.
entry-points-presentgrep -r '<script' build-vite/index.html and grep -r '<link rel="stylesheet"' build-vite/index.htmlAt least one <script type="module"> and one <link rel="stylesheet"> present — Vite bundle is wired into the HTML entry point.Bash
env-vars-renamedgrep -rn 'REACT_APP_' <repo_root>/src/No matches. All REACT_APP_ references replaced with VITE_ equivalents.Bash
no-process-env-react-appgrep -rn 'process\.env\.REACT_APP_' <repo_root>/src/No matches. All usages migrated to import.meta.env.VITE_*.Bash
lighthouse-perfLighthouse JSON at output/cra-to-vite-lighthouse-post-<timestamp>.json, categories.performance.score fieldScore does not regress more than <lighthouse_budget_points> points vs. <lighthouse_baseline>. Compute as (post − baseline) × 100; must be ≥ −<lighthouse_budget_points>.Bash: node -e "const b=require('<lighthouse_baseline>').categories.performance.score; const p=require('output/...').categories.performance.score; const d=(p-b)*100; process.exit(d < -<lighthouse_budget_points> ? 1 : 0)"
no-cra-artifacts-in-vite-outputgrep -r 'react-scripts' build-vite/No matches — the Vite build output must not reference CRA tooling.Bash

7. Outputs

ArtifactPath PatternFormatDescription
Env var rename mapoutput/cra-to-vite-env-map-<timestamp>.jsonjsonArray of {old, new} pairs mapping REACT_APP_*VITE_*; consumed in Step 9 and used as evidence in env-vars-renamed equivalence test.
Alias mapoutput/cra-to-vite-alias-map-<timestamp>.jsonjsonVite resolve.alias objects derived from tsconfig paths; consumed in Step 6 when writing vite.config.ts.
CSS inventoryoutput/cra-to-vite-css-inventory-<timestamp>.mdmarkdownProduced by code-archaeologist; lists CSS Modules files, class name string assertions in tests, and identity-obj-proxy usage. Consumed in Step 10.
Migration logoutput/cra-to-vite-log-<timestamp>.mdmarkdownChronological record of every change made (env renames, alias additions, Jest→Vitest config diffs, bundle size comparison), manual-review flags, and the final assumptions list and confidence level. Consumed by /validate and the PR reviewer.
Lighthouse post reportoutput/cra-to-vite-lighthouse-post-<timestamp>.jsonjsonRaw Lighthouse JSON captured in Step 15 after deploying the Vite artifact to staging. Performance score diff is computed against <lighthouse_baseline>.
Equivalence test resultsoutput/cra-to-vite-equiv-<timestamp>.mdmarkdownPass/fail verdict for every row in Section 6 with raw command output attached. Required by Section 9 Done Criteria.

8. References

  • references/strangler-fig-pattern.md — the parallel-run + feature flag mechanism used in Steps 8 and 15 is the traffic-shifting pattern described here.
  • references/migration-anti-patterns.md — "Migrating Without a Seam" (§2): the feature flag is the seam; confirm it exists before starting. "Skipping Equivalence Validation" (§3): the Lighthouse gate is non-negotiable.
  • references/stack-compatibility-matrix.md — "Framework Compatibility: CRA → Vite" row (add once assessed).
  • skills/03-prepare/ — CI parallelism, feature flag provisioning, and staging environment setup must be complete before this skill runs.
  • skills/04-migrate/frontend/js-to-typescript/ — complete the TypeScript migration before this skill if the repo has mixed .js/.ts files; Vite's default config handles .tsx cleanly but mixed-module CRA projects have edge cases.
  • skills/05-validate/ — run /validate after cut-over to confirm the Vite artifact behaves identically to CRA in production traffic.
  • https://vitejs.dev/guide/migration.html — official Vite migration notes; consult for CommonJS interop and optimizeDeps configuration when Step 13 fails.
  • https://vitest.dev/guide/migration.html — official Jest→Vitest migration guide; the vi.* API mapping table is the authoritative reference for Step 11.

9. Done Criteria

<!-- Claude evaluates each item and reports pass/fail before declaring this skill complete. Any unchecked item means the skill is NOT complete. Skill-specific gates (1–10) are above the mandatory universal gates (11–15). -->
  • react-scripts is absent from package.json dependencies and devDependenciesgrep '"react-scripts"' <repo_root>/package.json returns no matches. (This gate applies only after the feature flag has been flipped to Vite permanently and the CRA scripts have been removed in the cleanup commit.)
  • scripts.build in package.json is "vite build"grep '"build":' <repo_root>/package.json shows vite build, not react-scripts build.
  • CI runs only vite build; the build:vite script alias and the react-scripts build step have been removed from the CI pipeline config — read the CI config file (Makefile / .github/workflows/*.yml / Jenkinsfile) and confirm.
  • All REACT_APP_ prefixes are gone from src/grep -rn 'REACT_APP_' <repo_root>/src/ returns no matches. Also confirmed by env-vars-renamed and no-process-env-react-app equivalence tests.
  • No CRA config files remain: config/ directory does not exist; react-app-env.d.ts (if present) has been replaced with /// <reference types="vite/client" /> in src/vite-env.d.ts — verify with a glob check.
  • vitest-suite equivalence test recorded as pass — Vitest test counts match the pre-migration Jest baseline exactly.
  • vite-build-exits-0 equivalence test recorded as pass — build artifact exists at build-vite/index.html.
  • entry-points-present equivalence test recorded as pass — HTML entry has <script type="module"> and a stylesheet link.
  • lighthouse-perf equivalence test recorded as pass — Lighthouse Performance score does not regress more than <lighthouse_budget_points> points.
  • no-cra-artifacts-in-vite-output equivalence test recorded as passgrep -r 'react-scripts' build-vite/ returns no matches.
  • All output files listed in Section 7 exist at their declared paths — verify each with a file read.
  • Every equivalence test in Section 6 has a recorded result in output/cra-to-vite-equiv-<timestamp>.md — no test name is missing from the results file.
  • No equivalence test in Section 6 is recorded as fail — grep the results file for fail; zero matches required.
  • The migration log includes a confidence level (High / Medium / Low) — grep output/cra-to-vite-log-<timestamp>.md for Confidence:.
  • The migration log includes a numbered assumptions list — grep output/cra-to-vite-log-<timestamp>.md for Assumptions:.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.