Build React components library with Webpack 4 - javascript

I'm currently building a library of React components and bundling it with Webpack 4.
Everything works just fine from building the library's bundle to publishing it on an npm registry.
But then, I'm not able to import any of its components in an other React application and get this error message at runtime:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
And here is the related code:
A dumb component from my components library:
button/index.js
import React from "react";
const Button = () => <button>Foobar</button>;
export { Button };
The main entry point of my library index.js:
import { Button } from "./src/components/Button";
export { Button };
My Webpack config webpack.config.js:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: "./index.js",
plugins: [new CleanWebpackPlugin()],
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "commonjs",
library: ""
}
};
And finally, the import of this component in an other application:
import { Button } from "my-design-system";
I guess I'm missing something in my Webpack config or one of the property may be wrong, but after reading multiple posts and tutorials, I can't figure which one.

You're exporting your library as commonjs and trying to import it via import/export syntax. You should change your output to
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "umd",
library: "my-design-system"
}
Found a lot of info here: https://webpack.js.org/guides/author-libraries/

What I would do is to export your components as default and then re-export as named from index.js:
/// Button.js
import React from "react";
const Button = () => <button>Foobar</button>;
export default Button ;
// index.js
export { default as Button } from "./src/components/Button";
Then you can do
import { Button } from "my-design-system";
Also make sure you have main set up, pointing to your index.js, in your design system's package.json
Additionally, if you still want to have named exports in some of your components, you can export everything from that component file:
//index.js
export * from "./src/components/ComponentWithNamedExports";
Either way you will make sure there's always one point of export for all your components.
EDIT: As noted in by Maaz Syed Adeeb, you have wrong libraryTarget in your config. I'd remove both libraryTarget and library from there.

Related

Webpack — When building a code bundle, and subsequently importing it, how do I access named exports?

I’m building a custom version of CKEditor which uses webpack (v4) to bundle the src into a minified bundle. My src includes both default and named exports, along the lines of:
export default class MyEditor extends ClassicEditor {}
export class InteractiveMyEditor extends ClassicEditor {}
export const Context = CKContext
The webpack.config.js file includes the following output section
output: {
library: 'MyEditor',
path: path.resolve( __dirname, 'build' ),
filename: 'ckeditor.js',
libraryTarget: 'umd',
libraryExport: 'default'
},
it all builds fine and I can
import MyEditor from '#project/ckeditor-build-myeditor'
// MyEditor is defined
just fine.
But I can't access the InteractiveMyEditor or the Context objects
import { Context, InteractiveMyEditor } from '#project/ckeditor-build-myeditor'
// both Context and InteractiveMyEditor are undefined
I've been trying to make sense of the Webpack4 Docs but can't see what else to do other than try different values for libraryTarget. I've tried commonjs, and 'module just to see if that helped but alas no.
How do I export the named exports correctly?

Relative Imports in React.js

Currently I have an import which looks like this
import Button from "../../../components/Button/Button"
but I want to make it relative so I wouldn't require to type the ../../../ time and again.
I want to import using this method:
import Button from "components/Button/Button" or src/components/Button/Button
but please also assure that it will work on both production and development.
If you are using vscode with project created with create-react-app you can try adding a jsconfig.json file in root with this .
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
If you are using webpack. You can use resolve.
const path = require('path');
module.exports = {
//...
resolve: {
alias: {
Utilities: path.resolve(__dirname, 'src/utilities/'),
Templates: path.resolve(__dirname, 'src/templates/'),
Components: path.resolve(__dirname, 'src/components/'),
}
}
};
Now, instead of using relative paths when importing like so:
import Utility from '../../utilities/utility';
import Button from '../../src/components/Button';
you can use the alias:
import Utility from 'Utilities/utility';
import Button from 'Components/Button';
you can download the react snippet plugig to help you with import
import Butoon from './component/Button/Button' I Think

ReactRouter is undefined when bundling with webpack

I have a weird problem that seems related to webpack, but I am not sure. Here is the context, I am creating a front end using ReactJS, and I have a Java backend, so what I am trying to do is to compile and bundle all the JS using webpack with the config below.
const path = require("path");
module.exports = {
entry: {
main: path.join(__dirname, "src/js/index.js")
},
output: {
path: path.join(__dirname, "src/main/resources/static/js"),
filename: "bundle.js"
},
resolve: {
extensions: [".js", ".jsx"],
modules: [
path.join(__dirname, "src"),
"node_modules"
]
},
module: {
rules: [
{
test: /\.less$/,
loader: "less-loader"
},
{
test: /\.(js|jsx)$/,
loader: "babel-loader"
},
]
}
};
And here is the only JS file, with very basic display that is failing :
import PropTypes from "prop-types";
import React from "react";
import ReactDOM from "react-dom";
import ReactRouter, {Link} from "react-router";
document.addEventListener("DOMContentLoaded", function () {
console.log(React);
console.log(PropTypes);
console.log(ReactRouter);
console.log(Link);
console.log(document.getElementById("root"));
ReactDOM.render(<Link to="/">Home</Link>, document.getElementById("root"));
});
I've installed all npm required packages, webpack is running without errors, but when I open the page in the browser, there is an error + warning in the console.
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: undefined. You likely forgot to export your
component from the file it's defined in.
Error: Element type is
invalid: expected a string (for built-in components) or a
class/function (for composite components) but got: undefined. You
likely forgot to export your component from the file it's defined in.
I found that it is because Link and ReactRouter are undefined, but I don't understand how it's possible since they are imported and the package is installed.
Do you have any idea on how to solve the problem ?
In addition to the first comment on your post, you might wanna check out which version of React-Router you have. In the newest version, { Link } is a part of the "react-router-dom" package, and you can replace "react-router"'s { Router } with "react-router-dom"'s { BrowserRouter }.
React-Router-Dom is part of the React-Router package you installed (if it's a newer version), so all you need to do is replace React-Router with { BrowserRouter } and "react-router/" with "react-router-dom".

How to use React components from local npm package

I'm trying to create an npm package with some React components that I use for virtually all of my projects. Here's the folder structure and file contents:
/
dist/
bundle.js
src/
MyComponent.jsx
index.js
package.json
webpack.config.js
MyComponent.jsx
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return (
<p>Hello, world!</p>
);
}
}
export default MyComponent;
index.js
// eventually there will be more components imported/exported here
import MyComponent from './MyComponent.jsx';
exports.MyComponent = MyComponent;
webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
path: './dist',
filename: 'bundle.js'
},
// some loaders...
};
package.json
{
"name": "my-library",
"files": [
"dist",
"src"
],
"main": "dist/bundle.js",
// the usual stuff...
}
This is my understanding of the process. First, I build the src files with webpack. This looks at /src/index.js, imports the MyComponent class, then exports it, making it available as MyComponent. This is all added to a bundle.js file in /dist.
Next, I pack the module with npm pack. This creates an archive file with the /src and /dist directories in it.
Then I go over to my other project and run npm install ../path/to/archive/file. This adds the module to my node_modules directory. The problem is that when I try to import and use MyComponent...
import React, { Component } from 'react';
import MyComponent from 'my-library';
class App extends Component {
render() {
console.log(<MyComponent />);
return (
<MyComponent />
);
}
}
export default App;
...and render that component, I get this warning and error:
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of `App`.
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `App`.
The output of the console.log is a React object, but the type is Object, which seems like what the warning is alluding to (it should be a string instead, I guess).
Any idea what I'm missing?
EDIT:
Well, I'm closer. I added library options to my webpack config:
output: {
library: 'my-library',
libraryTarget: 'umd'
}
...and then I was able to use the component like this:
import MyLibrary from 'my-library';
const MyComponent = MyLibrary.MyComponent;
But I don't want to have to do that for every component. My goal is this:
import { MyComponent, MyOtherComponent } from 'my-library';
You have to export your component from the component index.js file like shown below
import MyComponent from './MyComponent.jsx';
import MyOtherComponent from './MyOtherComponent.jsx';
export {
MyComponent,
MyOtherComponent
}
Then in your Main project, Import them using
import { MyComponent, MyOtherComponent } from 'my-library';

Webpack namespacing es6 modules

Due to compatibility issues with typescript, babel, and webpack I have to use the export class Test {} syntax rather than export default class Test {}. It solves all of my issues with typescript but causes webpack to namespace everything on an object instead.
I'm having webpack generate umd and am testing the include via requirejs.
However, rather than passing in the function directly I'm now getting an object with a property instead. This won't fly in my real app.
{
Test: function Test() {}
}
webpack.config.js:
module.exports = {
entry: './test.js',
output: {
filename: 'a.js',
libraryTarget: 'umd'
},
module: {
loaders: [{
test: /\.js$/, loader: 'babel-loader'
}]
}
};
.babelrc:
{
"presets": ["es2015"]
}
I'm not sure if I've understood correctly, but after much experimentation I found the cleanest way to use modules in TypeScript is to simply use ES6 syntax in my source files.
// src/foo/Foo.ts
export class Foo {}
// src/bar/Bar.ts
import {Foo} from '../foo/Foo';
export class Bar extends Foo {}
This way your source files can remain agnostic to your output module format.
For large libraries, it's possible to keep an index.ts at the root of each of your "namespaces", which will afford you greater flexibility when exporting modules:
// src/widgets/FooWidget.ts
export class FooWidget {}
// src/widgets/BarWidget.ts
export class BarWidget {}
// src/widgets/index.ts
export * from './FooWidget';
export * from './BarWidget';
// src/index.ts
import * as widgets from './widgets';
import * as badgers from './badgers';
export {
widgets,
badgers
};

Categories