简体   繁体   中英

Webpacked React component not usable when published to NPM and imported in another module

I've pored over the posts related to my question, but I still cannot get my react component to import correctly into another project. I have semi-non-trivial react component that displays a grid (MaterialTable) using react hooks. It works well when imported directly from within the same project that implements it. The problem occurs when, after publishing it to NPM and importing it into another project:

react-dom.development.js?7f13:11125 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Here is the component:

import React, { useContext, createContext, useState, forwardRef } from 'react';
import MaterialTable from 'material-table';
import Button from '@material-ui/core/Button';

const QbsContext = createContext();

import AddBox from '@material-ui/icons/AddBox';
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import Check from '@material-ui/icons/Check';
import ChevronLeft from '@material-ui/icons/ChevronLeft';
import ChevronRight from '@material-ui/icons/ChevronRight';
import Clear from '@material-ui/icons/Clear';
import DeleteOutline from '@material-ui/icons/DeleteOutline';
import Edit from '@material-ui/icons/Edit';
import FilterList from '@material-ui/icons/FilterList';
import FirstPage from '@material-ui/icons/FirstPage';
import LastPage from '@material-ui/icons/LastPage';
import Remove from '@material-ui/icons/Remove';
import SaveAlt from '@material-ui/icons/SaveAlt';
import Search from '@material-ui/icons/Search';
import ViewColumn from '@material-ui/icons/ViewColumn';

const tableIcons = {
  Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
  Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
  Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
  DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
  Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref} />),
  Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
  FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
  LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
  NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
  PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
  ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
  Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
  SortArrow: forwardRef((props, ref) => <ArrowDownward {...props} ref={ref} />),
  ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />),
  ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />)
};

const qbsColumns = [
  { title: "First Name", field: "name" },
  { title: "Last Name", field: "surname" },
  { title: "Year of Birth", field: "birthYear", type: "numeric" },
  {
    title: "City of Birth",
    field: "birthCity",
    lookup: {
      34: 'Haughton',
      63: 'San Diego',
      88: 'Henryetta',
      90: 'Mount Vernon',
      91: 'Chicago',
      92: 'Dallas',
      93: 'Campbell',
      94: 'Cincinatti',
      95: 'Mesa',
      89: 'San Rafael'
    }
  }
];

const qbData = [
  { name: "Dak", surname: "Prescott", birthYear: 1993, birthCity: 34 }
];

const addData = [
  { name: "Danny", surname: "White", birthYear: 1952, birthCity: 95 },
  { name: "Roger", surname: "Staubach", birthYear: 1942, birthCity: 94 },
  { name: "Jerry", surname: "Rhome", birthYear: 1942, birthCity: 92 },
  { name: "Craig", surname: "Morton", birthYear: 1943, birthCity: 93 },
  { name: "John", surname: "Roach", birthYear: 1933, birthCity: 92 },
  { name: "Don", surname: "Heinrich", birthYear: 1948, birthCity: 91 },
  { name: "Don", surname: "Meredith", birthYear: 1938, birthCity: 90 },
  { name: "Edward", surname: "LeBaron", birthYear: 1930, birthCity: 89 },
  { name: "Troy", surname: "Aikman", birthYear: 1966, birthCity: 88 },
  { name: "Tony", surname: "Romo", birthYear: 1980, birthCity: 63 }
];

let qbIndex = 0;

const QbsComponent = () => {
  const { qbs, addRow } = useContext(QbsContext);

  return (
    <div>
      <MaterialTable
        icons={tableIcons}
        columns={qbsColumns}
        data={qbs}
        title="Dallas Cowboys Quarterbacks"
      />

      <Button onClick={() => addRow()}>Add Row</Button>
    </div>
  )
};

const Qbs = () => {
  const [qbs, setQbs] = useState(qbData);

  const addRow = () => {
    setQbs([...qbs, addData[qbIndex++]]);
  };

  return (
    <div>
      <QbsContext.Provider value={{ qbs, addRow }}>
        <QbsComponent />
      </QbsContext.Provider>
    </div>
  );
};

export default Qbs;

This is meant for a web page. This is the webpack config:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
   // Entry points that have the 'babel-polyfill' value do so in order to use
   // async/await
   entry: {
      'qbs': ['idempotent-babel-polyfill', './src/main/web/app/index.js'],
      'app': ['idempotent-babel-polyfill', './src/main/web/index.js']
   },

   devtool: 'sourcemaps',

   cache: true,

   mode: 'development',

   output: {
      path: path.resolve(__dirname + '/target/classes/static/'),
      filename: '[name].js',
      libraryTarget: 'commonjs-module'
   },

   module: {
      rules: [
         {
            test: /\.css$/,
            use: [
               'style-loader',
               'css-loader'
            ]
         },

         { test: /\.woff$/, use: 'url-loader?prefix=font/&limit=5000&mimetype=application/font-woff' },
         { test: /\.woff2$/, use: 'url-loader?prefix=font/&limit=5000&mimetype=application/font-woff2' },
         { test: /\.ttf$/, use: 'file-loader?prefix=font/' },
         { test: /\.eot$/, use: 'file-loader?prefix=font/' },
         { test: /\.svg$/, use: 'file-loader?prefix=font/' },
         { test: /\.(png|jpe?g|gif)$/i, use: [{loader: 'file-loader'}] },
         {
            test: /\.js$/,
            exclude: /(node_modules)/,
            use: [{
               loader: 'babel-loader',
               options: {
                  presets: [
                     '@babel/preset-env', {
                        plugins: [
                           '@babel/plugin-proposal-class-properties'
                        ]
                     },
                     '@babel/preset-react'
                  ],
                  plugins: [
                     [
                        '@babel/plugin-proposal-decorators', { 'legacy': true }
                     ]
                  ]
               }
            }]
         }
      ]
   },

   resolve: {
       alias: {
          'react-dom': '@hot-loader/react-dom'
       }
   },

   plugins: [
      new MiniCssExtractPlugin()
   ]
};

I copy the resulting qbs.js to a folder that has a minimal package.json:

{
   "name": "@sellis/qbs",
   "version": "1.0.0",
   "main": "qbs.js",
   "module": "qbs.js",
   "publishConfig": {
      "access": "public"
   },
   "peerDependencies": {
      "react": "^16.12.0",
      "react-dom": "^16.12.0"
   }
}

I've played around with changing the library and libraryTarget in webpack config, and the results have varied. I tried using Rollup instead and that resulted in different errors due to issues with MaterialTable.

If nobody has any ideas that help me with this, I'll put this out on github so that there is something more tangible to play around with.

The problem I've been experiencing is a known limitation to webpack when publishing es6 modules. There are many articles describing workarounds but so far I haven't gotten any of them to work for me. I found however that using Rollup is an alternative to webpack that can be made to work, and I was finally able to publish and reuse my react component by following along with the code in this github repository . I haven't updated the code in my sample repo but maybe I'll come back around to it, but I've been so busy with this issue for a couple days now and its time to move on.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM