My goal: I am creating a React component library in JavaScript with Webpack 5 and Babel 7 that is published to npm. I want consumers to import individual components into their React apps. I also want to add a development server for stakeholders to add components to the component library.
When running the app locally with 'npm run build && npm run serve' (npx webpack serve) I keep getting this error message:
ERROR in ./src/index.js 20:2
Module parse failed: Unexpected token (20:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| root.render(
> <App />
| );
Here is my setup: ./webpack.config.common.js
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
MyCoolButton: './src/components/buttons/MyCoolButton',
RandomButton: './src/components/buttons/RandomButton',
StyledButton: './src/components/buttons/StyledButton',
theme: './src/tokens/Theme'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
runtimeChunk: 'single',
},
resolve: {
alias: {
path: path.resolve(__dirname, 'src')
},
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
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",
],
},
{
// The test property identifies which file or files should be transformed
test: /\.(js|jsx)$/,
resolve: {
extensions: [".js", ".jsx"]
},
exclude: /[\\/]node_modules[\\/]/,
// The use property indicates which loader should be used to do the transforming.
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
},
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
],
},
};
./webpack.config.prod.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.config.common');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = merge(commonConfig, {
mode: 'production',
// do not include source maps on prod build
// devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
// Load css to separate file
MiniCssExtractPlugin.loader,
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
},
],
},
optimization: {
// minimize: true,
minimizer: [
// For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
// `...`,
new CssMinimizerPlugin({
test: /\.foo\.css$/i,
}),
new TerserPlugin({
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the `compress` and `output`
ecma: 8,
},
compress: {
ecma: 5,
inline: 2,
},
mangle: {
// Find work around for Safari 10+
safari10: true,
},
output: {
ecma: 5,
comments: false,
}
},
// Use multi-process parallel running to improve the build speed
parallel: true,
}),
],
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'styles.[contenthash].css',
})
],
// As you publish your code as React component, apart from set React as peer dependency you have to set the react as externals to use the react at the consumer library.
externals: {
'react': {
commonjs: 'react',
commonjs2: 'react',
amd: 'React',
root: 'React'
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'ReactDOM',
root: 'ReactDOM'
}
},
});
./babel.config.js
{
"plugins": [
"babel-plugin-module-resolver"
],
"presets": [
["@babel/preset-env"], // instead of "@babel/preset-es2015",
["@babel/preset-react"]
]
}
./src/index.js
export {default as MyCoolButton} from './components/buttons/MyCoolButton';
export {default as RandomButton} from './components/buttons/RandomButton';
export {default as StyledButton} from './components/buttons/StyledButton';
export {theme} from './tokens/Theme';
import './tokens/theme.scss'; // need import to generate minified css flat file
// kitchen sink for component development
import React from "react";
import { createRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('app');
const root = createRoot(rootElement);
root.render(
<App />
);
App.jsx
import React, { Component } from 'react';
import StyledButton from "./components/buttons/StyledButton";
import RandomButton from "./components/buttons/RandomButton";
class App extends Component {
render() {
return (
<div>
<div className='container body'>
<div className='row'>
<div className='col-12 text-center mb-4 mt-4'>
<h1>Welcome to the Kitchen Sink Page</h1>
</div>
<div className='col-6'>
<RandomButton />
</div>
<div className='col-6'>
<StyledButton />
</div>
</div>
</div>
</div>
);
}
}
export default App;
and my package.json
...
"peerDependencies": {
"prop-types": "^15.6.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"babel-core": "^6.0.0",
"webpack": "^4.0.0"
},
"devDependencies": {
"@babel/cli": "^7.17.0",
"@babel/core": "^7.17.2",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/npm": "^9.0.0",
"@semantic-release/release-notes-generator": "^10.0.3",
"@testing-library/react": "^12.1.2",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^7.1.5",
"babel-plugin-module-resolver": "^4.1.0",
"commitizen": "^4.2.4",
"css-loader": "^6.6.0",
"css-minimizer-webpack-plugin": "^3.4.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.8.0",
"eslint-plugin-jest": "^26.1.0",
"eslint-plugin-react": "^7.28.0",
"jest": "^27.5.0",
"path": "^0.12.7",
"prop-types": "^15.6.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sass": "^1.49.7",
"sass-loader": "^12.4.0",
"semantic-release": "^19.0.2",
"style-loader": "^1.3.0",
"styled-components": "^5.3.3",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"babel-plugin-styled-components": "^2.0.2",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^4.0.0",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.5.3",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"terser-webpack-plugin": "^5.3.1",
"webpack-merge": "^5.8.0"
},
...
I have browsed countless stackoverflow posts and answers, blog posts and I am having a hard time debugging this 'unexpected token' error.
I am new to Webpack so if anyone can see if I am doing something wrong in the Webpack config files, in way the React app is being bundled, or by loading Babel loaders, I would really appreciate some pointers.