从Craco迁移至Webpack后React运行异常求助
从Craco迁移到Webpack后React控制台异常排查
问题背景
原本运行正常的React应用,从craco.config.js迁移到自定义webpack.config.js后,启动无报错,但控制台出现大量React组件渲染相关异常(如React.createElement: type is invalid -- expected a string or a class/function but got: undefined),未修改任何业务代码,仅调整了配置,本地启动测试阶段无法定位问题。
当前Webpack配置(webpack serve)
const path = require("path"); const platformDir = path.resolve("../../../../../platform-web"); const sourceDir = path.resolve("./src"); const publicDir = path.resolve("./public"); const resourcesFile = path.join(sourceDir, "styles/_resources.scss"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { HotModuleReplacementPlugin } = require("webpack"); module.exports = { mode: "development", entry: path.join(sourceDir, "index.ts"), output: { path: path.resolve(__dirname, "build"), filename: "[name].[contenthash:8].js" }, resolve: { mainFiles: ["index"], extensions: [".tsx", ".ts", ".js", ".jsx"], modules: [ path.resolve("./src"), "./node_modules", path.join(platformDir, "./node_modules") ], alias: { resources: resourcesFile, "@platform-web": path.resolve(__dirname, "../../../../../platform-web/src") }, }, plugins: [ new HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: path.join(publicDir, "index.html") }) ], module: { rules: [ { test: /\.(ts|tsx|js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader" }, }, { test: /\.css$/i, use: ["style-loader", "css-loader"] }, { test: /\.s[ac]ss$/i, use: [ "style-loader", "css-loader", { loader: "sass-loader", options: { sassOptions: { includePaths: [path.resolve(platformDir, "./src/styles"), "./src/styles"], }, }, }, ], }, { test: /\.(png|jpe?g|woff2)$/i, use: { loader: "file-loader" } }, { test: /\.svg$/, use: ["@svgr/webpack"] } ], }, devServer: { port: 5100, hot: true, proxy: [{ context: ["/_api"], target: "https://some.url/", changeOrigin: true }], }, };
Babel配置(babel.config.js)
module.exports = { presets: [ ["@babel/preset-env", { modules: false, }], ["@babel/preset-react", {"runtime": "automatic"}], "@babel/preset-typescript" ], plugins: [ ["@babel/plugin-syntax-import-attributes", { "legacy": true }], ["@babel/plugin-transform-class-properties", { "loose": true }], ["@babel/plugin-transform-private-property-in-object", { "loose": true }], ["@babel/plugin-transform-private-methods", { "loose": true }] ] };
原Craco配置(craco.config.js)
const path = require("path"); const CracoAlias = require("craco-alias"); const platformDir = path.resolve("../../../../platform-web"); const platformSrc = path.join(platformDir, "src"); const sourceDir = path.resolve("./src"); const resourcesFile = path.join(sourceDir, "styles/_resources.scss"); module.exports = { reactScriptsVersion: "react-scripts", plugins: [ { plugin: CracoAlias, options: { source: "tsconfig", baseUrl: "./src", tsConfigPath: "./tsconfig.extend.json", }, }, ], style: { css: { loaderOptions: { modules: { auto: true, exportLocalsConvention: "camelCase", }, }, }, }, webpack: { configure: (cfg) => { const scopePluginIndex = cfg.resolve.plugins.findIndex( ({ constructor }) => constructor && constructor.name === "ModuleScopePlugin" ); cfg.resolve.modules = [ sourceDir, path.resolve("./node_modules"), path.join(platformDir, "./node_modules"), ]; cfg.resolve.alias = { resources: resourcesFile }; cfg.resolve.plugins.splice(scopePluginIndex, 1); const oneOfRule = cfg.module.rules.find((rule) => rule.oneOf); const tsRule = oneOfRule.oneOf.find((rule) => rule.test.toString().includes("ts|tsx") ); tsRule.include = [sourceDir, platformSrc]; tsRule.exclude = /node_modules/; return cfg; }, }, babel: { plugins: [ ["@babel/plugin-syntax-import-attributes", { "legacy": true }], ["@babel/plugin-transform-class-properties", { "loose": true }], ["@babel/plugin-transform-private-property-in-object", { "loose": true }], ["@babel/plugin-transform-private-methods", { "loose": true }] ], presets: [ ["@babel/preset-env", { modules: false, //Setting this to false will preserve ES services }], ["@babel/preset-react", {"runtime": "automatic"}], "@babel/preset-typescript" ], }, devServer: { port: 5100, proxy: { "/_api": { target: "https://some.url/", secure: true, changeOrigin: true }, }, } };
排查与修复方案
1. 补全跨目录代码的转译范围
原Craco配置中明确将platform-web/src加入了转译范围,但当前Webpack的JS/TS规则未包含该目录,导致外部组件未被Babel处理,出现类型无效错误。修改Webpack的module.rules:
{ test: /\.(ts|tsx|js|jsx)$/, include: [sourceDir, path.resolve(__dirname, "../../../../../platform-web/src")], exclude: /node_modules/, use: { loader: "babel-loader" }, }
2. 恢复CSS模块配置
原Craco开启了CSS模块的自动识别和驼峰命名转换,当前Webpack未配置该规则,可能导致样式导入异常间接影响组件。修改css-loader配置:
// CSS规则 { test: /\.css$/i, use: [ "style-loader", { loader: "css-loader", options: { modules: { auto: true, exportLocalsConvention: "camelCase" } } } ] }, // SCSS规则 { test: /\.s[ac]ss$/i, use: [ "style-loader", { loader: "css-loader", options: { modules: { auto: true, exportLocalsConvention: "camelCase" } } }, { loader: "sass-loader", options: { sassOptions: { includePaths: [path.resolve(platformDir, "./src/styles"), "./src/styles"], }, }, }, ], }
3. 确认React运行时依赖
@babel/preset-react启用runtime: "automatic"时,需确保安装了react/jsx-runtime依赖,执行以下命令确认:
npm install react react-dom @babel/plugin-transform-react-jsx-runtime --save
4. 同步别名与TS配置
原Craco通过craco-alias读取tsconfig的别名,当前Webpack手动配置的别名需与tsconfig.extend.json完全一致,避免导入路径错误导致组件找不到。
5. 添加开发环境SourceMap
在Webpack配置中添加devtool,方便定位错误位置:
module.exports = { // ...其他配置 devtool: "eval-cheap-module-source-map" }
内容的提问来源于stack exchange,提问作者fetixzz
相关产品推荐
相关产品推荐

