# CRA migration Vite(v4.1.1) ## 1. Remove Create React App - uninstall packages ```bash npm uninstall react-scripts ``` - remove package.json entries: **package.json** ```json "scripts": { "start": "react-scripts start", "build": "react-scripts build", }, ``` ## 2. Install Vite ```bash npm install -D vite @vitejs/plugin-react ``` ### Update `tsconfig` (path aliasing included) **tsconfig.json** ```json { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "paths": { "~/*": ["./src/*"] }, "baseUrl": "." }, "include": ["./src"] } ``` ### Update `package.json` ```json "scripts": { "dev": "vite", "dev:clean": "npm run dev -- --force", "build": "tsc && vite build", "serve": "vite preview --port 3000", } ``` ### Create config file Add **vite.config.ts** at the root of your project ```typescript import path from "path"; import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { createHtmlPlugin } from 'vite-plugin-html' const ENV_PREFIX = 'REACT_APP_' // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, 'env', ENV_PREFIX) return { root: './', plugins: [ react({ babel: { plugins: [ [ 'babel-plugin-styled-components', { displayName: true, fileName: false, }, ], ], }, }), createHtmlPlugin({ minify: true, /** * Data that needs to be injected into the index.html ejs template */ inject: { data: { title: 'index', injectScript: '<script src="./inject.js"></script>', }, tags: [ { injectTo: 'body-prepend', tag: 'div', attrs: { id: 'tag', }, }, ], }, }), envCompatible({ prefix: ENV_PREFIX }), ], resolve: { alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], }, server: { port: 3000, open: env.SERVER_OPEN_BROWSER === 'true', }, base: '/', build: { outDir: 'build', assetsDir: 'assets', }, } }) ``` ## 3. Environment variables Vite handles environment variables in a different way than CRA, [Read here](https://vitejs.dev/guide/env-and-mode.html), to avoid complete refactoring and preserve backwards-compatibilty we can use a custom [plugin](https://github.com/IndexXuan/vite-plugin-env-compatible). ```bash npm install -D vite-plugin-env-compatible ``` **vite.config.ts** ```typescript import envCompatible from "vite-plugin-env-compatible"; const ENV_PREFIX = "REACT_APP_"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { plugins: [envCompatible({ prefix: ENV_PREFIX })], }; }); ``` ## 4. Static analysis By default, type checking & linting in CRA was built into dev server, we can implement this in Vite with a [plugin](https://github.com/fi3ework/vite-plugin-checker). I will omit running `eslint` here since it can pollute terminal & browser console output with many warnings, it's also pretty slow in larger codebases. Additional linting can be handled by IDE setup, git hooks & PR checks (e.g. github actions). ```bash npm install -D vite-plugin-checker ``` **vite.config.ts** ```typescript import checker from "vite-plugin-checker"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { plugins: [ checker({ overlay: false, typescript: true, }), ], }; }); ``` ## 5. Babel plugins We were adding custom babel plugins to CRA webpack setup via [craco](https://github.com/gsoft-inc/craco). This is how we can handle it in Vite (example includes usage of [Styled Components plugin](https://styled-components.com/docs/tooling#babel-plugin)): ```typescript // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { plugins: [ react({ babel: { plugins: [ [ "babel-plugin-styled-components", { displayName: true, fileName: false, }, ], ], }, }), ], }; }); ``` ## 6. SVG Components CRA allows to transform `.svg` files into React components. ```tsx import { ReactComponent as Logo } from "./logo.svg"; ``` In order to make this work with Vite we will need a [plugin](https://github.com/pd4d10/vite-plugin-svgr) ```bash npm install -D vite-plugin-svgr ``` ```typescript import svgrPlugin from "vite-plugin-svgr"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { plugins: [ svgrPlugin({ svgrOptions: { icon: true, // ...svgr options (https://react-svgr.com/docs/options/) }, }), ], }; }); ``` You also might need to add additional declarations file to satisfy TypeScript. Create `src/custom.d.ts`: ```typescript declare module "*.svg" { import React = require("react"); export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>; const src: string; export default src; } ``` ## 7. Index.html - Move `index.html` from `/public` to the root of the project - Remove any `%PUBLIC_URL%` references from the file ```html <!-- Before --> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <!-- After --> <link rel="icon" href="./favicon.ico" /> ``` - Add Entry script tag to 'index.html' ```html <!-- Add to body --> <script type="module" src="./src/index.tsx"></script> ``` - Embedding JavaScript: CRA supported [ejs](https://ejs.co/) out of the box, we will need additional [plugin for Vite](https://github.com/anncwb/vite-plugin-html): ```bash npm install -D vite-plugin-html ``` **vite.config.ts** ```typescript import html from "vite-plugin-html"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { plugins: [ createHtmlPlugin({ minify: true, /** * Data that needs to be injected into the index.html ejs template */ inject: { data: { title: 'index', injectScript: '<script src="./inject.js"></script>', }, tags: [ { injectTo: 'body-prepend', tag: 'div', attrs: { id: 'tag', }, }, ], }, }), ], }; }); ``` ## 8. Additional features ### Open tab when server starts Add possibility to configure opening a new tab when dev server is launched (it was turned on by default in CRA): **.env** ```bash SERVER_OPEN_BROWSER=true ``` **vite.config.ts** ```typescript import { defineConfig, loadEnv } from "vite"; const ENV_PREFIX = ["REACT_APP_", "SERVER"]; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, "env", ENV_PREFIX); return { server: { port: 3000, open: env.SERVER_OPEN_BROWSER === "true", }, }; }); ```