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.
Related
I'm working on an outlook addin I have an express server running. I am setting webpack because I need to transpile js to es5 to make it work in Outlook Desktop. Here is the simplified project structure.
/public
/javascripts
ssoAuth.js
/addin
/commmands
commands.js
commands.html
/server
/bin
/helpers
app.js
The public folder is set as a static folder in my express server
app.use(express.static(path.join(__dirname, '../public'),
My problem is in commands.js I import ssoAuth.js with es6 module import with relative path :
import getGraphAccessToken from "/javascripts/ssoAuthES6.js";
It works fine when I run node ./server/app.js and load my outlook addin, but when I want to use Webpack to bundle, the import is not working, I get :
ERROR in ./addin/commands/commands.js
Module not found: Error: Can't resolve '/javascripts/ssoAuth.js'
I can't figure out how to configure webpack to allow the imports from the public folder.
Here are my webpack config files :
webpack.config.js :
const config = {
devtool: "source-map",
entry: {
polyfill: "#babel/polyfill",
commands: "./addin/commands/commands.js"
},
resolve: {
extensions: [".ts", ".tsx", ".html", ".js"]
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"]
}
}
},
{
test: /\.html$/,
exclude: /node_modules/,
use: "html-loader"
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: "file-loader"
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "commands.html",
template: "./addin/commands/commands.html",
chunks: ["polyfill", "commands"]
})
]};
webpack.server.config.js :
return ({
entry: {
server: './server/bin/www',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js'
},
target: 'node',
node: {
__dirname: false,
__filename: false,
},
externals: [nodeExternals()],
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
plugins: [
new CopyWebpackPlugin([
{
to: "./public",
from: "./public"
}
])
]})
Can you help figure this out ? Is there a better folder structure that I should use to make it work ?
Thanks
You're using an absolute path
import getGraphAccessToken from "/javascripts/ssoAuthES6.js";
// ^ this will look in your topmost directory on your OS
The relative path, from commands.js, would be:
import getGraphAccessToken from "../../javascripts/ssoAuthES6.js";
Alternatively, you can set Webpack to look for modules from your root directory by adding the following to your webpack configuration:
{
// ...
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"],
},
// ...
}
Then you can import from your project's root directory from anywhere, like so:
import getGraphAccessToken from "javascripts/ssoAuthES6.js";
Some other points:
Since you're setting the extensions: [".ts", ".tsx", ".html", ".js"], you don't need to provide file extensions for those imports
You specify .ts and .tsx in your webpack config, but you are using .js files. Consider removing the Typescript extensions
If you are using Typescript, you will need to update import paths in your tsconfig.json
You can consider import path aliases in both Webpack and Typescript to be more explicit that your imports are coming from your project root. Instructions here
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')
}
]
}
}
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.
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.
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.