Workspace配置实战:从基础到高级的完整指南

佳佳酱~ 工具 阅读 2,611
赞 22 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

搞前端这几年,Workspace 配置这事儿我踩过不少坑。一开始以为就是个简单的路径映射,后来发现配不好,热更新慢、模块解析错、甚至本地开发环境直接跑不起来。现在我基本固定了一套配置方式,虽然不是最优雅的,但稳定、可维护、团队协作不打架,分享出来。

Workspace配置实战:从基础到高级的完整指南

我现在的项目基本都用 Vite + pnpm + monorepo 结构,所以 Workspace 配置主要围绕 pnpm-workspace.yaml 和 Vite 的 alias 来做。核心原则就一条:别让依赖和路径打架

先看我的 pnpm-workspace.yaml

packages:
  - 'packages/*'
  - 'apps/*'
  - '!**/test/**'

这个写法简单明了,把所有子包都纳入管理,但排除测试目录。注意那个 !,很多人漏掉,结果把 node_modules 里的测试文件也扫进来了,导致安装变慢甚至冲突。

然后是 Vite 的 vite.config.js,重点在 alias 部分:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@ui': path.resolve(__dirname, '../packages/ui/src'),
      '@utils': path.resolve(__dirname, '../packages/utils/src')
    }
  }
})

这里我不用相对路径(比如 ../../../packages/ui),因为一旦文件层级深了,改起来头疼,而且容易出错。用 path.resolve 绝对路径,一劳永逸。虽然看起来啰嗦点,但后期维护成本低很多。

另外,别把 alias 名称和 npm 包名搞混。比如你有个包叫 @myorg/utils,就别再 alias 成 @utils,容易混淆。我习惯用 @xxx 前缀表示内部模块,和外部依赖区分开。

这几种错误写法,别再踩坑了

下面这些是我见过或者自己踩过的雷,列出来帮大家避避:

  • 用相对路径硬编码 alias:比如在某个组件里写 import Button from '../../../../packages/ui/Button'。这种写法短期能跑,但一旦目录结构调整,整个项目可能崩掉。而且 ESLint 会报一大堆警告,看着就烦。
  • Workspace 范围太宽:有人直接写 packages: ['*'],结果把根目录下的 node_modulesdist.git 全扫进去了。pnpm 安装时会卡死,或者报奇怪的权限错误。一定要用 ! 排除无关目录。
  • alias 指向 dist 而不是 src:为了“方便”,有人把 alias 指向编译后的 dist 目录。结果每次改 UI 组件都要手动 build,热更新完全失效。开发体验直接降级到石器时代。
  • 不同项目 alias 不一致:团队里 A 用 @components,B 用 @cmp,C 直接写相对路径。代码 review 时看得人眼花,合并后还得统一重构。从第一天就定好规范,比后期补救省十倍力气。

还有一个隐藏坑:TypeScript 的 paths 没和 Vite alias 同步。比如你在 tsconfig.json 里配了:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@ui/*": ["../packages/ui/src/*"]
    }
  }
}

但 Vite config 里没配,或者路径不一样。结果 TS 编译通过,但浏览器跑不起来,或者反过来。我建议只维护一份路径映射,要么全靠 Vite(配合 vite-tsconfig-paths 插件),要么全靠 TS。我选前者,因为 Vite 是运行时入口,更关键。

实际项目中的坑

上周刚处理一个线上问题:本地开发一切正常,但 CI 构建失败,报 Cannot find module '@ui/Button'。折腾了半天,发现是 Docker 镜像里没跑 pnpm install,导致 workspace link 没生效。后来在 Dockerfile 里加了:

RUN pnpm install --frozen-lockfile

才解决。所以提醒一句:本地能跑 ≠ 线上能跑。CI/CD 环境一定要模拟真实安装流程,不能只 copy node_modules。

还有一次,两个子包互相依赖,比如 app-a 依赖 utilsutils 又不小心 import 了 app-a 的某个工具函数。结果循环依赖,打包时直接 stack overflow。这种问题在 monorepo 里特别隐蔽,因为本地开发时 pnpm 会软链接,看不出问题。建议用 madge 这类工具定期检查依赖图:

npx madge --circular apps/app-a

如果输出非空,就得赶紧拆了。

另外,别在 package.json 里写绝对路径。比如:

{
  "main": "/Users/xxx/project/packages/ui/dist/index.js"
}

这种路径提交到 Git,别人 clone 下来直接废掉。一律用相对路径,比如 "main": "dist/index.js",配合 pnpm 的 workspace 协议自动解析。

小技巧:调试 alias 是否生效

有时候配了 alias 但没生效,怎么快速排查?我一般在入口文件加一行:

console.log('Resolved path:', import.meta.url)

然后看浏览器控制台输出的路径是不是你期望的。或者更暴力点,在 alias 指向的文件里加个 console.error('I am loaded!'),看会不会触发。

如果还是不行,检查 Vite 插件顺序。有些插件(比如 legacy)会干扰 resolve,确保 alias 配置在 plugins 之前处理。

结尾

以上是我总结的最佳实践,有更好的方案欢迎评论区交流。其实 Workspace 配置没有银弹,关键是在团队里达成一致,然后严格执行。我这套方案虽然啰嗦点,但上线半年没出过路径相关的问题,值了。

这个技巧的拓展用法还有很多,比如结合 Turborepo 做增量构建,或者用 changesets 管理版本。后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论