ReferenceError: React is not defined - 从 CRA 迁移到 Vite 和 NX

分享于2022年07月17日 javascript nrwl-nx reactjs typescript vite 问答
【问题标题】:ReferenceError: React is not defined - 从 CRA 迁移到 Vite 和 NX(ReferenceError: React is not defined - Migrating from CRA to Vite and NX)
【发布时间】:2022-01-27 20:17:33
【问题描述】:

我目前正在将 create-react-app (CRA - v4) monorepo Webpack 设置迁移到由 vite 提供支持的 NX Monorepo。

我目前正试图弄清楚如何解决典型问题

Uncaught ReferenceError: React is not defined

当一个文件不直接导入 React,但有一个命名的导入时,就会发生这种情况,例如:

import { memo } from 'react';

我已经运行了删除所有 import React 语句的 linter,而且我会害怕通过成百上千个文件再次添加它。

这里有更多信息:

  • 我相信我正在使用最新的 JSX 转换和 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, (更多内容在 package.json 中)

我还反复阅读了 GitHub、SO 和网络上的几个资源,但没有找到任何东西

这是我的 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,
  },
});

这是我的 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"]
}

还有基础的 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
      }
    }
  }
}

这是给我带来问题的文件之一(向我抛出 React 未定义): (这个来自故事书正在处理的组件存储库)

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 = (props) => (
  
    {props.children!}
  
);

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 = 

( ComponentToContainer: React.ComponentType

) => class WithContainer extends React.PureComponent

{ render() { return ( ); } }; 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"
  }
}

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"
}


【解决方案1】:

我发现了问题。显然有一段时间我删除了所有 import React 语句。但请注意,如果您不销毁 Children、Fragment 或其他内容,则不能盲目地删除它。

例如:

如果你有:

import React, {memo} from 'react';

const MyElem = () => {
  const myMemo = useMemo(() => {}, []);
  return (
       
       
  );
}

如果你删除了 React ,你将拥有 Fragment 的依赖。

你必须导入它,或者破坏 Fragment

意思是,你必须这样做:

import {Fragment, memo} from 'react';

const MyElem = () => {
  const myMemo = useMemo(() => {}, []);
  return (
       
       
  );
}

  • 很高兴您找到了解决方案。作为@nxext 的开发者之一。我可以确认 NX 引入的标准是使用 tsconfig "jsx": "react-jsx", 自动注入 React 因此我们为 vite builder 遵循了这个标准。如果人们想手动导入 react 那么你可以从 tsconfig 中删除 JSX 行
  • 噢噢噢噢。很高兴知道乔丹!太感谢了。 Nx 太不可思议了!感谢团队付出的所有辛勤工作!!!