4

I have a React application with a page importing a component from another file.

// code from IconPage.tsx

import {AccountBalanceIcon} from './icons';

<AccountBalanceIcon />
// Code from ./icons.ts
export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance";

This works fine when the exporting file is a typescript (.ts) file, however when I rename the file to .js, I get significantly different behavior.

// Code from ./icons.js
export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance";

This errors during webpack's build step

Module not found: Error: Can't resolve '@material-ui/icons/AccountBalance' in '.../src/pages'
Did you mean 'AccountBalance.js'?
BREAKING CHANGE: The request '@material-ui/icons/AccountBalance' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

Yes, I'm using "type": "module" in my package.json, so I refactor the code so it compiles by adding .js extension.

// Code from ./icons.js
export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance.js";

Once this is done, webpack builds, but I get the familiar React error in the browser console when you mess up your import statement.

react-dom.development.js:25058 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 `IconPage`.
    at createFiberFromTypeAndProps (react-dom.development.js:25058:1)
    at createFiberFromElement (react-dom.development.js:25086:1)
    at createChild (react-dom.development.js:13446:1)

Renaming the file back to .ts (and even keeping the AccountBalance.js in place), resolves the runtime error.

Analysis

The reason for the discrepancy is shown in the build code.

As a JavaScript source file, the resulting code webpack produces is as follows.

    __webpack_require__.d(__webpack_exports__, {
        "AccountBalanceIcon": () =>
            (_material_ui_icons_AccountBalance_js__WEBPACK_IMPORTED_MODULE_0__)
    });

And the typescript output, looks like this.

    __webpack_require__.d(__webpack_exports__, {
        "AccountBalanceIcon": () =>
           (_material_ui_icons_AccountBalance_js__WEBPACK_IMPORTED_MODULE_0__["default"])
    });

Note, the typescript source references the imported module's .default property, while the JavaScript source code refers to the module itself.

tsc --showConfig

{
    "compilerOptions": {
        "target": "es2017",
        "jsx": "react",
        "experimentalDecorators": true,
        "module": "esnext",
        "moduleResolution": "node",
        "allowJs": true,
        "outDir": "./dist",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    },
    "include": [
        "./src/**/*"
    ],
    "exclude": [
        "./test/**/*"
    ]
}

webpack.config.js

import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";

const pathResolve = (path) => new URL(path, import.meta.url).pathname;

export default {
  mode: process.env.NODE_ENV === "production" ? "production" : "development",
  devtool: "inline-source-map",
  entry: "./src/index.tsx",
  output: {
    path: pathResolve("dist"),
    filename: "bundle.js",
    publicPath: '/',
  },
  devServer: {
    historyApiFallback: true,
    hot: true,
    port: 3000,
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
    rules: [
      { test: /\.tsx?$/, exclude: /node_modules/, loader: "ts-loader"},
      { test: /\.js$/, use: ["source-map-loader"], enforce: "pre" },
      { test: /\.css$/, use: ['style-loader', 'css-loader']},
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
    ],
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: pathResolve("./public/index.html"),
      filename: "index.html"
    }),
    new MiniCssExtractPlugin({filename: "app.css"}),
  ],
  ignoreWarnings: [/Failed to parse source map/],
};

I expect my problem is actually related to one of these config files.

Question

How can I get the JavaScript file to export the Material-UI component cleanly for JS and TS projects to consume the export?

I ask because the icons.ts file in this example is actually a simplification to show the problem. The code for the icons is part of a larger component library which is packaged and distributed as transpiled JS files.

oravecz
  • 1,146
  • 11
  • 16
  • Does the code work if you split the export? e.g. `import AccountBalanceIcon from '@material-ui/icons/AccountBalance.js';` And then later `export { AccountBalanceIcon }` – Casper Kuethe Jul 18 '22 at 15:30
  • Could u share `@material-ui/icons/` folder structure screenshot? – Toni Bardina Comas Jul 18 '22 at 15:37
  • Why do you re-export icons? Just import it directly `import{ AccountBalance as AccountBalanceIcon } from "@material-ui/icons"`; – sultan Jul 18 '22 at 20:24

1 Answers1

1

instead of this:

export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance";

try:

export { AccountBalance as AccountBalanceIcon } from "@material-ui/icons";
akjpmc
  • 11
  • 1