Importing a component library using react in Storybook - javascript

I have two projects: one is a component library called simply '#loonslanding/components'. The other is a storybook project. Inside of the components library, I have the following in src/index.js:
import React, { Component } from 'react';
class Button extends Component {
render () {
return (
<button>{this.props.label}</button>
);
}
}
export default Button;
I build this using webpack and babel with the following webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
output: {
filename: 'index.bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react']
}
}
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
}
]
}
};
I then link my js module using yarn link in the components directory and run yarn link #loonslanding/components in my storybook directory. I have a single story in storybook, called button.stories.js:
import Button from "#loonslanding/components";
import { action } from '#storybook/addon-actions';
import React from 'react';
export default {
component: Button,
title: `Atoms/Button`
};
export const Standard = () => <Button onClick={action('clicked')} label={"Standard Button"}></Button>;
When I run yarn storybook, I get the following error message:
Element 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, or you might have mixed up default and named imports.
Check the render method of `storyFn`.
This seems like a pretty common error, and I've researched it over the past few hours, but I don't think I've actually mixed up named and default exports/imports. When I attempt to import the components project into a different project (e.g. a react application other than Storybook), it gives me a similar exception. This leads me to believe that something is wonky with how Babel is transpiling the components module. Thoughts on what I might be doing incorrectly?

This was due to having incorrect parameters in the Webpack configuration file. I learned from this answer that Webpack is designed to package applications by default, and if you want it to package a library, you need to add the library name, as well as the library target type. This looks like the following when using the commonjs2 library target type:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: 'loonslandingstorybook',
libraryTarget: 'commonjs2'
},
plugins: [new CleanWebpackPlugin()],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: ['#babel/plugin-transform-react-jsx']
}
}
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, './src')
}
]
}
}

Related

React reusable component library styles not loading

hey people I need help with the following:
am creating React UI component library
I am using webpack and dev build works great, scss files are loaded and components are displayed correctly
on production build, JS bundle is created as well as CSS (I use SCSS) bundle
BUT when I install the library in another React project and import the component, CSS is not loaded (cmp is not styled), JS works fine and the component is rendered yet styles are not loaded...
EDIT
Apparently this approach requires manual loading of CSS in parent app project. Which I want to avoid. Is there alternative way which can provide scenario in which styles will be resolved on the level on component without need for manual loading?
Here is my production webpack config:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, '../lib'),
libraryTarget: 'commonjs',
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader']
})
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.svg/,
use: {
loader: 'svg-url-loader',
options: {}
}
}
]
},
externals: {
'react': 'commonjs react',
'react-dom': 'commonjs react-dom',
},
resolve: {
modules: [
path.resolve('./src'),
path.resolve('./node_modules')
]
},
plugins: [
new ExtractTextPlugin({
filename: 'ui-library.css'
})
]
};
You could simply not use ExtractTextPlugin.
The whole purpose of Webpack is to group assets not based on file type but by a component perspective.
So, if you remove ExtractTextPlugin, your CSS will be included in your .js bundle.

webpack 'import' only at the top level

I'm trying to implement async routing in my react-project using react-router-async-routing.
The problem is that I get this error during webpack compiling:
'import' and 'export' may only appear at the top level
It happens in my router.js which is equal to the one from the link above.
Replacing the import with System.import compiles without errors and the browser loads the chunk (visible in the network-traffic-view), but the page remains blank (I guess it is not being executed!).
This is my webpack-config:
var { path, resolve } = require("path");
var webpack = require("webpack");
module.exports = {
entry: {
app: "./src/js/user/main.js",
vendor: ["react", "react-dom"]
},
output: {
path: __dirname + "/resources/user/js",
publicPath: "/resources/user/js/",
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "vendor.bundle.js"
})
],
module: {
loaders: [{
test: /.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['env', 'react']
}
},
{
test: /\.(less)$/,
loaders: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.(css)$/,
loaders: ["style-loader", "css-loader"]
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
loader: "file-loader"
},
{
test: /\.(png|jpg|gif)$/,
loader: "url-loader"
}
]
},
resolve: {
alias: {
"global": __dirname + "/src/js/user/module/global"
}
}
};
How can I solve this problem?
EDIT1:
File with error:
import AsyncSetup from "react-router-async-routing";
import routes from "./routes";
const {Route, Link, Preload} = AsyncSetup(routes, path => {
import(`./routes/${path}.jsx`);
});
export {Link, Route, Preload};
EDIT2:
I solved the problem with the import by installing
babel-plugin-syntax-dynamic-import
and adding it to babel :
test : /.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: [['es2015'], 'react'],
plugins: ["syntax-dynamic-import"]
}
Now everything is working!
The new Import API is still a little confusing, because of different Node versions, and specially when using transpiling tools such as Babel and Webpack. I strongly recommend you to use require() instead of import() in your dynamic imports (where the file path is not a constant) and let import for constant dependencies, that's what I do in my projects, and I have no issues.
Something like this:
import AsyncSetup from "react-router-async-routing";
import routes from "./routes";
const {Route, Link, Preload} = AsyncSetup(routes, path => {
return require(`./routes/${path}.jsx`);
});
export {Link, Route, Preload};
You can also use absolute paths, this might be safer depending on your environment and code.
import path from 'path';
import AsyncSetup from "react-router-async-routing";
import routes from "./routes";
const {Route, Link, Preload} = AsyncSetup(routes, path => {
return require(path.join(__dirname, `./routes/${path}.jsx`));
});
export {Link, Route, Preload};
If this don't solve your issue, maybe you'll need to do some tweaking in the babel/webpack modules resolver.

Webpack output is empty object

I want to build a react component library as a node module to then import it into different projects. But if I try to import a component it just returns an empty object.
button.jsx:
import React, {Component} from 'react'
export class Button extends Component {
render() {
return <button className='btn'>Hello Button comp</button>
}
}
export default Button
index.js
var Button = require('./button/button').default;
module.exports = {
Button: Button
}
webpack.config.js
const Path = require('path');
module.exports = {
resolve: {
extensions: ['.js', '.jsx']
},
entry: {
app: './src/components/index.js'
},
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
}
]
}
}
Main property in package.json is bundle.js
I figured out that when I import Button in a project it is just an empty object. It seems to me as if webpack doesn't bundle the index file properly. Any ideas what could be wrong here?
A webpack bundle does not expose your exports by default, as it assumes that you're building an app and not a library (which is the far more common use of webpack). You can create a library by configuring output.library and output.libraryTarget.
output: {
path: __dirname,
filename: 'bundle.js',
library: 'yourLibName',
libraryTarget: 'commonjs2'
},
output.libraryTarget is the format of the module, which would also allow you to expose the library as a global variable. commonjs2 is the module format that Node uses. See What is commonjs2? for the difference between commonjs and commonjs2.
Since you're using React, you'll expect that the consumer of the library will have React present as a dependency and therefore you don't want to include it in your bundle. To do that you can define it as an External. This is shown in Authoring Libraries, which walks you through a small example.

Correctly bundled image could not be loaded

I have a very simple React component, that is supposed to display an image.
I am also using Webpack for bundling.
It's probably worth noting that I am using ReactJS.NET.
Although the webpack bundle builds properly, and the .jpg generated by webpack is viewable (using Windows Photo Viewer, for example), the image does not display in my View.
When I take a peek into inspector, the html structure is built properly, but I am getting:
"Could not load the image" - when I hover over the image path.
I made sure that the image path is correct.
Below is my react component:
var React = require('react');
var BackgroundImg = require('./Images/img_fjords.jpg');
class Login extends React.Component {
render() {
return (
<img src={BackgroundImg} />
);
}
componentDidMount() {
console.log("Mounted");
}
}
module.exports = Login;
Webpack config:
var path = require('path');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
context: path.join(__dirname, 'App'),
entry: {
server: './server',
client: './client'
},
output: {
path: path.join(__dirname, 'Built/'),
publicPath: path.join(__dirname, 'Built/'),
filename: '[name].bundle.js'
},
plugins: [
new WebpackNotifierPlugin()
],
module: {
loaders: [
{ test: /\.css$/, loader: "style-loader!css-loader" },
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "url-loader?limit=10000&mimetype=application/font-woff"
},
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' },
{ test: /\.jsx$/, loader: 'jsx-loader?harmony' }
]
},
resolve: {
// Allow require('./blah') to require blah.jsx
extensions: ['.js', '.jsx']
},
externals: {
// Use external version of React (from CDN for client-side, or
// bundled with ReactJS.NET for server-side)
react: 'React'
}
};
The problem was solved thanks to help from #Luggage.
The webpack.config was wrong, it should have been:
output: {
path: path.join(__dirname, 'Built/'),
publicPath: 'Built/',
filename: '[name].bundle.js'
},
Well, I can't see your webpack config, but I'm assuming your using all the correct loaders (file-loader, extract-loader, html-loader, url-loader)? The way I handle it is using the webpack-copy-plugin to copy over my images folder so relative paths still work.

Typescript: How to have some imports in the global scope?

Context:
I work on a project where the senior programmer decided to reduce the boilerplate code in newly created typescript files. Two examples of this boilerplate code would be importing the React library or the function that fetches and processes our localized strings.
Question:
Is it possible to have imports always available in files placed in certain folders without having to write the import tags every time?
What I've tried:
I've searched and read on the subject and found those links that talk about defining variables to use in the global space:
global.d.ts, global-modifying-module.d.ts, A typescript issue that seems to get it working
However, I was still unable to get it to work. Here is what I've tried:
At the root of the folder where I want React to be always available, I created a global.d.ts file which contains:
import * as R from "react";
declare global{
const React: typeof R;
}
With this file, the resource "React" is supposed to always be available to other files in subsequent folders. My IDE (Webstorm) recognizes that the import is there and allows me to manipulate the variable React without complaining. However, when I try to run the app, I get this error:
ReferenceError: React is not defined
I don't understand what is wrong with the code! Here is an example of the file I'm trying to render:
export default class World extends React.Component<{}, any> {
public render() {
return (<div>Hello world</div>);
}
}
From this stackoverflow question, I was under the impression that the problem could be webpack related. For the sake of completeness, here is the webpack config file we're currently using:
const webpack = require('webpack');
const path = require('path');
const BUILD_DIR = path.resolve(__dirname, './../bundles');
const WEBPACK_ENTRYFILE = path.resolve(__dirname, './../srcReact/ReactWrapper.tsx');
// `CheckerPlugin` is optional. Use it if you want async error reporting.
// We need this plugin to detect a `--watch` mode. It may be removed later
// after https://github.com/webpack/webpack/issues/3460 will be resolved.
const { CheckerPlugin } = require('awesome-typescript-loader');
const config = {
entry: [WEBPACK_ENTRYFILE],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.less']
},
output: {
path: BUILD_DIR,
filename: 'bundle.js'
},
plugins: [
new CheckerPlugin()
],
devtool: 'source-map', // Source maps support ('inline-source-map' also works)
module: {
loaders: [
{
loader: 'url-loader',
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.(ts|tsx)$/,
/\.css$/,
/\.less$/,
/\.ttf/,
/\.woff/,
/\.woff2/,
/\.json$/,
/\.svg$/
],
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
{
loader: 'url-loader',
test: /\.(ttf|woff|woff2)$/
},
{
loader: "style-loader!css-loader!less-loader",
test: /\.less$/
},
{
loader: "style-loader!css-loader",
test: /\.css$/
},
{
loader: "svg-loader",
test: /\.svg$/
},
{
loader: "json-loader",
test: /\.json$/
},
{
loader: "awesome-typescript-loader",
test: /\.(ts|tsx)$/
}
]
}
};
module.exports = config;
I am certain I am missing something. Can anyone help me?
Surely already open followed a tutorial like this
To do this creates a vendor file where you import these types of "global".
./src/vendors.ts;
import "react";
Add this file a to first place at entry parameter:
entry: { 'vendors': './src/vendors.ts', 'main': './src/main.ts' }
And add CommonChunkPlugins:
plugins: [ new CommonsChunkPlugin({
name: 'vendors'
}),
Like this in AngularClass with polyfills.

Categories