记录搭建与使用过程情况
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
中生效,无法在项目中生效
jsonweb:
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_NAME
在dockerfile
中使用时生效
-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
文件地址
需要指定端口
jsonversion: "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
相关代码
dockerfileARG 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 的应用程序。
dockerfileARG VERSION FROM node:${VERSION}-alpine AS alpine
这个 Dockerfile 从 Alpine Linux 版本的 Node.js 镜像开始。Alpine Linux 以其小巧的体积和安全性著称,非常适合容器化部署。通过使用 ARG 指令,我们可以在构建时灵活指定 Node.js 的版本。
dockerfileRUN apk update RUN apk add --no-cache libc6-compat
这一阶段更新 Alpine 的包管理器并安装 libc6-compat。这是许多 Node.js 应用程序所需的兼容性库。
dockerfileFROM alpine as base RUN npm install pnpm turbo@2.0.6 --global RUN pnpm config set store-dir ~/.pnpm-store
在 base 阶段,我们安装了两个重要工具:
dockerfileFROM base AS pruner WORKDIR /app COPY . . RUN turbo prune --scope=${CONTAINER_NAME} --docker
pruner 阶段使用 Turbo 的 prune 命令来优化工作空间。这个步骤:
dockerfileFROM 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 阶段负责实际的应用程序构建:
dockerfileFROM alpine AS runner RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs USER nodejs
runner 阶段创建了最终的生产镜像:
dockerfileCOPY --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
特别注意文件的所有权设置和目录结构的保持。
dockerfileENV BASE_DEV_URL=${BASE_DEV_URL} ENV dev=${dev} ENV PORT=${PORT} EXPOSE ${PORT} CMD ["node", "server.js"]
最后设置环境变量和暴露端口,使用 node 命令启动服务器。
--no-cache
标志避免 apk 缓存这个 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
,dev
在env.mjs
中设置
JavaScriptimport { 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
**中设置
jsonenv: {
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
封装中使用
TypeScriptconst 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"
}
}
本文作者:还是夸张一点
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!