Skip to content

Instantly share code, notes, and snippets.

@vegaasen
Last active April 30, 2022 06:19
Show Gist options
  • Save vegaasen/57b9aba83bf7977935f7d0976f8c82f8 to your computer and use it in GitHub Desktop.
Save vegaasen/57b9aba83bf7977935f7d0976f8c82f8 to your computer and use it in GitHub Desktop.
Retrofitting Vite in an React (CRA) application

Retrofitting Vite in an React (CRA) application

πŸ‘‹ hewwoww

This is just a quick introduction to migrating an existing web-app from a CRA => Vite. It doesn't solve all the problems you may have, but it gives a breif introduction to how to migrate. Also added a few points on stuff that went sideways for us whilst performing an migration πŸ€ͺ.

🧾 Assumptions

This assumes the following:

  • You're using CRA
  • You're on latest CRA and React
  • You're using typescript

🍻 Migration

1. Change public/index.html

Two things.

  1. Remove the %PUBLIC_URL% found in your index.html-file. Example:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
// to 
<link rel="icon" href="/favicon.ico"/>
  1. Include a vite-specific module script after your root-element:
<script type="module" src="/src/index.tsx"></script>
  1. Move the file index.html => root of the project :-).

2. Stir things up in package.json!

You, of course, need to adjust your dependencies and package.json a tad bit. This shows how to!

  1. Remove the package-lock.json-file

  2. Install some dependencies

npm install vite-plugin-env-compatible
npm install -D @babel/preset-typescript @types/react-router-dom @vitejs/plugin-react esbuild-jest jest vite vite-plugin-svgr jest-junit
  1. Adjust scripts
 "scripts": {
    "start": "vite --host",
    "build": "vite build",
    "test": "jest",
    "test-coverage": "jest --coverage --no-cache",
    "test-ci": "CI=true jest --coverage --no-cache --reporters=default --reporters=jest-junit"
  },
  1. Include configuration for jest and babel
  "jest": {
    "roots": [
      "<rootDir>/src"
    ],
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}",
      "!src/**/*.d.ts"
    ],
    "setupFilesAfterEnv": [
      "<rootDir>/src/setupTests.ts"
    ],
    "testMatch": [
      "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
    ],
    "testEnvironment": "jsdom",
    "transform": {
      "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": [
        "esbuild-jest",
        {
          "sourcemap": true
        }
      ]
    },
    "transformIgnorePatterns": [
      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$"
    ],
    "moduleNameMapper": {
      "../../assets/size/.*": "<rootDir>/mock/staticFileMock.js"
    },
    "resetMocks": true
  },
  "babel": {
    "presets": [
      "@babel/preset-typescript"
    ]
  }
  1. Create new file {project.dir}/mock/staticFileMock.js with content (will be used during test):
module.exports = 'test-file-stub';
  1. Remove dependencies react-scripts and ts-jest dependency in package.json (if present)!

3. Create a vite.config.ts-file. Getting close!

import react from '@vitejs/plugin-react'
import {defineConfig} from 'vite'
import envCompatible from 'vite-plugin-env-compatible';
import svgrPlugin from 'vite-plugin-svgr'

export default defineConfig({
  build: {
    outDir: 'build',
  },
  server: {
    open: "/my-app-path",
  },
  plugins: [
    react(),
    svgrPlugin(),
    envCompatible(),
  ],
})

4. Adjust tsconfig.json to allow Vite to function!

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "types": ["vite/client", "vite-plugin-svgr/client"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "noImplicitAny": false,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ],
  "exclude": [
    "src/**/*.test.tsx"
  ]
}

5. Typescriptify setupTests.js

Rename the setupTests.js to setupTests.ts (+ adjusting if required).

6. πŸŽ‰ Done! D - O - N - E πŸŽ‰

Or, hopefully. Something didn't work as intended, check below πŸ€ͺ.

😬πŸ’₯ Problems & what not!

Some issues with import React from 'react'

We discovered some oddities with import React from 'react' in tests. Moving to import * as React from 'react' solves this issue.

React 17 allows for no React declare. But this fails?

Yeah, no idea why. However, just slap that import of React in the affected component, and you're done.

import React from 'react';

πŸ€” What do I do with..?

I'm not totally over to Typescript yet from *.js(x)-files!

No worries! Vite will function with non-ts and/or some-ts projects. Just include typescript as a dependency:

npm install -D typescript

And rename, if you have some files with jsx within them, from *.js => *.jsx, and you're mostly done.

Do I have to?

Yes, otherwise you may see this error:

The JSX syntax extension is not currently enabled

My middleware setting for setupProxy.js?

Whilst using CRA, you get a nice option of just specifying a setupProxy.js-file in your src-folder, add a few lines of configuration for som get/post/whatever paths, and you're already off by allowing for mocked responses or proxied responses. Quite amazing!

In Vite, this does not work by just including Vite, as its a CRA thing. However, its ΓΌber-easy to fix. Here is how to just that.

Add express as a devDependency

npm install -D express

setupProxy.js adjustments

  1. Locate your setupProxy.js-file and add const express = require('express') to the file. Then, create a new instance of express using: const app = express(). Export this constant directly:
module.exports = app

You should end up with something like:

const express = require('express')
const {createProxyMiddleware} = require('http-proxy-middleware');
const bodyParser = require('body-parser')
const useRealEnvironment = false
const app = express()
if (useRealEnvironment) {
  app.use(createProxyMiddleware('/some/path', {target: 'https://some-host.com/', changeOrigin: true}));
} else {
  app.get('/some/path/api/user', (req, res) => res.json("{user: 'its you!'}"))
}
app.use(bodyParser.json())
console.log('βœ… express server ready')
module.exports = app

vite.config.ts adjustments

  1. In the vite.config.ts file add the following plugin:
const expressServerPlugin = (path, expressApp) => ({
  name: 'configure-server',
  configureServer(server) {
    server.middlewares.use(path, expressApp);
  }
});
  1. And use the plugin as follows (add it within the plugins: [..]-section) as follows:
plugins: [
  ...,
  expressServerPlugin("/", app),
  ...
]
  1. Where does the app come from?! This you just imported from your updated setupProxy.js. E.g:
import app from './mock/setupProxy'

Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment