reactjs - Løse flere versjoner av React i komponentbibliotek bygget med Webpack og Storybook


Jeg prøver å bygge et React-komponentbibliotek basert på MUI og bruke Storybook og TypeScript. Fordi Storybook (som bruker create-react-app) er basert på Webpack, og fordi komponentbiblioteket mitt inkluderer SASS-filer som ikke kan kompileres med tsc, bruker jeg Webpack for å bygge bundtet. Deretter importerer jeg komponentbiblioteket inn i en annen React-applikasjon, med sin egen versjon av React.

For å teste dette har jeg laget en standard TypeScript create-react-app demo-app, og importerer biblioteket mitt fra en spesifikk gren på min hostede Github-repo. Når jeg prøver å inkludere en komponent fra biblioteket, vises TS-typene korrekt, men appen kaster en av disse feilene, som peker mot en bruk av hooks i det underliggende MUI-biblioteket. Dette virker veldig sannsynlig for meg å være et problem med konkurrerende React-versjoner, snarere enn et hooks-problem, fordi A) MUI fungerer, og B) komponentene fungerer innen Storybook.

Her er grunnstrukturen til komponentbiblioteket mitt.

Jeg la til app-avhengigheter som ikke er relatert til bygging (React, MUI, MUI-avhengigheter) i peerDependencies, slik at de ikke blir duplisert.

Komponentbibliotek

package.json

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

webpack.config.js

const path = require("path");

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

tsconfig.build.json

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

Når jeg kjører npm run build, kompilerer Webpack filene til dist/index.js korrekt, og typene blir lagt (med en mengde overflødig ting jeg gjerne vil ordne opp i) i dist/types.

Demoapp

Deretter er min forbrukende demo-app en grunnleggende CRA-applikasjon, med TypeScript-malen. Her er nøkkelkomponentene i pakke-filen:

package.json

{
  "name": "cld",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@types/jest": "^27.4.1",
    "@types/node": "^16.11.27",
    "@types/react": "^17.0.1",
    "@types/react-dom": "^17.0.1",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "5.0.1",
    "typescript": "^4.6.3",
    "web-vitals": "^2.1.4",

    // Note the Github require
    "my-component-library": "github:my-private-handle/component-library#branch"
  },
}

I filen min App.tsx:

App.tsx

import React from "react";
import { Button } from "my-component-library";

function App() {
  return (
    <div className="App">
      <Button>Hello</Button>
    </div>
  );
}

export default App;

Hvis jeg holder musen over Button, ser jeg komponentdokumentasjonen, og hvis jeg gir den dårlige props, viser TS feilene. Men hvis jeg kjører appen, kaster filen ovennevnte feil på grunn av uoverensstemmende React-versjoner (tror jeg).

Det ser imidlertid ut til å være riktig at det bare finnes én versjon av både React og React DOM - den som er definert av den brukende appen.

npm list react react-dom
[email  /Users/sasha/code/cld
├─┬ [email 
│ └── [email  deduped
├─┬ [email 
│ └── [email  deduped
├── [email 
└─┬ [email  (git+ssh://[email /private-handle/component-library.git#09b5db39d8aa8a76f7c1fecacdc3afaecd57812e)
  ├─┬ @emotion/[email 
  │ └── [email  deduped
  ├─┬ @emotion/[email 
  │ └── [email  deduped
  ├─┬ @mui/[email 
  │ └── [email  deduped
  ├─┬ @mui/[email 
  │ ├─┬ @mui/[email 
  │ │ ├── [email  deduped
  │ │ └── [email  deduped
  │ ├─┬ @mui/[email 
  │ │ ├─┬ @mui/[email 
  │ │ │ └── [email  deduped
  │ │ ├─┬ @mui/[email 
  │ │ │ └── [email  deduped
  │ │ └── [email  deduped
  │ ├─┬ @mui/[email 
  │ │ └── [email  deduped
  │ ├── [email  deduped
  │ ├─┬ [email 
  │ │ ├── [email  deduped
  │ │ └── [email  deduped
  │ └── [email  deduped
  ├── [email  deduped
  ├─┬ [email 
  │ ├── [email  deduped
  │ ├─┬ [email 
  │ │ └── [email  deduped
  │ └── [email  deduped
  └── [email  deduped

OPPDATERING: Etter å ha fulgt feilsøkingsanbefalingene her, gjorde jeg dette i mottaks-/demo-appen:

// Add this in node_modules/react-dom/index.js
window.React1 = require('react');

// Add this in your component file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);

Per den siste kjøringen returnerer dette false, fordi window.React1 er udefinert -- og den lokale filen node_modules/react-dom/index.js ser ut til ikke å bli brukt til tross for require("react-dom"). Resultatet av den require er et ReactDOM-objekt, så jeg er ikke sikker på hvor det kommer fra. Veldig merkelig, men sannsynligvis roten til problemet.


OPPDATERING: Jeg har bekreftet at dette er det eneste problemet i repoet. Komponenter som ikke er bygget på MUI og ikke bruker hooks (direkte eller gjennom MUI) rendres helt fint. Verktøyfunksjoner eksportert fra samme bibliotek fungerer perfekt, det samme gjør typene. Det er bare at alle komponenter som bruker MUI eller et annet bibliotek som bruker React hooks kaster en feil. Dette gir ingen mening for meg, siden det finnes kun én enkelt versjon av React i appen.

Svar #1

Problemet kokte ned til dette: Webpack bygde en pakke med React, ReactDOM og React Router. Versjonene av React osv. i pakken konflikterte med versjonene i den «mottakende» appen, selv om npm list ikke viste flere versjoner.

For å fikse dette brukte jeg ekstern-funksjonaliteten i webpack-konfigurasjonen min:

{
  ...
  externals: {
    'react': 'react',
    'react-dom': 'react-dom',
    'react-router-dom': 'react-router-dom',
    'react-router': 'react-router'
  }
}

Dette bygger da til en dist-mappe, men inkluderer ikke React osv. i bygget, og forventer i stedet at det er installert i mottaksappen. Den byggede pakken publiseres. For øyeblikket henter jeg den bare fra Github, men dette bør fungere utsendt til NPM eller et privat pakkeregister.

Den forbrukende/mottakende appen kan da importere filene/typene uten problemer, uten konflikter.

Svar #2

Jeg hadde akkurat det samme problemet da jeg utviklet et React-komponentbibliotek.

Den eneste løsningen som konsekvent fungerte for meg var å bruke relativ npm link fra eksempel-mappen til webpack-mappen og tvinge denne spesifikke versjonen på webpack-prosjektet. Les de siste linjene i denne delen på nytt og prøv det ut. Merk at for meg fungerte kun npm link (yarn link fungerte ikke)
Her er et ekte prosjekt som bruker denne metoden, og et skript som håndterer det https://github.com/Eliav2/react-xarrows/blob/197326ccb032d5b5a3a7e18def596abac7327b05/examples/package.json#L34. Sørg for å justere den relative stien til ditt tilfelle

(beklager for formatet, svarer fra mobiltelefon)

Et annet alternativ er å bruke monorepo, da vil en enkelt kopi av React bli installert, og du kan importere biblioteket ditt fra eksempelprosjektet

Relaterte artikler