1. 为什么需要共享node_modules做过前端开发的朋友应该都深有体会每次新建一个Vue项目光是安装依赖就要等上半天。特别是当你同时维护多个Vue项目时每个项目都要单独安装一遍node_modules不仅占用大量磁盘空间还浪费宝贵的时间。更头疼的是如果这些项目依赖的版本不一致还可能出现各种奇怪的兼容性问题。我去年接手过一个微前端项目包含5个Vue子应用每个子应用都单独维护自己的node_modules。结果光是node_modules就占用了近10GB空间每次拉取代码都要等很久。更糟的是不同子应用使用的Vue版本从2.6到2.7不等导致组件库在不同子应用中表现不一致调试起来特别痛苦。这时候npm workspaces就派上用场了。它允许我们在一个根目录下管理多个子项目所有子项目共享同一个node_modules目录。实测下来原本5个项目需要安装5遍的依赖现在只需要安装1遍磁盘空间节省了80%安装时间也大幅缩短。而且所有子项目强制使用相同版本的依赖彻底解决了版本不一致的问题。2. 准备工作搭建Monorepo工程结构2.1 创建项目根目录首先我们需要创建一个项目根目录这个目录将作为我们整个Monorepo工程的容器。我习惯在项目根目录下创建packages文件夹用来存放各个子项目。mkdir vue-monorepo cd vue-monorepo mkdir packages2.2 初始化根package.json在根目录下运行npm init -y创建package.json文件。这个文件将作为整个工作区的配置中心。关键是要添加workspaces字段指定哪些子目录需要共享node_modules。{ name: vue-monorepo, version: 1.0.0, workspaces: [ packages/* ], private: true }这里有几个需要注意的点private: true是必须的防止意外发布整个Monorepoworkspaces支持glob模式匹配packages/*表示packages下的所有子目录都会参与工作区建议使用npm 7版本它对workspaces的支持最完善2.3 创建Vue子项目现在可以在packages目录下创建各个Vue项目了。我通常会使用Vue CLI创建标准项目cd packages vue create project1 vue create project2创建完成后记得删除子项目自带的node_modules因为我们要使用工作区共享的node_modules。3. 配置依赖共享3.1 统一安装依赖现在可以在根目录下统一安装所有子项目需要的依赖了。npm会自动处理依赖关系相同的依赖只会安装一次。npm install vue2.7.14 vue-router3.5.1 -W-W参数表示将依赖安装到工作区根目录。如果某个依赖是某个子项目特有的可以使用-w参数指定具体项目npm install lodash -w project13.2 处理Vue CLI的特殊配置Vue CLI项目默认会在项目根目录下查找node_modules我们需要修改vue.config.js来适配工作区结构const path require(path) module.exports { configureWebpack: { resolve: { modules: [ path.resolve(__dirname, ../../node_modules), node_modules ] } } }这个配置告诉webpack先在上级目录的node_modules中查找依赖如果找不到再使用项目本地的node_modules虽然我们一般不保留本地node_modules。3.3 解决Git提交问题使用工作区后你可能会遇到Git提交时的hook问题。这是因为像husky这样的工具会在每个项目中查找node_modules。解决方法是在根目录安装这些工具npm install husky lint-staged -D -W然后在根package.json中配置{ scripts: { prepare: husky install }, lint-staged: { *.{js,vue}: eslint --fix } }4. 高级配置技巧4.1 处理peerDependencies警告当不同子项目依赖的版本有差异时可能会遇到peerDependencies警告。这时可以在根package.json中使用overrides字段强制统一版本{ overrides: { vue: 2.7.14, vue-router: 3.5.1 } }4.2 优化构建性能为了进一步提升开发体验可以配置Vite来利用Monorepo的优势。首先在根目录安装Vitenpm install vite -D -W然后在子项目的vite.config.js中import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, ./src), vue: path.resolve(__dirname, ../../node_modules/vue) } } })4.3 跨项目引用组件在工作区中你可以直接引用其他子项目的组件而不需要发布到npm。只需要在tsconfig.json或vite.config.js中配置路径别名// vite.config.js export default defineConfig({ resolve: { alias: { project1: path.resolve(__dirname, ../project1/src) } } })然后在代码中就可以直接引用了import { Button } from project1/components5. 常见问题排查5.1 依赖找不到的问题如果遇到Module not found错误首先检查依赖是否确实安装在根node_modules中vue.config.js中的resolve.modules配置是否正确尝试删除所有node_modules和lock文件后重新安装5.2 版本冲突问题当不同子项目需要不同版本的依赖时可以考虑尽量统一版本将冲突的依赖安装到子项目本地不推荐使用npm overrides强制指定版本5.3 IDE支持问题WebStorm等IDE可能需要额外配置才能正确识别工作区将根目录标记为Project Root在设置中启用npm workspaces支持可能需要手动配置JavaScript库路径我在实际项目中遇到过VSCode无法正确跳转到依赖定义的问题解决方法是在根目录下创建jsconfig.json{ compilerOptions: { baseUrl: ., paths: { *: [node_modules/*] } } }6. 最佳实践建议经过多个项目的实践我总结出以下几点经验保持依赖版本一致这是工作区能正常工作的前提建议使用npm overrides强制统一版本合理规划项目结构将紧密相关的项目放在同一个工作区不相关的项目最好分开管理善用符号链接npm workspaces底层使用符号链接理解这一点有助于排查问题统一工具链所有子项目应该使用相同版本的构建工具和代码规范渐进式迁移对于已有项目可以逐步迁移到工作区不必一次性完成一个典型的生产级Monorepo目录结构可能长这样vue-monorepo/ ├── package.json ├── packages/ │ ├── app1/ # 主应用 │ ├── app2/ # 子应用 │ ├── components/ # 共享组件库 │ └── utils/ # 共享工具库 ├── config/ │ ├── eslintrc.js │ └── jest.config.js └── scripts/ # 共享脚本这种结构既保持了项目的独立性又能最大化共享代码和配置。