I’m currently in the process of migrating a create-react-app (CRA – v4) monorepo Webpack setup to an NX Monorepo powered by vite.
I’m currently stuck in trying to figure out how to solve the typical
Uncaught ReferenceError: React is not defined
Which happens whenever a file doesn’t import React directly, but has a named import from it, such as:
import { memo } from 'react';
I’ve ran the linter that removed all the import React
statements, and I’d be daunting to through hundreds and hundreds of files to add it again.
Here’s more info:
- I believe I’m using the newest JSX transform and React 17.
- nx-plugin-vite: ^1.1.0
- vite: ^2.7.1
- vite-tsconfig-paths: ^3.3.17
- vite-plugin-eslint: ^1.3.0
- @vitejs/plugin-react: ^1.1.3
- @nrwl/react”: 13.2.4,
(More in the package.json)
I’ve also read and read again several sources across GitHub, SO, and the web and haven’t found anything
- https://javascript.plainenglish.io/migrating-a-150k-loc-codebase-to-vite-and-esbuild-how-part-2-3-91b0b873f388
- https://github.com/nordcloud/pat-frontend-template/blob/master/docs/CRA_MIGRATION_GUIDE.md#update-tsconfig-path-aliasing-included
- https://darekkay.com/blog/create-react-app-to-vite/
Here’s my vite.config.ts
import path from 'path';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import eslintPlugin from 'vite-plugin-eslint';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths(), eslintPlugin(), react()],
resolve: {
alias: {
stream: 'stream-browserify',
'~': path.resolve(__dirname, 'src'),
},
},
server: {
open: true,
},
});
Here’s my tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": true,
"skipLibCheck": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"noFallthroughCasesInSwitch": true
},
"include": ["./src"]
}
And the base tsconfig.json:
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"isolatedModules": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@schon/legacy-components/*": ["apps/app/src/components/*"],
"@schon/graphql/*": ["apps/app/src/graphql/*"],
"@schon/hooks/*": ["libs/components/src/hooks/*"],
"@schon/components/*": ["libs/components/src/ad/*"],
"@schon/legacy-components2/*": ["libs/components/src/stories/*"],
"@schon/theme": ["libs/components/src/styles/index.ts"],
"@schon/typings": ["libs/components/src/typings/index.ts"],
"@schon/utils": ["libs/components/src/utils/index.ts"],
"~/*": ["apps/app/src/*"]
}
},
"exclude": ["node_modules", "tmp"]
}
project.json:
{
"root": "apps/app",
"projectType": "application",
"sourceRoot": "apps/app/src",
"tags": [],
"targets": {
"serve": {
"executor": "nx-plugin-vite:serve",
"options": {
"configFile": "apps/app/vite.config.ts",
"port": 3001,
"host": false,
"https": false
}
},
"preview": {
"executor": "nx-plugin-vite:preview",
"options": {
"configFile": "apps/app/vite.config.ts"
}
},
"build": {
"executor": "nx-plugin-vite:build",
"options": {
"outDir": "dist",
"configFile": "apps/app/vite.config.ts",
"watch": false,
"write": true,
"emitAtRootLevel": false,
"manifest": true
}
}
}
}
Here’s one of the files that is giving me problems (Throws me the React is not defined):
(This one comes from a component repo that is being handled by storybook)
import { memo } from 'react';
import { Container as MaterialContainer } from '@material-ui/core';
import { ThemeSpecs } from '../../../styles/theme';
type ContainerProps = {
children?:
| JSX.Element
| JSX.Element[]
| React.ReactNode
| React.ReactChildren;
className?: string;
};
/**
* Jose decided to wrap this up, in case we needed to apply a general styling to the container
* itself, and avoid repeating it in every other component.
*/
const Component: React.FC<ContainerProps> = (props) => (
<MaterialContainer
className={props.className}
fixed
style={{ paddingTop: ThemeSpecs.container.paddingTop }}
>
{props.children!}
</MaterialContainer>
);
type withContainerProps = {};
/**
* This is a HOC so we can use this to Containerize the imports back
* at root. This way we can choose which routes use Containers
* and which don't.
*/
export const withContainer = <P extends object>(
ComponentToContainer: React.ComponentType<P>
) =>
class WithContainer extends React.PureComponent<P & withContainerProps> {
render() {
return (
<Container>
<ComponentToContainer {...this.props} />
</Container>
);
}
};
export const Container = memo(Component) as typeof Component;
package.json
{
"scripts": {
"start": "nx serve",
"build": "nx build",
"test": "nx test"
},
"private": true,
"dependencies": {
"@apollo/client": "^3.5.6",
"@auth0/auth0-react": "^1.8.0",
"@aws-sdk/client-s3": "^3.44.0",
"@date-io/date-fns": "^2.11.0",
"@material-table/core": "^4.3.11",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@material-ui/pickers": "^3.3.10",
"@material-ui/system": "^4.12.1",
"@nivo/calendar": "^0.74.0",
"@nivo/core": "^0.74.0",
"@nivo/line": "^0.74.0",
"@nivo/tooltip": "^0.74.0",
"@reach/router": "^1.3.4",
"auth0-js": "^9.18.0",
"aws-appsync-auth-link": "^3.0.7",
"aws-appsync-subscription-link": "^3.0.9",
"aws-sdk": "^2.1046.0",
"clsx": "^1.1.1",
"core-js": "^3.6.5",
"d3-array": "^3.1.1",
"date-fns": "^2.27.0",
"dotenv": "^10.0.0",
"exceljs": "^4.3.0",
"file-saver": "^2.0.5",
"formik": "^2.2.9",
"formik-persist": "^1.1.0",
"framer-motion": "^5.4.5",
"fraql": "^1.2.1",
"graphql": "^16.1.0",
"husky": "^7.0.4",
"immer": "^9.0.7",
"linkifyjs": "^3.0.4",
"lodash": "^4.17.21",
"logrocket": "^2.1.2",
"material-table": "^1.69.3",
"msw": "^0.36.3",
"password-validator": "^5.2.1",
"randomcolor": "^0.6.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-dropzone-uploader": "^2.11.0",
"react-elastic-carousel": "^0.11.5",
"react-error-boundary": "^3.1.4",
"react-google-docs-viewer": "^1.0.1",
"react-icons": "^4.3.1",
"react-intersection-observer": "^8.32.5",
"react-lazy-load-image-component": "^1.5.1",
"react-loading-skeleton": "^3.0.1",
"react-prerendered-component": "^1.2.4",
"regenerator-runtime": "0.13.7",
"stream-browserify": "^3.0.0",
"styled-components": "^5.3.3",
"suneditor": "^2.41.3",
"suneditor-react": "^3.3.1",
"sw-precache": "^5.2.1",
"tiny-slider-react": "^0.5.3",
"tslib": "^2.0.0",
"use-debounce": "^7.0.1",
"uuid": "^8.3.2",
"validate-password": "^1.0.4",
"yup": "^0.32.11"
},
"devDependencies": {
"@angular-devkit/schematics": "^13.0.4",
"@babel/core": "7.12.13",
"@babel/preset-typescript": "7.12.13",
"@nrwl/cli": "13.2.4",
"@nrwl/cypress": "13.2.4",
"@nrwl/eslint-plugin-nx": "13.2.4",
"@nrwl/jest": "13.2.4",
"@nrwl/linter": "13.2.4",
"@nrwl/node": "^13.2.4",
"@nrwl/nx-cloud": "latest",
"@nrwl/react": "13.2.4",
"@nrwl/storybook": "^13.3.0",
"@nrwl/tao": "^13.2.4",
"@nrwl/web": "13.2.4",
"@nrwl/workspace": "^13.2.4",
"@nxext/react": "^13.0.0",
"@snowpack/plugin-dotenv": "^2.2.0",
"@snowpack/plugin-react-refresh": "^2.5.0",
"@snowpack/plugin-typescript": "^1.2.1",
"@snowpack/web-test-runner-plugin": "^0.2.2",
"@storybook/addon-actions": "^6.4.9",
"@storybook/addon-essentials": "~6.3.0",
"@storybook/addon-knobs": "^6.4.0",
"@storybook/addon-links": "^6.4.9",
"@storybook/addon-storysource": "^6.4.9",
"@storybook/builder-webpack5": "~6.3.0",
"@storybook/manager-webpack5": "~6.3.0",
"@storybook/react": "~6.3.0",
"@svgr/webpack": "^5.4.0",
"@testing-library/react": "12.1.2",
"@testing-library/react-hooks": "7.0.2",
"@types/auth0-js": "^9.14.5",
"@types/chai": "^4.2.21",
"@types/jest": "27.0.2",
"@types/mocha": "^9.0.0",
"@types/node": "14.14.33",
"@types/react": "17.0.30",
"@types/react-dom": "17.0.9",
"@types/react-lazy-load-image-component": "^1.5.2",
"@types/snowpack-env": "^2.3.4",
"@types/tiny-slider-react": "^0.3.3",
"@types/uuid": "^8.3.3",
"@types/yup": "^0.29.13",
"@typescript-eslint/eslint-plugin": "~4.33.0",
"@typescript-eslint/parser": "~4.33.0",
"@vitejs/plugin-react": "^1.1.3",
"@web/test-runner": "^0.13.17",
"babel-jest": "27.2.3",
"babel-loader": "8.1.0",
"chai": "^4.3.4",
"cypress": "^8.3.0",
"eslint": "7.32.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-cypress": "^2.10.3",
"eslint-plugin-import": "2.25.2",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-hooks": "4.2.0",
"jest": "27.2.3",
"nx-plugin-snowpack": "^0.3.0",
"nx-plugin-vite": "^1.1.0",
"prettier": "^2.3.1",
"react-test-renderer": "17.0.2",
"snowpack": "^3.8.8",
"storybook-theme-toggle": "^0.1.2",
"ts-jest": "27.0.5",
"typescript": "~4.4.3",
"url-loader": "^3.0.0",
"vite": "^2.7.1",
"vite-plugin-eslint": "^1.3.0",
"vite-preset-react": "^2.2.0",
"vite-tsconfig-paths": "^3.3.17"
}
}
The nx.json:
{
"npmScope": "schon",
"affected": {
"defaultBase": "main"
},
"cli": {
"defaultCollection": "@nrwl/react"
},
"implicitDependencies": {
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
".eslintrc.json": "*"
},
"tasksRunnerOptions": {
"default": {
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"],
}
}
},
"targetDependencies": {
"build": [
{
"target": "build",
"projects": "dependencies"
}
]
},
"generators": {
"@nrwl/react": {
"application": {
"style": "css",
"linter": "eslint",
"babel": true
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
}
},
"defaultProject": "app"
}