use webpack to bundle a React component to be imported by another React component

I have some React code in two git repos. The goal is to npm publish a React component is repo 1 and consume it in repo 2.

In repo 1, I defined Hello.jsx:

'use strict';
import React from 'react';

export default class Hello extends React.Component {
    render() {
        return (<div>hello world Nivesh</div>);
    }
};

In repo 2, I defined HelloWorld.jsx:

import React from 'react';
import Hello from 'repo1';

export default class HelloWorld extends React.Component {
    render() {

        console.log(JSON.stringify(Hello));
        return (
        <div>
            <Hello/>
        </div>
        );
    }
}

I'm trying to use webpack to bundle code in repo 1, but when I try to import Hello in repo 2, I got this error.

VM10112 main-bundle.js:660 Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in. Check the render method of HelloWorld. in HelloWorld

The console log shows the object that it imported is {}.

However, if I only use babel but not webpack, I was able to get it to work.

My webpack.config.js:

'use strict';

module.exports = {
mode: 'development',

entry: {
    hello: './src/Hello.jsx'
},
// output: {
    // filename: 'bundle.js',
    // libraryTarget: 'umd',
    // libraryExport: ['Hello'],
// },

plugins: [
    new MiniCssExtractPlugin({
        filename: 'cssBundle.css'
    }),
    // new HtmlWebpackPlugin(),
    // new CleanWebpackPlugin(),
],

module: {
    rules: [
        {
            test: /\.(js|jsx)$/,
            use: [
                {
                    loader: 'babel-loader',
                    query: {
                        presets: ['react', 'es2015']
                    }
                },
                // 'eslint-loader'
            ],
            exclude: [/node_modules/, /build/, /dist/]
        },
        {
            test: /\.(sa|sc|c)ss$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        hmr: process.env.NODE_ENV === 'development',
                        modules: true,
                        localIdentName: '[name]__[local]',
                    },
                },
                'css-loader',
                'sass-loader',
            ],
            exclude: /node_modules/
        }
    ]
},

resolve: {
    extensions: ['.js', '.jsx', '.scss', '.css']
},

devtool: 'source-map',

devServer: {
    compress: true,
    inline: true,
    host: '0.0.0.0',  // to allow connection from outside of localhost
    port: 8081,
},

};

The resulting webpack generated js:

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {
/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {
/******/        if(mode & 1) value = __webpack_require__(value);
/******/        if(mode & 8) return value;
/******/        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/        var ns = Object.create(null);
/******/        __webpack_require__.r(ns);
/******/        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/        return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./src/Hello.jsx");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./src/Hello.jsx":
/*!***********************!*\
  !*** ./src/Hello.jsx ***!
  \***********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = __webpack_require__(/*! react */ "react");

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Hello = function (_React$Component) {
    _inherits(Hello, _React$Component);

    function Hello() {
        _classCallCheck(this, Hello);

        return _possibleConstructorReturn(this, (Hello.__proto__ || Object.getPrototypeOf(Hello)).apply(this, arguments));
    }

    _createClass(Hello, [{
        key: 'render',
        value: function render() {
            return _react2.default.createElement(
                'div',
                null,
                'hello world Nivesh'
            );
        }
    }]);

    return Hello;
}(_react2.default.Component);

;

exports.default = Hello;

/***/ }),

/***/ "react":
/*!**************************************************************************************!*\
  !*** external {"commonjs":"react","commonjs2":"react","amd":"React","root":"React"} ***!
  \**************************************************************************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = undefined;

/***/ })

/******/ });
//# sourceMappingURL=hello.js.map

and if i only use babel, the generated code looks like this:

'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Hello = function (_React$Component) {
    _inherits(Hello, _React$Component);

    function Hello() {
        _classCallCheck(this, Hello);

        return _possibleConstructorReturn(this, (Hello.__proto__ || Object.getPrototypeOf(Hello)).apply(this, arguments));
    }

    _createClass(Hello, [{
        key: 'render',
        value: function render() {
            return _react2.default.createElement(
                'div',
                null,
                'hello world Nivesh'
            );
        }
    }]);

    return Hello;
}(_react2.default.Component);

exports.default = Hello;
;
//# sourceMappingURL=Hello.js.map

I've read other posts and have tried

import Hello from ...

vs

import {Hello} from ...

That was not the problem. My question is mainly on the import mechanism in javascript.

A shorter version of the webpack generated file:

(function(modules) {

/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
    return __webpack_require__(__webpack_require__.s = "./src/Hello.jsx");
}) 
({
   'src/Hello.jsx': (function() {

   ...
   exports.default = Hello;
   })
})

I'm trying to understand what's happening in the webpack output, so please correct me with the following statement.

It seems that the webpack generated file has an anonymous function, and the parameter passed into this anonymous function is an object:

{ 'src/Hello.jsx': function... }

and when this anonymous function runs, it calls the function 'webpack_require' which returns module.exports (which I assume returns the "exports.default = Hello;" defined inside the function generated from Hello.jsx. So it looks to me that the webpack file is exporting "Hello".

I'm not well versed in the javascript import/export mechanism. I understand there's many different ways of defining modules in javascript. But i'm not sure if something exported in one way can be imported in another way. For example, the way it exports Hello in the webpack file, can it be imported from a jsx file by

import Hello from ...?

When i compare the output from babel with webpack, the last line in the babel output is

exports.default = Hello;

It seems to me that when the babel output exports Hello with exports.default, I can import it with "import x from y".

I understand that webpack allows one to specify libraryTarget in the output section, and I see the different options there. But I'm not sure which way allows one to import something like "import x from y".

https://webpack.js.org/configuration/output/#outputlibrarytarget

2 answers

  • answered 2019-05-15 03:27 Tony Ngo

    It's should be

    import Hello from 'repo1/hello.jsx';
    

    Because by default webpack will only resolve type for js file only if you want to import by default using jsx extension you have to specify the extension file too

    like I have here

    import React, {Component} from "react";
    
    class AddUserModal extends Component {
    }
    
    export default AddUserModal;
    

    then in another file

    import AddUserModal from "./Manage/AddUserModal.jsx";
    
    class AccountTable extends Component {
    }
    

    Please let me know if you still have problem

  • answered 2019-05-15 18:29 youhong

    I'm answering my own question. Turns out there's a config in webpack's output section called LibraryTarget, and setting it to 'umd' solves the problem.

    https://webpack.js.org/configuration/output/#outputlibrarytarget