Webpack下TS与JS默认导出差异致React组件报错解决方案
React 跨JS/TS文件导出Material-UI组件异常问题
问题场景
现有React应用,业务页面从独立文件导入组件渲染,核心代码如下:
// code from IconPage.tsx import {AccountBalanceIcon} from './icons'; <AccountBalanceIcon />
// Code from ./icons.ts export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance";
当导出文件为TypeScript格式(.ts后缀)时,上述代码可正常运行;但将该文件重命名为.js后缀后,程序出现异常,初始JS文件代码如下:
// Code from ./icons.js export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance";
报错流程
- 首次重命名为.js后缀后,Webpack构建阶段直接抛出错误:
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.
- 由于项目
package.json中配置了"type": "module",为导入路径补充.js后缀修复构建错误,修改后的icons.js代码如下:
// Code from ./icons.js export { default as AccountBalanceIcon } from "@material-ui/icons/AccountBalance.js";
- 修改后Webpack可正常完成构建,但浏览器控制台抛出React导入格式错误:
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)
补充现象:如果将文件重命名回.ts后缀(即使保留导入路径中的AccountBalance.js后缀),该运行时错误会自动消失。
根因定位
对比Webpack最终构建产物可定位行为差异的根源:
- 当源文件为JavaScript格式时,Webpack生成的导出逻辑代码如下:
__webpack_require__.d(__webpack_exports__, { "AccountBalanceIcon": () => (_material_ui_icons_AccountBalance_js__WEBPACK_IMPORTED_MODULE_0__) });
- 当源文件为TypeScript格式时,Webpack生成的导出逻辑代码如下:
__webpack_require__.d(__webpack_exports__, { "AccountBalanceIcon": () => (_material_ui_icons_AccountBalance_js__WEBPACK_IMPORTED_MODULE_0__["default"]) });
可见TypeScript编译处理时会自动引用导入模块的.default属性,而JavaScript源文件则直接引用模块对象本身。
现有配置
执行tsc --showConfig输出的TypeScript编译配置如下:
{ "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/], };
初步推测该问题与上述两份配置文件的参数设置相关。
待解决问题
如何配置才能让JavaScript文件正常导出Material-UI组件,同时支持JS和TS项目稳定消费该导出?本示例中的icons.ts是简化后的复现用例,实际场景中图标逻辑属于一个更大的组件库,最终会打包编译为JS文件对外分发。
内容的提问来源于stack exchange,提问作者oravecz
相关产品推荐
相关产品推荐

