JS 开发全栈项目,一个 pnpm 仓库就搞定!

246 阅读4分钟

本项目代码已开源,具体见:fullstack-blog

数据库初始化脚本:关注公众号程序员白彬,回复关键字“博客数据库脚本”,即可获取。

前言

当前,这个全栈博客项目已经改为了 pnpm monorepo 架构,前端和后端代码都在同一个代码仓中,无论是否使用 docker compose 都可以做到一键启动,一键 build 等,开发体验较好。当然,这只是 monorepo 的其中一个优点。

我们采用的是前后端分离架构,在未改用 pnpm monorepo 架构之前,前后端代码是独立的工程。在开发环境下,我们在后端工程目录下通过命令npm start启动 PM2 开发服务,在前端工程目录下通过命令yarn serve启动 Vue CLI 开发服务,开发服务中有 devServer 类的配置,其中有 proxy 将接口请求路径反向代理到后端服务,然后就可以开发联调了。

然而,这种体验仍然是割裂的。我们虽然推崇在技术架构上将前后端做一个分离解耦,但这并不是说它们不是一个整体。特别是在采用容器化部署时,各个环境变量,端口关系都要作为一个整体来看待,避免由于配置分散导致的不一致问题。

不过,仅仅从文字上描述还是过于生硬,具体还是得从实践中去感受和发现问题。

具体改造

项目初始化

使用pnpm init就可以创建一个初始项目,接着我们要新建一个pnpm-workspace.yaml以搭建起 monorepo 的雏形。这里主要是声明packages,用于表明工作空间的哪些目录下存放着我们的包。一个包可以理解为一个子项目工程,也可以理解为一个 library。按照惯例,可以将所有的包的放在 packages 目录下。如果你希望将项目和library 区分开,通常可以设置 app 和 packages 两个目录,app 下放置各个子项目,packages 下放置各个库。

packages:
  - "packages/*"
  - "app/*"

接着我们把原来独立的前后端工程分别迁入到 app 目录下,其中 frontend 是前端工程,backend 是后端工程。

├─app
│  ├─frontend
│  ├─backend

当然,命名是比较随意的,你完全可以按照自己的喜好来命名。以本项目为例,由于项目中会采用 vue, react 等不同技术实现前端,我会以技术栈的特征来命名。

├─app
│  ├─webpack-vue3
│  ├─vite-vue3
│  ├─cra-react18
│  ├─express-server

其中前三个都是前端的实现,采用了不同的前端框架。第四个就是后端的实现,目前仅仅实现了 express.js 版本。

lock 文件的变化

采用 pnpm monorepo 架构后,一个显著的特点是 lock 文件的变化。使用独立仓库时,各个项目仓库都有自己的 lock 文件;而现在,依赖版本信息都统一由工程根目录下的 pnpm-lock.yaml 来管理。

workspace 协议

pnpm 有着完善的 workspace 协议支持,你可以通过workspace:来引用pnpm-workspace.yaml文件下囊括的各个包。

以本项目为例,我们将公共的一些 eslint 配置提取到了packages/eslint-config这个目录下,包名定义为@fullstack-blog/eslint-config。接着在各个子项目下都可以引用这个包,不用特意去声明这个包的版本号。举例如下:

"@fullstack-blog/eslint-config": "workspace:^"

这仅仅是以 eslint 公共配置为例进行说明,如果你的项目中各个子项目下有很多重复的逻辑,可以提取出 utils, hooks 之类的子包进行复用。

更便捷的脚本

在使用前后端独立工程开发时,我们需要分别在前端和后端的工程下去运行开发脚本,然后进行开发。使用 pnpm monorepo 后,我们可以在前后端项目提供 dev 或者 serve 命令作为开发脚本,然后在根目录下的 packages.json 中定义一键启动前后端服务的脚本,比如:

"fullstack:dev": "pnpm --filter vite-vue3 --filter express-server dev"

这个脚本通过 pnpm 的 --filter 选项筛选出要执行 dev 命令的前后端工程,批量启动对应的开发服务。

容器化的考量

在前面的章节里,我们学会了怎么使用 Docker 运行前后端项目,具体可以参考下面这些文章。

结合以上文章综合来看,前后端的 Docker 操作还是独立的,联系不够紧密,并且对于使用 docker compose 来说也是不太友好的。针对容器化这部分,我们后面单独开一篇文章来详细介绍。