编辑
2024-12-31
React
00

目录

我的搭建
我的项目目录
具体使用详看turbo官网
packages/ui中样式失效问题
以下介绍如何docker打包
配置多环境
dockerfile中变量使用
docker-compose.yml使用
dockerfile
深入解析多阶段构建的 Dockerfile:优化 Node.js 应用程序部署
基础镜像设置
环境准备
工具安装和配置
项目剪裁(Pruning)
构建阶段
最终运行阶段
文件复制和配置
环境配置和启动
优化特点
最佳实践
dockerfile中作用于项目的变量失效解决方案
变量使用
客户端请求时代理
服务端请求

记录搭建与使用过程情况
turbo
Turborepo 是什么? Turborepo 是适用于 JavaScript 和 TypeScript 代码库的高性能构建系统。它专为扩展 monorepos 而设计,同时还能加快单包工作区中的工作流程。

从个人开发人员到世界上最大的企业工程组织,Turborepo 通过轻量级方法优化您需要在存储库中运行的任务,节省了数年的工程时间和数百万美元的计算成本。
这是他的介绍,我只使用了很小一部分,以下记录使用以及打包的dockerfile

我的搭建

我的项目需求,一个web项目,需要实现mobile版本,但是需要分包发布,不能直接使用一个web包进行动态适配
所以找到了turbo
使用pnpm pnpm-workspace共同实现

我的项目目录

pingx-app/
|
|──apps
|      |──web
|      └──mobile
|
|──packages
|      |──assets-config
|      |──eslint-config
|      |──redux-store
|      |──tailwind-config
|      |──theme-config
|      |──types-config
|      |──typescript-config
|      |──ui
|      └──utils-config
|──pnpm-workspace.yaml
|
└───turbo.json


assets-config //静态文件导出
eslint-config // 一些eslint设置
redux-store //redux配置
tailwind-config //tailwind配置
theme-config //主题配置
types-config //type与enums配置
typescript-config //typescript配置
ui // 公共UI
utils-config // 请求之类的


具体使用详看turbo官网

遇到的问题

packages/ui中样式失效问题

本项目使用了tailwindcss, 在主项目web,mobile中都需要进行tailwind引入,以及在ui中也需要进行引入,并且在ui中需要将组件内部使用的css进行导出,然后在主项目中引入

会造成如下原因
1、ui中style需要使用tailwindcss指令进行css生成合并;导致在主项目使用时需要时刻关注是否有新的tailwindcss生成,每次都需要重新运行整个项目
2、每次本地build之后整个node_modules就失效,需要删除重新拉取依赖

以下介绍如何docker打包

遇到的问题

docker中设置变量,有时候会失效,跟node环境有关 在docker中设置的变量只能在dockerfile中生效,无法在项目中生效

json
web: build: args: - VERSION=20 - dev=dev - BASE_DEV_URL=请求地址 - PORT=3002 - CONTAINER_NAME=web context: . dockerfile: ./apps/web/Dockerfile restart: always container_name: web ports: - 3002:3002

以上生效的只有VERSION,dev,PORT,CONTAINER_NAMEdockerfile中使用时生效

配置多环境

-dev

docker-compose.dev.yml

-test

docker-compose.test.yml

指令运行:

多项目同事打包: docker-compose -f docker-compose.test.yml -f docker-compose.dev.yml up

dockerfile中变量使用

1、VERSION: 指定node版本 2、PORT: 指定node启动后的端口号 3、CONTAINER_NAME: 指定主项目目录名

docker-compose.yml使用

需要在项目根目录
需要配置变量,dockerfile文件地址
需要指定端口

json
version: "3" services: web: build: args: - VERSION=20 - dev=dev - PORT=3002 - CONTAINER_NAME=web context: . dockerfile: ./apps/web/Dockerfile restart: always environment: - dev=dev container_name: web ports: - 3002:3002 # networks: # - app_network mobile: build: args: - VERSION=20 - dev=production - PORT=3003 - CONTAINER_NAME=mobile context: . dockerfile: ./apps/mobile/Dockerfile restart: always container_name: mobile ports: - 3003:3003 # networks: # - app_network # Define a network, which allows containers to communicate # with each other, by using their container name as a hostname # networks: # app_network: # external: true

dockerfile

相关代码

dockerfile
ARG VERSION # Alpine image FROM node:${VERSION}-alpine AS alpine ARG BASE_DEV_URL ARG PORT ARG dev ARG CONTAINER_NAME RUN apk update RUN apk add --no-cache libc6-compat # ENV CONTAINER_NAME=web # Setup pnpm and turbo on the alpine base FROM alpine as base RUN npm install pnpm turbo@2.0.6 --global RUN pnpm config set store-dir ~/.pnpm-store # Prune projects FROM base AS pruner WORKDIR /app COPY . . RUN turbo prune --scope=${CONTAINER_NAME} --docker # Build the project FROM base AS builder WORKDIR /app # Copy lockfile and package.json's of isolated subworkspace COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml COPY --from=pruner /app/out/json/ . # First install the dependencies (as they change less often) RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile # Copy source code of isolated subworkspace COPY --from=pruner /app/out/full/ . RUN turbo build --filter=${CONTAINER_NAME} # RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm prune --prod --no-optional # RUN rm -rf ./**/*/src # Final image FROM alpine AS runner RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs USER nodejs WORKDIR /app # COPY --from=builder --chown=nodejs:nodejs /app . COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/next.config.mjs . COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing # COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/.next/standalone/apps/${CONTAINER_NAME} ./ COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/.next/standalone ./ COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/.next/static ./apps/${CONTAINER_NAME}/.next/static COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/public ./apps/${CONTAINER_NAME}/public # RUN rm -rf ./apps/${CONTAINER_NAME}/node_modules WORKDIR /app/apps/${CONTAINER_NAME} # ARG PORT=3002 # ENV dev=production ENV BASE_DEV_URL=${BASE_DEV_URL} ENV dev=${dev} ENV PORT=${PORT} EXPOSE ${PORT} CMD ["node", "server.js"]

深入解析多阶段构建的 Dockerfile:优化 Node.js 应用程序部署

在现代前端开发中,如何高效地构建和部署应用程序是一个重要话题。本文将详细分析一个使用多阶段构建的 Dockerfile,该文件专门用于构建和部署基于 Node.js 的应用程序。

基础镜像设置

dockerfile
ARG VERSION FROM node:${VERSION}-alpine AS alpine

这个 Dockerfile 从 Alpine Linux 版本的 Node.js 镜像开始。Alpine Linux 以其小巧的体积和安全性著称,非常适合容器化部署。通过使用 ARG 指令,我们可以在构建时灵活指定 Node.js 的版本。

环境准备

dockerfile
RUN apk update RUN apk add --no-cache libc6-compat

这一阶段更新 Alpine 的包管理器并安装 libc6-compat。这是许多 Node.js 应用程序所需的兼容性库。

工具安装和配置

dockerfile
FROM alpine as base RUN npm install pnpm turbo@2.0.6 --global RUN pnpm config set store-dir ~/.pnpm-store

在 base 阶段,我们安装了两个重要工具:

  • pnpm:高性能的包管理器
  • Turbo:用于管理单体仓库(monorepo)的构建系统

项目剪裁(Pruning)

dockerfile
FROM base AS pruner WORKDIR /app COPY . . RUN turbo prune --scope=${CONTAINER_NAME} --docker

pruner 阶段使用 Turbo 的 prune 命令来优化工作空间。这个步骤:

  • 仅保留指定容器所需的依赖
  • 创建一个最小化的构建上下文
  • 显著减少最终镜像的大小

构建阶段

dockerfile
FROM base AS builder WORKDIR /app COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml COPY --from=pruner /app/out/json/ .

builder 阶段负责实际的应用程序构建:

  1. 复制必要的配置文件
  2. 安装依赖
  3. 复制源代码
  4. 执行构建命令

最终运行阶段

dockerfile
FROM alpine AS runner RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs USER nodejs

runner 阶段创建了最终的生产镜像:

  • 创建专用的系统用户和用户组
  • 采用最小权限原则
  • 仅包含运行应用所需的文件

文件复制和配置

dockerfile
COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/.next/standalone ./ COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/.next/static ./apps/${CONTAINER_NAME}/.next/static COPY --from=builder --chown=nodejs:nodejs /app/apps/${CONTAINER_NAME}/public ./apps/${CONTAINER_NAME}/public

特别注意文件的所有权设置和目录结构的保持。

环境配置和启动

dockerfile
ENV BASE_DEV_URL=${BASE_DEV_URL} ENV dev=${dev} ENV PORT=${PORT} EXPOSE ${PORT} CMD ["node", "server.js"]

最后设置环境变量和暴露端口,使用 node 命令启动服务器。

优化特点

  1. 多阶段构建:通过多个构建阶段最小化最终镜像大小
  2. 依赖优化:使用 pnpm 和 Turbo 实现高效的依赖管理
  3. 安全性考虑:使用非 root 用户运行应用
  4. 灵活配置:通过 ARG 和 ENV 指令提供构建时和运行时的配置选项

最佳实践

  1. 使用 --no-cache 标志避免 apk 缓存
  2. 利用构建缓存优化依赖安装
  3. 合理组织多阶段构建,优化构建效率
  4. 注意文件权限和所有权设置

这个 Dockerfile 展示了现代前端应用的容器化最佳实践,特别适用于基于 Next.js 的应用程序部署。

无效内容 BASE_DEV_URL使用无效,可剔除
dev使用无效可剔除
指定turbo版本原因,打包需要依赖于turbo, 不指定时会导致自动升级 使用 pnpm install --frozen-lockfile原因,拉取依赖时,要求按照pnpm-lock.yaml进行拉取,确保与本地依赖一致,减少打包问题

dockerfile中作用于项目的变量失效解决方案

关键解决

env.mjs: 提供变量
next.config.mjs: 设置变量使用

例子:
BASE_DEV_URL,devenv.mjs中设置

JavaScript
import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" const BASE_DEV_URL = 'http://xxxx.com' .toString() .trim() const dev = "dev".toString().trim() const apps = "web".toString().trim() // dev使用代理时设置 const isVpn = "false".toString().trim(); // vpn端口,ip默认: 127.0.0.1 const vpn_port = "4780".toString().trim(); export const env = createEnv({ server: { ANALYZE: z .enum(["true", "false"]) .optional() .transform((value) => value === "true"), BASE_DEV_URL: z.string().optional(), dev: z.string().optional(), isVpn: z.string().optional(), vpn_port: z.string().optional(), apps: z.string().optional(), }, client: {}, runtimeEnv: { ANALYZE: process.env.ANALYZE, BASE_DEV_URL, dev, isVpn, vpn_port, apps }, })

**next.config.mjs**中设置

json
env: { BASE_DEV_URL: process.env.BASE_DEV_URL || env.BASE_DEV_URL || "", dev: process.env.dev || env.dev || "", isVpn: process.env.isVpn || env.isVpn || "false", vpn_port: process.env.vpn_port || env.vpn_port || "", apps: process.env.apps || env.apps || "", }

变量解释

BASE_DEV_URL 请求地址
dev:设置当前环境
isVpn: 设置是否需要使用vpn
vpn_port: 设置vpn端口
apps: 设置在某些情况下剔除一些配置的变量标识

变量使用

客户端请求时代理

json
//next.config.mjs的rewrites中 async rewrites() { return [ { source: "/api/:path*", destination: `${process.env.BASE_DEV_URL || env.BASE_DEV_URL}/api/:path*` } ] }

服务端请求

axios封装中使用

TypeScript
const useRequest: AxiosInstance = axios.create({ xsrfCookieName: 'xsrf-token', baseURL: processConfig.BASE_DEV_URL,// processConfig是在utils中将process.env变量全写进去的对象 timeout: 20000, }); ... //判断是否是客户端 if (config.isClient) { config.baseURL = ''; // 清除baseURL token = Cookies.get('token') || ''; } else { //服务端照旧 } ... if (process.env.isVpn === "true" && process.env.vpn_port) { config.proxy = { host: '127.0.0.1', port: Number(process.env.vpn_port), protocol: "http" } }
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:还是夸张一点

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

还是夸张一点技术专栏 © 2019 - 2023 | 滇ICP备2022001556号
世间情动不过盛夏白瓷梅子汤,碎冰碰壁当啷响。