解决Webpack和Storybook构建的组件库中React的多个版本

Sas*_*sha 8 npm typescript reactjs webpack material-ui

我正在尝试构建一个基于 MUI 并使用 Storybook 和 TypeScript 构建的 React 组件库。因为 Storybook(使用 create-react-app)基于 Webpack,并且因为我的组件库包含无法使用以下命令编译的 SASS 文件tsc,所以我使用 Webpack 来构建捆绑包。然后我将组件库导入到另一个 React 应用程序中,并使用它自己的 React 版本。

\n

为了测试这一点,我构建了一个普通的 TypeScript create-react-app 演示应用程序,并从我托管的 Github 存储库上的特定分支导入我的库。当我尝试包含库中的组件时,TS 类型正确显示,但应用程序抛出这些错误之一,指出在底层 MUI 库中使用了挂钩。在我看来,这极有可能是 React 版本竞争问题,而不是钩子问题,因为 A) MUI 可以工作,B) 组件可以在 Storybook 中工作。

\n

这是我的组件库的基本结构。

\n

我将与构建无关的应用程序依赖项(React、MUI、MUI 依赖项)放入 中peerDependencies,这样它们就不会重复。

\n

元件库

\n

package.json

\n
{\n  "name": "my-component-library",\n  "version": "0.1.0",\n  "private": true,\n  "license": "MIT",\n  "main": "dist/index.js",\n  "types": "dist/types/index.d.ts",\n  "dependencies": {\n    "react-scripts": "5.0.0",\n    "web-vitals": "^1.0.1"\n  },\n  "scripts": {\n    "build": "webpack"\n    // ...\n  },\n  // ... Lotsa devDependencies\n  "peerDependencies": {\n    "@emotion/react": "^11.7.1",\n    "@emotion/styled": "^11.6.0",\n    "@mui/icons-material": "^5.4.1",\n    "@mui/material": "^5.4.1",\n    "classnames": "^2.3.1",\n    "lodash": "^4.17.21",\n    "react": "^17.0.2",\n    "react-dom": "^17.0.2",\n    "react-router-dom": "^6.2.1"\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

webpack.config.js

\n
const path = require("path");\n\nmodule.exports = {\n  entry: "./src/index.ts",\n  devtool: "source-map",\n  output: {\n    filename: "index.js",\n    path: path.resolve(__dirname, "dist"),\n    library: "MyComponentLibrary",\n    libraryTarget: "umd",\n    clean: true,\n  },\n  mode: "development",\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        loader: "ts-loader",\n        options: { configFile: "tsconfig.build.json" },\n        exclude: /node_modules/,\n      },\n      {\n        test: /\\.s[ac]ss$/i,\n        use: ["style-loader", "css-loader", "sass-loader"],\n      },\n    ],\n  },\n  resolve: {\n    extensions: [".tsx", ".ts", ".js"]\n  },\n  optimization: {\n    minimize: false,\n  },\n  target: "node",\n};\n
Run Code Online (Sandbox Code Playgroud)\n

tsconfig.build.json

\n
{\n  "compilerOptions": {\n    "allowJs": true,\n    "allowSyntheticDefaultImports": true,\n    "baseUrl": "src",\n    "declaration": true,\n    "esModuleInterop": true,\n    "emitDeclarationOnly": true,\n    "forceConsistentCasingInFileNames": true,\n    "isolatedModules": true,\n    "jsx": "react-jsx",\n    "lib": ["es6", "dom"],\n    "module": "esnext",\n    "moduleResolution": "node",\n    "noEmit": false,\n    "noFallthroughCasesInSwitch": true,\n    "noImplicitAny": true,\n    "noImplicitThis": true,\n    "outDir": "dist/types",\n    "resolveJsonModule": true,\n    "skipLibCheck": true,\n    "strict": true,\n    "strictNullChecks": true,\n    "sourceMap": true,\n    "target": "es5"\n  },\n  "include": ["src"],\n  "exclude": ["src/**/*.test.tsx", "src/**/*.stories.tsx", "src/__mocks__"]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当我运行时,Webpack会正确地将文件编译npm run build成.dist/index.jsdist/types

\n

演示应用程序

\n

然后,我的消费演示应用程序是一个基本的 CRA 应用程序,带有 TypeScript 模板。这是包文件的关键组成部分:

\n

package.json

\n
{\n  "name": "cld",\n  "version": "0.1.0",\n  "private": true,\n  "dependencies": {\n    "@types/jest": "^27.4.1",\n    "@types/node": "^16.11.27",\n    "@types/react": "^17.0.1",\n    "@types/react-dom": "^17.0.1",\n    "react": "^17.0.1",\n    "react-dom": "^17.0.1",\n    "react-scripts": "5.0.1",\n    "typescript": "^4.6.3",\n    "web-vitals": "^2.1.4",\n\n    // Note the Github require\n    "my-component-library": "github:my-private-handle/component-library#branch"\n  },\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在我的 App.tsx 文件中:

\n

App.tsx

\n
import React from "react";\nimport { Button } from "my-component-library";\n\nfunction App() {\n  return (\n    <div className="App">\n      <Button>Hello</Button>\n    </div>\n  );\n}\n\nexport default App;\n
Run Code Online (Sandbox Code Playgroud)\n

如果我将鼠标悬停在 上Button,我会看到组件文档,如果我给它错误的 props,TS 会显示错误。但如果我运行该应用程序,由于 React 版本不匹配(我认为),该文件会抛出上述错误。

\n

然而,React 和 React DOM 似乎都只有一个版本——由使用应用程序定义的版本。

\n
npm list react react-dom\ncld@0.1.0 /Users/sasha/code/cld\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac react-dom@17.0.2\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac react-scripts@5.0.1\n\xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react@17.0.2\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\xac my-component-library@0.1.0 (git+ssh://git@github.com/private-handle/component-library.git#09b5db39d8aa8a76f7c1fecacdc3afaecd57812e)\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @emotion/react@11.9.0\n  \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @emotion/styled@11.8.1\n  \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/icons-material@5.6.1\n  \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/material@5.6.1\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/base@5.0.0-alpha.76\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react-dom@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/system@5.6.1\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/private-theming@5.6.1\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/styled-engine@5.6.1\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac @mui/utils@5.6.1\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react-dom@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac react-transition-group@4.4.2\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react-dom@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react-dom@17.0.2 deduped\n  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac react-router-dom@6.3.0\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 react-dom@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\xac react-router@6.3.0\n  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n  \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 react@17.0.2 deduped\n
Run Code Online (Sandbox Code Playgroud)\n
\n

更新:按照此处的调试建议,我在接收/演示应用程序中执行了此操作:

\n
import React from "react";\nimport { Button } from "my-component-library";\n\nfunction App() {\n  return (\n    <div className="App">\n      <Button>Hello</Button>\n    </div>\n  );\n}\n\nexport default App;\n
Run Code Online (Sandbox Code Playgroud)\n

截至最近一次运行,它返回false,因为window.React1未定义 - 并且本地node_modules/react-dom/index.js文件似乎没有被命中,尽管require("react-dom"). 该 require 的结果一个 ReactDOM 对象,所以我不确定它来自哪里。很奇怪,但可能是问题的根源。

\n
\n

更新:我已经验证这是存储库中的唯一问题。不是基于 MUI 构建且不使用钩子(直接或通过 MUI)的组件渲染得很好。从同一个库导出的实用程序可以完美地工作,类型也是如此。只是任何使用 MUI 或其他使用 React hooks 的库的组件都会抛出错误。这对我来说毫无意义,因为应用程序中只有一个版本的 React。

\n

Sas*_*sha 5

问题归结为:Webpack 正在使用React、ReactDOM 和 React Router 构建一个捆绑包。捆绑包中的 React 等版本与“接收”应用程序中的版本冲突,即使 npm list没有显示多个版本。

为了解决这个问题,我在 webpack 配置中使用了外部功能:

{
  ...
  externals: {
    'react': 'react',
    'react-dom': 'react-dom',
    'react-router-dom': 'react-router-dom',
    'react-router': 'react-router'
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,这会构建到一个dist文件夹,但构建中不包含 React 等,而是期望将其安装在接收应用程序中。该构建包已发布。目前,我只是从 Github 拉取它,但这应该可以发布到 NPM 或私有包存储库。

然后,消费/接收应用程序可以很好地导入文件/类型,而不会发生冲突。