2

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.

C-Dev
  • 425
  • 1
  • 6
  • 15
  • So the error message says the problematic file is `./src/index.js`. I note that this has the `.js` extension, although it contains jsx in it. If you rename this file to `index.jsx`, does that help at all? – CRice Apr 08 '22 at 17:46
  • Hi @CRice, I tried renaming to index.jsx and updating the entry point in webpack.config.common to './src/index.jsx' but I get a different error message: ``` Field 'browser' doesn't contain a valid alias configuration ``` What I found about this new error is that webpack is somehow not resolving the file. I do have a 'resolve' option in the config file though so I am not sure if this is part of the same issue that there might be something wrong with the webpack config file? – C-Dev Apr 08 '22 at 18:16
  • I'm not sure, nothing looks wrong with your webpack config to me. Do any of the answers to [this question](https://stackoverflow.com/questions/43037590/field-browser-doesnt-contain-a-valid-alias-configuration) help at all? – CRice Apr 08 '22 at 20:09
  • Thank you for the link. I checked my import paths again and read through the Webpack resolver API documentation. I have tried removing the alias and adding different aliases, but the second error for index.jsx remains. I seems like webpack is not seeing that I am passing in the extension '.jsx' to the resolver. I console logged the merged webpack config files and they are returned together in one json object. So the config merge works, but the resolver is not working on build. I cannot see what I am doing wrong here though – C-Dev Apr 08 '22 at 21:51
  • 3
    In case anyone is running into the same issue. I solved the problem by breaking out index.js into a main.js file to export the components and kept index.js as the file the webpack server looks to. Just importing components in index.js. I also removed ```optimization: { runtimeChunk: 'single', },``` from webpack.config.common.js – C-Dev May 18 '22 at 22:07

0 Answers0