参考错误:未定义$RefreshReg$

9
在一个基于 CRA 的 React 应用程序中,当我在 webworker 文件中导入也被普通应用程序代码(normal === 非 worker)导入的文件并使用 babel 开发服务器以调试模式运行应用程序时,我会遇到这个错误(所有代码都使用 TypeScript)。
搜索此错误会带我进入各种特定于模块的问题报告/解决方案,但我无法使用它们。但很明显,这是一个 React 热重载的问题,我想知道如何解决它,因为它阻止了我在 Web Workers 中使用我的应用程序代码。
我的开发依赖项如下:
    "devDependencies": {
        "@babel/core": "^7.12.10",
        "@babel/preset-env": "^7.11.11",
        "@babel/preset-typescript": "^7.12.7",
        "@testing-library/react": "^11.2.3",
        "@types/classnames": "^2.2.11",
        "@types/color": "^3.0.1",
        "@types/d3": "^6.2.0",
        "@types/history": "4.7.8",
        "@types/jest": "^26.0.20",
        "@types/lodash": "^4.14.168",
        "@types/node": "^14.14.22",
        "@types/react": "^17.0.0",
        "@types/react-dom": "^17.0.0",
        "@types/react-test-renderer": "^17.0.0",
        "@types/react-window": "^1.8.2",
        "@types/resize-observer-browser": "^0.1.5",
        "@types/selenium-webdriver": "^4.0.11",
        "@types/topojson": "^3.2.2",
        "@types/uuid": "^8.3.0",
        "@types/ws": "^7.4.0",
        "@typescript-eslint/eslint-plugin-tslint": "^4.14.0",
        "acorn": "^8.0.5",
        "antlr4ts-cli": "^0.5.0-alpha.4",
        "eslint": "^7.18.0",
        "eslint-plugin-flowtype": "^5.2.0",
        "eslint-plugin-import": "^2.22.1",
        "eslint-plugin-jsdoc": "^31.3.3",
        "eslint-plugin-jsx-a11y": "^6.4.1",
        "eslint-plugin-prefer-arrow": "^1.2.2",
        "eslint-plugin-react": "^7.22.0",
        "eslint-plugin-react-hooks": "^4.2.0",
        "identity-obj-proxy": "^3.0.0",
        "jest": "26.6.0",
        "jest-transform-stub": "^2.0.0",
        "jest-websocket-mock": "^2.2.0",
        "mock-socket": "^9.0.3",
        "monaco-editor-webpack-plugin": "^1.9.1",
        "monaco-typescript": "^4.2.0",
        "raw-loader": "^4.0.2",
        "react-app-rewired": "^2.1.8",
        "react-scripts": "^4.0.3",
        "react-test-renderer": "^17.0.1",
        "selenium-webdriver": "^4.0.0-beta.1",
        "ts-jest": "^26.5.2",
        "tslint": "^6.1.3",
        "typescript": "^4.1.3",
        "typescript-eslint-parser": "22.0.0",
        "webdriver-manager": "^12.1.8",
        "webpack": "4.44.2",
        "webpack-bundle-analyzer": "^4.4.0",
        "worker-loader": "^3.0.8",
        "ws": "^7.4.3"
    },

我唯一能应用的有前途的解决方案是:https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/176#issuecomment-683150213,但它没有帮助到我。 我仍然遇到同样的错误。 我的Web Worker代码如下:
/* eslint-disable no-restricted-globals */
/* eslint-disable no-eval */
/* eslint-disable @typescript-eslint/no-unused-vars */

(window as any).$RefreshReg$ = () => {/**/};
(window as any).$RefreshSig$ = () => () => {/**/};

import { ShellInterfaceSqlEditor } from "../../supplement/ShellInterface";
import { PrivateWorker, ExecutionResultType, IConsoleWorkerData } from "./console.worker-types";

const backend = new ShellInterfaceSqlEditor();
const ctx: PrivateWorker = self as any;

关于更改babel loader配置的进一步讨论,恐怕我无能为力,因为我自己不进行配置(但我正在使用react-app-rewired来配置一些自定义loader)。

还有什么其他方法可以尝试修复这个问题吗?


嗨Mike,你找到了解决方案吗? - AmerllicA
如果我有的话,答案就在这里了。我目前所做的是避免混合某些内容,也就是说,既被 WebWorker 使用又被主应用程序使用的文件必须只包含简单的东西,如接口。这有点限制,但效果还不错。 - Mike Lischke
6个回答

4
在我的情况下,当覆盖Webpack的mode选项时,会出现这个错误。删除下面的行后,我停止收到错误信息。
webpackConfig.mode = 'none';     // this line caused the bug

我认为这是因为react-refresh需要webpack处于mode: 'development'模式才能正常工作。

您可能想阅读有关Webpack模式的内容,可以在此处了解:https://webpack.js.org/configuration/mode/。也有可能您正在使用NODE_ENV环境变量设置Webpack模式。


我可以确认,在我的情况下确实是react-refresh - undefined

1

我尝试了这个答案和各种Github问题中的许多解决方案,但是它们都没有解决我的问题。最终,我通过Webpack的sideEffects优化和配置找到了一个解决方案!

简而言之:

  • 确保您的Webpack配置设置了一个optimization对象,并且optimization对象具有一个属性sideEffects设置为true。似乎nx和潜在的cracraco将其默认设置为false,导致未使用的React组件在WebWorker捆绑包中被导入,如果它们作为“barrel”文件的一部分被导出
  • 如果您正在导入自己的共享UI包,该包导出React组件和一些非React组件,则需要在您的包的package.json文件中指定sideEffects配置,以确保React组件被标记为“纯”的并且安全删除。有关更多详细信息,请参见下文
检查完我的WebWorker的捆绑包后,我意识到一些React组件被从worker-loader打包进了worker的捆绑包中,即使它们在worker中并没有被使用。我们的应用程序中至少出现了这种情况,因为我们有一个共享包,其中包含了一些React组件和其他常规的TS工具。
我们的WebWorker从该包中导入了一个值(命名导出),由于某种原因,整个包都被包括在了WebWorker中,包括那些根本没有被使用的React组件!然后,React Fast Refresh插件(默认安装/使用CRA)通过所需的更改来增强我们的WebWorker捆绑包,以支持热刷新/重新加载(调用$RefreshReg$等)。
关于这个问题,一些Github问题提到了Webpack的一个树摇优化叫做sideEffects,你可以在https://webpack.js.org/guides/tree-shaking/上阅读更多相关信息。下面是相关部分内容:

新的Webpack 4版本通过"sideEffects" package.json属性为编译器提供了提示的方式,以标记项目中哪些文件是"纯净的",因此如果未使用,可以安全地修剪。

在你的包的 package.json 中,你可以指定 "sideEffects": false 来标记整个包为“纯净”的,如果未使用则可以安全地修剪,或者你可以指定具有副作用的特定导出/路径,例如对于我们:
  "sideEffects": [
    "./src/styles/*"
  ],

然而,设置这个值并没有立即为我们的应用程序工作,这让人困惑。最终,我遇到了一个关于 nx 的线程,在该线程中,一位非常有帮助的评论指出,他们的 Webpack 配置禁用了 Webpack 的 sideEffects 优化:(https://github.com/nrwl/nx/issues/9717#issuecomment-1163533981)

我发现 Nx 正在向 webpack 传递 optimization: { sideEffects: false },这明确关闭了树摇,无论您的 package.json 内容如何。在我的原始问题中未被摇掉的库(lodash-es),已经拥有自己的 package.json,其中指定了必要的设置以便实现最佳的树摇效果,但 Nx 在全局范围内关闭了 Webpack 中的树摇,因此它不会发生在项目中的任何库或任何代码中。

我尝试在我的应用程序中显式设置 webpackConfig.optimization.sideEffects = true,问题解决了,React 组件被排除在 WebWorker 包之外。


1
我已经从CRA转向preact、vite.js、esbuild和rollup,所以我无法评估你的答案。但是从你付出的努力和阅读给定链接的描述来看,它似乎是一个很好的答案,所以我会接受这个答案。 - Mike Lischke

0

我在一个CRA项目中也遇到了这个问题

虽然我没有为Web Worker使用TypeScript,但是添加这个代码有助于解决错误。(顺便说一句,感谢你的问题,让我找到了解决方法。谢谢!)

web.worker.js
if (process.env.NODE_ENV != 'production') {
  global.$RefreshReg$ = () => {};
  global.$RefreshSig$ = () => () => {};
}

也许如果你尝试使用(global as any),它会起作用?
我正在做的另一件事是,我只在顶部import库,并且任何被主程序和工作程序都使用的代码都会在使用之前添加一个require
例如:
web.worker.js
const generateSingle = async (client) => {
  const mark = `generate for: ${client.id}`;
  performance.mark(mark);
  const { BillingReportDocument } = require('../pdf/PdfBillingReport');
  /* ... */
}

否则我会得到一个不同的错误,比如“未定义浏览器”,这与在该文件中使用emotion/react有关。

0
你可以尝试使用worker-plugin代替worker-loader。在尝试worker-plugin之前,我也遇到了同样的问题很长一段时间。

这可能是一个可行的解决方案,只是对于我来说不行,因为这个模块是在Apache许可证下发布的,而这与GPLv2不兼容。无论如何还是谢谢。 - Mike Lischke

0
在我的情况下,错误是由于以大写字母开头的函数名(坏猴子!)引起的。显然,这会导致React Fast Refresh将该函数解释为React组件,并错误地修改工作程序包。当然,你的工作程序也不应包含实际React函数组件的定义。
// ✗ Bad
const Identity = x = x

// ✓ Good!
const identity = x => x

请注意,如果在工作脚本导入的任何文件中定义了一个大写的函数,它也会导致“$RefreshReg$未定义”。因此,如果您正在使用一个模块的单个导出项,并且该模块还包含React函数组件,那么您可能会遇到这个问题。

0
在我的情况下,就像@Jon Sakas所说的那样,在babel-loader中使用了react-refresh。我通过在开发模式下使用条件语句,将react-refresh/babel传递给它来修复了这个问题。
import { argv } from 'yargs'
export const { mode } = argv

export const babelLoader = {
  loader: 'babel-loader',
  options: {
    plugins: [
      'babel-plugin-styled-components',
      '@babel/plugin-syntax-dynamic-import',
      '@babel/plugin-proposal-class-properties',
      '@babel/plugin-proposal-export-namespace-from',
      '@babel/plugin-proposal-throw-expressions',
      '@babel/proposal-object-rest-spread',
      // Applies the react-refresh Babel plugin on dev mode only
      ...( mode === 'development' ? ['react-refresh/babel'] : [])
    ]
  }
}

尽管我已经在package.json上设置了选项,但是
...
"babel": {
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": [">1%", "last 4 versions", "not ie < 9"]
        },
        "useBuiltIns": "usage",
        "debug": false,
        "corejs": 3
      }
    ],
    "@babel/preset-react"
  ]
},...

我所做的是将插件移动到webpack配置中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接