Unstract 前端工程指南:Vite 7 + Bun + Biome 构建体系、VITE_ 环境变量与 Docker 热更新实践
【免费下载链接】unstractLLM-Driven Extraction of Unstructured Data — Built for API Deployments & ETL Pipeline Workflows项目地址: https://gitcode.com/GitHub_Trending/un/unstract
本文以 Unstract 前端工程的官方文档 frontend/README.md 为主体,结合仓库中真实的 vite.config.js、package.json、VITE_MIGRATION.md 与 Docker 构建文件,系统讲解该 React 前端如何完成从 Create React App(CRA)到 Vite 7 的迁移,以及如何用 Bun 作为包管理器完成本地开发、环境变量配置、生产构建与容器化部署。读完后你可以独立拉起前端开发服务器、配置代理与热更新(HMR),并理解其 Docker 镜像中运行时配置注入的完整机制。
一、技术栈与迁移背景:从 CRA 到 Vite 7
Unstract 前端是 Unstract(一个面向非结构化数据的 LLM 抽取平台)的用户界面,核心功能覆盖 Prompt Studio、工作流编排、ETL 管道与 API 部署等模块。官方文档明确说明:
- 构建工具:Vite(当前
^7.3.5),配合@vitejs/plugin-react ^4.4.0; - 包管理器:Bun(要求 1.0.0+),依赖锁文件为
bun.lock; - 代码检查与格式化:Biome(
^2.3.13),统一替代 ESLint + Prettier; - 测试:Vitest(
^3.2.6)+ happy-dom 环境; - Node.js 要求
>=20.19.0(见 package.json 的engines字段)。
需要注意一处文档与代码的差异:README 开头写“built with React 18”,但从 package.json 当前依赖表看,react与react-dom均为^19,可以推断 React 大版本已在迁移之后跟进升级,以依赖声明为准。
迁移有两个关键时间节点(来自 VITE_MIGRATION.md):
| 事件 | 时间 | 说明 |
|---|---|---|
| 初次迁移 CRA → Vite 6 | 2025-10-19 | 从 react-scripts 5.0.1 迁移到 Vite 6.0.5 |
| 升级至 Vite 7 | 2026-02-04 | Vite 7.3.1、Vitest 3.2.4、@vitejs/plugin-react 4.4.0 |
Vite 7 带来的硬性变化包括:Node.js 最低要求提升到 20.19+/22.12+(Node 18 被移除);Vitest 必须升到 3.2+;浏览器目标默认从modules改为baseline-widely-available;配置文件中__dirname被import.meta.dirname取代(在 vite.config.js 中可以看到path.resolve(import.meta.dirname, "./src")的实际用法)。
目录结构上,CRA 与 Vite 的差异是:index.html从public/移到仓库根目录(即 frontend/index.html),public/只放纯静态资源,代理配置从src/setupProxy.js移入server.proxy。当前的 index.html 使用<script type="module" src="/src/index.jsx"></script>作为应用入口,并移除了 CRA 的%PUBLIC_URL%占位符。
二、环境准备与快速启动
1. 前置条件
- Bun:1.0.0 或更高版本(推荐);
- Node.js:20.19.0 或更高版本(兼容性要求)。
2. 安装依赖并配置环境变量
git clone https://github.com/Zipstack/unstract.git cd unstract/frontend # 安装依赖 bun install # 复制示例环境变量文件 cp sample.env .env仓库自带的 sample.env 是真实可用的起点,其当前内容为:
VITE_BACKEND_URL=http://localhost:8000 # Analytics - PostHog (set to false to disable tracking) # Default: false for local development VITE_ENABLE_POSTHOG=false # For development NODE_ENV=development # Enable file watching via polling instead of filesystem events # 这些对 Docker 容器内的热更新至关重要: # 普通文件系统事件通知在挂载卷上不可靠 CHOKIDAR_USEPOLLING=true # HMR WebSocket 端口 - 必须匹配外部可访问端口(Traefik = 80) # 不是 Vite 内部端口(3000),因为浏览器是通过代理连接的 WDS_SOCKET_PORT=80README 示例中给出的另一组取值(用于本地直连开发):
VITE_BACKEND_URL=http://frontend.unstract.localhost:8081 VITE_ENABLE_POSTHOG=false VITE_FAVICON_PATH=/path/to/custom/favicon.ico VITE_CUSTOM_LOGO_URL=/path/to/custom/logo.svg3. 启动开发服务器
bun start # 等价于 bun run dev应用运行在http://localhost:3000(端口由env.PORT或默认 3000 决定,见 vite.config.js)。HMR 默认开启,修改组件后页面自动更新且不丢失组件状态;Vite 提供近乎即时的服务器启动、按需编译和构建错误浮层(error overlay)。
生产构建预览:
bun run preview # http://localhost:4173三、环境变量体系:VITE_ 前缀与内置变量
这是 CRA 迁移中最容易踩坑的部分。Vite 只暴露以VITE_开头的自定义变量,访问方式从process.env改为import.meta.env:
// ✅ 正确 console.log(import.meta.env.VITE_BACKEND_URL); // ❌ 错误(CRA 风格,不再可用) console.log(process.env.REACT_APP_BACKEND_URL);官方给出的迁移映射(VITE_MIGRATION.md):
REACT_APP_BACKEND_URL → VITE_BACKEND_URL REACT_APP_ENABLE_POSTHOG → VITE_ENABLE_POSTHOG REACT_APP_FAVICON_PATH → VITE_FAVICON_PATH REACT_APP_CUSTOM_LOGO_URL → VITE_CUSTOM_LOGO_URL代码层面的转换:
// Before (CRA): const backendUrl = process.env.REACT_APP_BACKEND_URL; const isDev = process.env.NODE_ENV === 'development'; // After (Vite): const backendUrl = import.meta.env.VITE_BACKEND_URL; const isDev = import.meta.env.MODE === 'development';Vite 提供四个内置变量:import.meta.env.MODE('development'/'production')、import.meta.env.DEV、import.meta.env.PROD、import.meta.env.BASE_URL。环境文件加载规则:.env(所有场景)、.env.local(所有场景,git 忽略)、.env.[mode](指定模式)、.env.[mode].local(指定模式且 git 忽略)。修改.env后必须重启开发服务器才能生效。
对 TypeScript 使用者,vite-env.d.ts 与 tsconfig.json 提供了类型支撑,可按 VITE_MIGRATION.md 中的示例为ImportMetaEnv声明VITE_BACKEND_URL、VITE_ENABLE_POSTHOG等字段以获得补全。
四、vite.config.js 深度解析
vite.config.js 是整个前端工程的核心配置文件,以下按配置块逐一拆解。
4.1 代理(Proxy)配置:/api转发到后端
proxy: env.VITE_BACKEND_URL && env.VITE_BACKEND_URL.trim() !== "" ? { "/api": { target: env.VITE_BACKEND_URL, changeOrigin: true, secure: false, // 同时转发 WebSocket 升级 —— Socket.IO 日志/结果通道 // 使用纯 websocket 传输连接 /api/v1/socket ws: true, }, } : undefined,(见 vite.config.js)开发时业务代码直接调用相对路径,即可被透明代理:
axios.get('/api/v1/users'); fetch('/api/v1/workflows');从源码注释可以看出一个关键细节:ws: true是必要的——Prompt Studio 的结果流式推送走 Socket.IO 的/api/v1/socket,纯 WebSocket 升级请求若不代理到后端,开发环境下结果永远无法流式更新到 UI(生产环境不受影响,由 Traefik 路由)。
4.2 Docker 兼容的开发服务器配置
server: { host: "0.0.0.0", // 允许容器外访问 port: Number(env.PORT) || 3000, allowedHosts: env.VITE_DEV_ALLOWED_HOSTS ? ... : [], // 按环境放行 Host 头 watch: { usePolling: true, interval: 100 }, // 轮询文件监听 hmr: { port: Number(env.PORT) || 3000, clientPort: env.WDS_SOCKET_PORT ? Number(env.WDS_SOCKET_PORT) : 3000, ...(env.VITE_DEV_HMR_PROTOCOL ? { protocol: env.VITE_DEV_HMR_PROTOCOL } : {}), }, }(见 vite.config.js)三个环境变量分别解决容器/K8s 场景下的不同问题:
CHOKIDAR_USEPOLLING=true+usePolling:Docker 挂载卷不支持原生文件系统事件,改用 100ms 间隔轮询保证热更新触发;WDS_SOCKET_PORT:浏览器通过反向代理(如 Traefik 80 端口)访问时,HMR WebSocket 必须指向外部端口而非容器内部端口;VITE_DEV_HMR_PROTOCOL:当页面经 TLS 终结的 ingress 以 https 提供时,HMR socket 需显式使用wss,否则 Vite 按自身 http 服务推断协议,socket 永远打不开;VITE_DEV_ALLOWED_HOSTS:逗号分隔的 Host 白名单,用于集群 Pod 后浏览器发送真实域名、被 Vite DNS 重绑定防护拦截的场景。
4.3 两个自定义 Rollup 插件
这是从源码结构看最值得学习的部分,两个插件都服务于同一目标:让 OSS(开源)仓库与 Docker 构建时从 unstract-cloud 仓库复制进来的src/plugins/插件树共存。
optionalPluginImports()(vite.config.js):当代码里try { await import("./plugins/...") } catch {}引用了不存在的插件路径时,不直接让构建失败,而是解析为一个空模块(JS 导出throw new Error('Optional plugin not available'),图片类资源导出空字符串),由运行时的 catch 兜底。jsxInJs()(vite.config.js):把src/下含 JSX 的.js文件用transformWithEsbuild以loader: "jsx"、jsx: "automatic"编译。源码注释明确警告jsx: "automatic"是 load-bearing 的:缺失时 esbuild 默认走 classic runtime 生成React.createElement(...),而这些插件文件并未 import React,结果是构建全绿、HTTP 200,页面却在渲染 router 时抛ReferenceError: React is not defined白屏。注释还解释了为何不复用 Vite 的esbuild选项:其include会替换默认过滤条件(导致.ts/.tsx完全不转换),且单文件 transform API 的loader只能是一个字符串(把 TypeScript 交给 JSX 解析器会把泛型<当成标签),因此单独写插件并限定/src/.*\.js$是刻意为之。
4.4 插件注册顺序与构建产物配置
插件顺序有讲究(vite.config.js):optionalPluginImports()必须在tailwindcss()之前,保证缺失的 cloud-plugin 导入先被解析为 stub;jsxInJs()必须排在react()之前,先把.js里的 JSX 变成普通 JS,React 转换与 Rollup 的 import 分析才能正常进行。
构建相关配置(vite.config.js):
build: { target: "esnext", outDir: "build", // 刻意不用默认的 dist/,保持与 Docker 流水线兼容 sourcemap: true, cssCodeSplit: false, // 单一样式表:按 chunk 加载的 CSS 会因导航顺序 // 让跨组件同等特异性规则的胜负不可预测 chunkSizeWarningLimit: 1000, rollupOptions: { output: { manualChunks: { "react-vendor": ["react", "react-dom", "react-router-dom"], "pdf-vendor": [ "@react-pdf-viewer/core", "@react-pdf-viewer/default-layout", "@react-pdf-viewer/highlight", "@react-pdf-viewer/page-navigation", "pdfjs-dist", ], }, }, }, }值得注意:README 与 VITE_MIGRATION.md 中提到的antd-vendorchunk 在当前配置里已不存在——从 package.json 依赖表看,Ant Design 已被radix-ui、lucide-react、sonner等组件库取代,manualChunks也相应只保留了react-vendor与pdf-vendor两组。此外define: { "process.env": {} }为仍期望process.env的第三方库兜底,optimizeDeps.include预打包 React 三件套以加快冷启动,并在optimizeDeps.esbuildOptions中为.js指定jsxloader。
五、工程脚本与 Biome 检查
package.json 的scripts定义了完整命令集:
| 命令 | 实际执行 | 用途 |
|---|---|---|
bun start/bun run dev | vite | 启动带 HMR 的开发服务器 |
bun run preview | vite preview | 本地预览生产构建(:4173) |
bun run build | vite build | 生产构建,输出到build/ |
bun test | vitest | Vitest 测试(watch 模式) |
bun run lint/lint:fix | biome lint [--write] src/ | 检查/自动修复 lint |
bun run format/format:fix | biome format [--write] src/ | 检查/自动修复格式 |
bun run check/check:fix | biome check [--write] src/ | lint + 格式一体化 |
bun run lint:changed | git diff 管道 +biome check --write | 只检查本次变更的.[jt]sx?文件 |
bun run typecheck | tsc --noEmit | TypeScript 类型检查 |
Biome 配置见 biome.json:启用 git VCS 忽略、2 空格缩进、80 列行宽、双引号、尾逗号all,并对 CSS 解析开启tailwindDirectives支持 Tailwind v4 指令;linter 采用精选规则集(关闭recommended,显式开启correctness、suspicious、style等分类下的高价值规则,如noDoubleEquals、noDebugger、noVar、useConst、noUnusedVariables等),并开启assist的organizeImports自动整理导入。
六、Vitest 测试配置
vitest.config.mjs 是独立于vite.config.js的第二份配置,其中的注释值得细读:Vitest不会读取vite.config.js,因此jsxInJs()与optionalPluginImports()两个插件必须在此镜像一份——注释记载了这一不对称曾导致两个真实事故:一次是 248 个测试中只有 126 个被静默加载却仍全绿,另一次是 OSS 检出下任何触及src/helpers/GetStaticData.js动态插件导入的测试直接收集失败。
关键测试选项:globals: true、environment: "happy-dom"、setupFiles: "./src/setupTests.js"(对应 src/setupTests.js)、css: false(Tailwind v4 的@import "tailwindcss"/@pluginat-rule 无法被 Vitest 的 CSS 管线处理,故测试中整体 stub CSS)、resolve.alias中补齐@→./src(注释标注为 P0-14 修复:此前缺失该别名,首个 import@/components/ui/*的测试会解析失败)、exclude额外排除build/避免测试扫描器走进生产构建产物。Playwright 端到端测试位于仓库级tests/e2e/ui,不在 Vitest 范围内。
七、Docker 开发容器与生产镜像
前端被完整容器化,开发/生产共用 frontend.Dockerfile 的多阶段构建(基于oven/bun:1-alpine)。
7.1 从仓库根启动
./run-platform.sh # 仓库根的一键脚本(run-platform.sh 存在) # 或手动 docker compose up前端将暴露在http://frontend.unstract.localhost。
7.2 开发容器
开发阶段镜像的关键点(frontend.Dockerfile):
- 先只拷贝
package.json与bun.lock执行bun install --frozen-lockfile --ignore-scripts,利用层缓存; - 开发服务器设置
PORT=80与生产 NGINX 端口对齐,EXPOSE 80; - 启动命令先执行
/app/generate-runtime-config.sh再bun run start(CMD 见第 34 行),因为 alpine 基础镜像不会自动跑/docker-entrypoint.d/; - HMR 依赖 sample.env 中的
CHOKIDAR_USEPOLLING=true、WDS_SOCKET_PORT(浏览器经代理连接的端口)与 4.2 节的vite.config.js轮询/HMR 配置共同工作。
7.3 生产构建与运行时配置注入
生产阶段(frontend.Dockerfile)流程:
bun install --frozen-lockfile --ignore-scripts→bun run build(Vite 输出到build/);- 基于
nginx:1.29.1-alpine,将build/拷入/usr/share/nginx/html,并用仓库内 nginx.conf 覆盖默认配置; - 用
sed在构建之后向index.html注入<script src="/config/runtime-config.js"></script>(第 69 行)。之所以必须后置注入,是因为 Vite 构建期会处理所有<script>标签,而该文件构建时并不存在; - 容器启动时 generate-runtime-config.sh 把 Docker 环境写入
/usr/share/nginx/html/config/runtime-config.js,生成:
window.RUNTIME_CONFIG = { faviconPath: "${VITE_FAVICON_PATH:-${REACT_APP_FAVICON_PATH}}", logoUrl: "${VITE_CUSTOM_LOGO_URL:-${REACT_APP_CUSTOM_LOGO_URL}}", enablePosthog: "${VITE_ENABLE_POSTHOG:-${REACT_APP_ENABLE_POSTHOG}}", version: "${APP_VERSION}" };脚本自带js_escape对反斜杠/双引号转义以保证值是合法 JS 字符串,并对VITE_前缀保留REACT_APP_回退兼容;生产代码随后从window.RUNTIME_CONFIG读取白标(favicon/logo)与 PostHog 开关——即改白标配置无需重新构建镜像。
八、构建优化与性能
手动 chunk 拆分的收益(vendor 代码变化频率低、可并行下载、主包更小)叠加 Vite 的依赖预打包与 tree-shaking,构成生产包缓存策略。README 给出的官方性能对比(CRA vs Vite,文档口径的参考值):
| 指标 | CRA | Vite |
|---|---|---|
| 开发服务器启动 | 10–30 秒 | 1–2 秒 |
| HMR 更新 | 2–5 秒 | < 1 秒 |
| 生产构建 | 60–120 秒 | 30–60 秒 |
生产构建还包含:esnext目标、内容哈希文件名、source map 生成(可通过build.sourcemap配置)、基于路由动态导入的自动分包。
九、常见问题排查(Troubleshooting)
- 环境变量不加载:确认
VITE_前缀;确认通过import.meta.env.VITE_*访问(而非process.env);修改.env后重启开发服务器。 - Docker 中 HMR 失效:检查
.env中CHOKIDAR_USEPOLLING=true;检查 vite.config.js 的watch.usePolling与hmr.clientPort(WDS_SOCKET_PORT)是否与外部端口一致;确认 volume 挂载正确。 - 构建报 “Cannot find module”:核对导入路径;
bun pm ls <package>确认依赖已安装;清理重装rm -rf node_modules && bun install;清理 Vite 缓存rm -rf node_modules/.vite。 - 端口 3000 被占用:
lsof -ti:3000 # 找到占用进程 kill -9 $(lsof -ti:3000) # 结束它 # 或换端口:vite --port 3001- 构建/开发缓慢:清 Vite 缓存、
bun update升级依赖、检查src/下过大文件、临时关闭 source map。
十、代码组织与静态资源
静态资源一律从public/目录以绝对路径引用:
// ✅ 正确 <img src="/images/logo.png" alt="Logo" /> // ❌ 错误(CRA 风格) <img src={`${process.env.PUBLIC_URL}/images/logo.png`} alt="Logo" />路由级代码分割使用动态导入:const Dashboard = lazy(() => import('./pages/Dashboard')),Vite 会为动态导入的模块自动拆出独立 chunk。SVG 支持两种用法:public/icons/下直接引用,或通过vite-plugin-svgr以import Logo from './logo.svg?react'方式作为 React 组件导入(由svgr()插件支撑)。React Strict Mode 在开发环境默认启用,组件会被挂载两次以暴露副作用与不安全的生命周期写法,该行为只发生在开发环境。
当前前端目录结构(结合 package.json、index.html 实际内容):
frontend/ ├── docs/ # 文档(VITE_MIGRATION.md 等) ├── public/ # 静态资源(manifest.json、icons/、favicon.ico) ├── src/ │ ├── assets/ # 图片、字体 │ ├── components/ # 可复用 React 组件 │ ├── helpers/ # 工具函数 │ ├── hooks/、pages/、store/、layouts/、routes/、lib/ │ ├── config.js # 应用配置 │ ├── index.jsx # 应用入口 │ └── setupTests.js # Vitest setup 文件 ├── index.html # HTML 入口(Vite 风格,位于根目录) ├── vite.config.js # Vite 配置(代理/HMR/chunk/自定义插件) ├── vitest.config.mjs # 独立测试配置(镜像 vite 插件) ├── biome.json # Biome 检查/格式配置 ├── sample.env # 环境变量样例 ├── generate-runtime-config.sh # Docker 运行时配置生成脚本 ├── nginx.conf # 生产镜像 NGINX 配置 └── package.json # 依赖与脚本十一、CRA 迁移快速清单
如果你接手旧分支或需要对照迁移差异,README 给出的检查清单(并对照 VITE_MIGRATION.md 中的回滚步骤):
- 所有
.env中的REACT_APP_*改为VITE_*; - 代码中
process.env替换为import.meta.env; - SVG 导入改用
?react查询参数(由vite-plugin-svgr提供); - 删除 HTML 中的
%PUBLIC_URL%,改用绝对路径; - 代理配置从
setupProxy.js迁移到server.proxy(本仓库已含ws: true增强); - 验证 Docker 内 HMR 与文件监听正常工作;
- 生产构建输出确认位于
build/而非dist/。
以上即 Unstract 前端“Vite 7 + Bun + Biome + Vitest + Docker”完整工程链路的落地细节:本地一条bun start即可带 HMR 与/api代理开发,bun run build产出面向 NGINX 的静态包,容器场景则通过轮询监听、HMR 端口对齐与运行时配置注入三层机制保证白标与热更新同时可用。所有关键实现均可在 vite.config.js、vitest.config.mjs、biome.json 与 frontend.Dockerfile 中逐行核对。
【免费下载链接】unstractLLM-Driven Extraction of Unstructured Data — Built for API Deployments & ETL Pipeline Workflows项目地址: https://gitcode.com/GitHub_Trending/un/unstract
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考