
本文详解如何修复使用 Rollup 构建 React 设计系统时,因 @mui/material/styles 模块解析失败导致的 ESM 导入错误,重点配置 Rollup 插件链与 TypeScript 编译选项以兼容 MUI 的模块规范。
本文详解如何修复使用 rollup 构建 react 设计系统时,因 `@mui/material/styles` 模块解析失败导致的 esm 导入错误,重点配置 rollup 插件链与 typescript 编译选项以兼容 mui 的模块规范。
在构建可复用的 React 设计系统包(如基于 Material UI)时,一个常见但棘手的问题是:组件导出正常,但主题相关模块(如 createTheme、ThemeProvider)在消费项目中报错——典型错误为:
Module not found: Error: Can't resolve '@mui/material/styles' ... BREAKING CHANGE: The request failed to resolve only because it was resolved as fully specified. Add the extension to the request.
该错误本质源于 ESM 严格解析规则 与 MUI v5.13+(尤其是其 ESM 构建产物)的交互问题:当 Rollup 打包后的 .js 文件被标记为 "type": "module"(或运行在原生 ESM 环境),Node.js 要求所有裸导入(bare imports)必须显式声明扩展名(如 .js),而 @mui/material/styles 是一个无扩展名的包入口,其内部依赖链可能未完全适配此约束。
✅ 核心解决方案:调整 Rollup 插件顺序与配置
关键在于确保 @rollup/plugin-node-resolve 在 @rollup/plugin-commonjs 之前 执行,并启用 exportConditions 显式支持 ESM 条件导出。同时,需将 @mui/* 相关包设为外部依赖(peer deps),避免重复打包引发路径错乱。
以下是经过验证的 rollup.config.js 配置要点(含注释说明):
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import typescript from "@rollup/plugin-typescript";
import postcss from "rollup-plugin-postcss";
import url from "@rollup/plugin-url";
import svgr from "@svgr/rollup";
import dts from "rollup-plugin-dts";
import packageJson from "./package.json";
export default [
{
input: "./src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
// ✅ 必须最先:将 peer dependencies(如 @mui/material)排除在 bundle 外
peerDepsExternal(),
// ✅ 关键:启用 ESM 条件解析,匹配 MUI 的 package.json exports 字段
resolve({
exportConditions: ["module", "import", "require"],
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
// ✅ 其次:转换 CommonJS 模块(如某些 MUI 内部依赖)
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
postcss(),
url(),
svgr(),
],
},
// 类型声明文件单独生成
{
input: "./build/dts/index.d.ts",
output: [{ file: "build/index.d.ts", format: "es" }],
plugins: [dts()],
},
];? 为什么
resolve必须在commonjs之前?
若commonjs先执行,它会尝试将@mui/material/styles当作 CJS 模块处理,但该路径实际指向一个 ESM 入口(如index.js),导致解析失败。resolve提前识别并正确解析其 ESM 条件导出后,commonjs才能安全跳过它。
?️ TypeScript 配置强化(tsconfig.json)
确保 moduleResolution 和 module 与 Rollup 输出格式对齐,并启用严格的类型生成:
{
"compilerOptions": {
"target": "es2018",
"lib": ["es2018", "dom", "dom.iterable"],
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationDir": "build/dts",
"emitDeclarationOnly": true,
"jsx": "react-jsx",
"noEmit": false, // ⚠️ 注意:Rollup 负责编译,此处可设 false;若用 tsc 生成 .d.ts 则保留 true
"outDir": "./build"
},
"include": ["src"],
"exclude": ["node_modules", "build"]
}? 重要注意事项
-
不要重写 MUI 导入路径:如将
@mui/material/styles改为@mui/material/styles/index.js—— 这虽临时绕过错误,但破坏了语义化版本兼容性,且在不同环境(如 Vite、Webpack 5+)下行为不一致。 -
验证 peerDependencies:确保
design-system的package.json中明确声明:"peerDependencies": { "@mui/material": "^5.14.0", "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0" } -
消费端需安装对应 peer deps:下游项目必须手动安装
@mui/material等,否则即使构建通过,运行时仍会缺失样式注入逻辑。 -
CSS-in-JS 支持:
rollup-plugin-postcss需配置inject: false(默认)并确保@emotion/react正确注入<cacheprovider></cacheprovider>,否则主题样式不会生效。
✅ 验证是否成功
构建后检查 dist/esm/index.js 中对 @mui/material/styles 的引用是否保留为原始路径(未被重写),并在消费项目中执行:
// ✅ 正确用法(无需修改路径)
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { DesignSystemButton } from 'design-system';
const theme = createTheme({ palette: { primary: { main: '#007bff' } } });
function App() {
return (
<ThemeProvider theme={theme}>
<DesignSystemButton>Click me</DesignSystemButton>
</ThemeProvider>
);
}若控制台无 Module not found 错误,且按钮渲染符合主题定义,则配置已生效。
通过上述 Rollup 插件协同与 TypeScript 精准配置,即可彻底解决 MUI 主题在设计系统包中的 ESM 加载异常,为多项目共享高质量 UI 奠定坚实基础。

















