基于React+Lerna+Storybook的Monorepo组件库Symlinking问题求助
我之前在维护基于Lerna+Storybook的React组件库时也遇到过几乎一模一样的问题,分享下我踩坑后总结的解决方案,应该能帮你搞定:
1. 正确声明包之间的依赖关系
首先要在SlideoutMenu包的package.json里,明确添加对Button包的依赖。注意要用你组件包的实际包名(比如@your-ui/button),版本号可以用*或者Lerna能识别的版本范围(比如^1.0.0),这样Lerna会自动处理内部依赖的软链接:
// packages/slideout-menu/package.json "dependencies": { "@your-ui/button": "^1.0.0" }
另外,一定要把React、ReactDOM这类公共依赖放在每个组件包的peerDependencies里,避免多实例导致的hooks报错:
"peerDependencies": { "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }
2. 用Lerna建立可靠的软链接
不要手动用npm link,直接用Lerna自带的命令来处理内部包的链接,它会自动遍历所有包,把相互依赖的包创建软链接到对方的node_modules里:
lerna link
如果你的Monorepo是用lerna init初始化的,这个命令应该能一次性搞定所有内部依赖的链接。
3. 修复Storybook的React重复加载问题
这是软链接场景下最常见的坑:因为软链接的包会加载自己node_modules里的React,导致出现多个React实例,进而触发Invalid hook call之类的错误。解决方法是在Storybook的webpack配置里,强制所有包都使用根目录的React副本:
在.storybook/main.js里添加webpackFinal配置:
const path = require('path'); module.exports = { // 其他Storybook配置... webpackFinal: async (config) => { config.resolve.alias = { ...config.resolve.alias, // 强制指向根目录的React react: path.resolve(__dirname, '../node_modules/react'), 'react-dom': path.resolve(__dirname, '../node_modules/react-dom'), }; return config; }, };
4. 确保组件包的入口配置正确
每个组件包的package.json里,main(CommonJS入口)或module(ES模块入口)要正确指向组件的编译后文件或源码文件。比如Button包的配置:
// packages/button/package.json "main": "dist/index.js", "module": "src/index.js", // 开发模式下可以直接用源码,减少编译步骤 "scripts": { "build": "babel src --out-dir dist", "watch": "babel src --out-dir dist --watch" // 开发时监听文件变化自动编译 }
5. 启动开发/预览模式
开发时,先启动Button包的监听编译(如果需要编译的话),再启动Storybook:
# 启动Button包的监听编译 lerna run watch --scope @your-ui/button # 启动Storybook(在根目录或SlideoutMenu包目录) npm run storybook
这样修改Button组件的代码时,SlideoutMenu能实时获取到最新的组件代码,Storybook也会自动刷新。
如果还有问题,可以检查下Lerna的配置文件lerna.json,确保packages字段正确包含了你的组件目录:
{ "packages": ["packages/*"], "version": "independent" // 如果你用独立版本控制的话 }
内容的提问来源于stack exchange,提问作者Enjayy

