>_kejun
...

使用 Docker Compose + Turborepo 部署 Monorepos & PNPM Workspace 中的 Nuxt3 应用

2023-09-02 11:20

公司有很多项目,基本长得一样,但是也有客制化需求,且技术老大不喜欢也不注重前端工程化,基础设施更是别想了。当然不怪他,怪我菜,没能到一个足够好的公司。

本文我分享的不是最佳实践,纯个人情况和经验,不会适用于大部分人,希望你看完有所收获。

为什么是 Nuxt3

我是一个 Vue 开发者,Nuxt3 推出后就一直在关注,其中的 Layers 功能让我印象深刻。

这个功能总的来说就是你可以让你的 Nuxt 应用继承一个已经存在的 Nuxt3 应用,其中包括:

  • nuxt.config.ts 配置信息
  • 所有 components/ 下的组件
  • 所有 composables/ 下的可组合函数
  • 所有 plugins/ 下的插件
  • 所有 server/ 下的服务器相关逻辑
  • 所有 pages/ 下的页面
  • 所有 middleware/ 下的中间件
  • 所有 layouts/ 下的布局
  • 所有 public/ 下的文件
  • 所有 app/ 下的文件,即 app/router.options
  • app.vue 文件

这些功能很适合我的业务场景,即大量应用都几乎一模一样,但是也有客制化需求。

为此,抽离出一个 package,专门做一个 baseLayer,填充公共内容(api、库配置、公用逻辑和页面等),所有应用继承即可。

Monorepos && PNPM Workspace

项目结构如下:

image.png

这个没什么好说的,其中 packages 中主要为了抽出并复用:api、utils、eslint-config、stylelint-config,apps/ 中才是正菜。

pnpm-workspace.yaml 也很简单:

packages:  - 'apps/*'  - 'packages/*'

服务端渲染 SSR

先说结论,已弃用,原因是太消耗服务器资源(运行时、磁盘空间),本来就是小水管,如果跑个 Nginx 带几个静态页面不要太轻松,选择 ssr 意味着每个应用都要跑一个 node 进程,再加上使用 Docker 在服务器上做自动构建和部署(没有私有Hub),磁盘 IO 轻松爆炸。

当然,如果可以的话,我选 SSR,SEO 加 FCP 对于重内容的应用很香啊。

当然,本文还是先讲 SSR 的情况。

Turborepo

没别的原因,就看重了 turbo prune --scope $APP_NAME --docker 这一个命令,具体可以看这里

他可以有效缩短大型 workspace 的 Docker 构建时间,起码对我来说是有用的

Docker & Docker Compose

首先,我想解决的是:

  • 把本地一个一个打包
  • 然后压缩好丢服务器
  • 使用 node run 起来(ssr)

Docker 是不二之选,可以轻松解决环境安装、打包、运行的问题。

通用的 Dockefile

利用分布构建,有效减少镜像大小。

// docker/nuxt/DockerfileFROM node:16.20-alpine as baseARG APP_NAMEWORKDIR /appFROM base as pnpmRUN corepack enableFROM base as workspaceRUN yarn global add turboCOPY . .RUN turbo prune --scope $APP_NAME --dockerFROM pnpm AS builderCOPY .gitignore .gitignoreCOPY --from=workspace /app/out/json/ .COPY --from=workspace /app/out/pnpm-lock.yaml ./pnpm-lock.yamlRUN pnpm installCOPY --from=workspace /app/out/full/ .COPY assets ./assetsRUN pnpm exec turbo run build --filter=$APP_NAME...FROM baseCOPY --from=builder /app/apps/$APP_NAME/.output .output# Fixed https://github.com/nuxt/nuxt/issues/12493RUN mkdir /app/.output/server/node_modules/@popperjs && mv .output/server/node_modules/@sxzz/popperjs-es .output/server/node_modules/@popperjs/coreEXPOSE 3000CMD ["node", "/app/.output/server/index.mjs"]

docker-compose.yml

以单个 app 为例

开发环境

docker-compose.dev.yml
version: '3.9'services:  app1:    build:      context: .      dockerfile: ./docker/nuxt/Dockerfile      args:        APP_NAME: app1    ports:      - '49152:3000'    environment:      NUXT_PUBLIC_API_BASE_URL: http://dev.example.com/api

正式环境

docker-compose.yml
version: '3.9'services:  app1:    extends:      file: ./docker-compose.dev.yml      service: app1    environment:      NUXT_PUBLIC_API_BASE_URL: http://prod.example.com/api    restart: always

部署

第一次

git clone monorepos.gitcd monoreposdocker compose build && docker compose up -d

更新

git pulldocker compose up -d --force-recreate --build

总结

这套模式在有限的条件下运行了很久也很好。可是,当应用数量到了 8 个的时候,服务器开始卡了,磁盘开始满了,最终回到了本地打包 SPA,SFTP 到服务器。