React 前端工程实战指南:awesome-compose 中 react-express-mysql 前端的脚手架、脚本与 Compose 部署解析

原创2026-09-29 14:45:051,913 阅读
文章标签:示例工程

React 前端工程实战指南:awesome-compose 中 react-express-mysql 前端的脚手架、脚本与 Compose 部署解析

本篇技术指南以 awesome-compose 仓库中 react-express-mysql 示例的 React 前端为对象,系统讲解 Create React App 脚手架的可用脚本、开发/测试/构建全流程,以及前端如何通过 HTTP 代理与 Express 后端、MySQL 数据库在 Docker Compose 中联调运行。读完本文,你将掌握该前端工程的 npm start/npm test/npm run build 三大核心命令的语义、开发代理与多阶段 Dockerfile 的配合方式,并能独立完成从本地开发到 Compose 部署的完整闭环。

工程概览:Create React App 脚手架与项目结构

react-express-mysql 是一个"React 前端 + Node.js/Express 后端 + MySQL 数据库"的三层 Compose 示例。前端目录 react-express-mysql/frontend 由 Create React App 明确声明了 React 17.0.2、react-dom 17.0.2 以及 react-scripts 5.0.0 作为构建工具链。CRA 将 Webpack、Babel、ESLint 等复杂配置封装进 react-scripts,开发者无需维护独立的构建配置即可获得开箱即用的开发服务器、生产构建与测试运行器。

前端源码结构如下(与 Compose 的卷挂载和构建目标直接相关):

  • src/index.js:应用入口,通过 ReactDOM.render 挂载 <App />,并在末尾调用 serviceWorker.unregister()(该示例不启用离线 PWA 缓存);
  • src/App.js:根组件,在 useEffect 中 fetch("/api/") 拉取后端消息并渲染;
  • src/setupProxy.js:开发环境 API 代理配置,将 /api 请求转发至后端服务;
  • src/App.test.js:基于 Testing Library 的冒烟测试;
  • Dockerfile:多阶段构建文件,同时提供开发镜像、构建镜像与生产 Nginx 镜像;
  • package.json:依赖与脚本声明,也是 Docker 层缓存优化的关键输入文件。

可用脚本:四个 npm 命令的语义与使用场景

在 frontend 目录下,CRA 提供了四个标准脚本(定义于 package.json 的 scripts 字段),它们分别是 react-scripts start、react-scripts test、react-scripts build 与 react-scripts eject:

npm start:启动开发模式

在项目目录中运行:

npm start

该命令启动开发模式服务器,浏览器访问 http://localhost:3000 即可查看页面。它具备两个典型开发特性:

  • 热更新:编辑源码保存后页面自动重载("The page will reload if you make edits.");
  • 实时报错:控制台会即时输出 lint 错误,便于在编码阶段发现问题。

需要特别说明的是,在本仓库的 Compose 编排中,npm start 是前端开发容器的默认命令。compose.yaml 中 frontend 服务通过 build.target: development 选中 Dockerfile 的 development 阶段,并将容器端口 3000 映射到宿主机 3000:

frontend:
  build:
    context: frontend
    target: development
  ports:
    - 3000:3000
  volumes:
    - ./frontend/src:/code/src
    - /code/node_modules
  networks:
    - public
  depends_on:
    - backend

volumes 中 ./frontend/src:/code/src 把宿主机源码实时注入容器,/code/node_modules 匿名卷则避免宿主机的 node_modules 覆盖镜像内已用 npm ci 安装的依赖——这正是 npm start 热更新在容器内依然生效的底层保障。

npm test:交互式测试运行器

npm test

启动测试运行器并进入交互式 watch 模式("Launches the test runner in the interactive watch mode")。对应当前仓库的冒烟测试 src/App.test.js:

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  const { getByText } = render(<App />);
  const linkElement = getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

测试断言页面能渲染出包含 "Learn React" 的链接元素。在 CI 环境中,Dockerfile 通过 ENV CI=true 让测试以非 watch 的一次性模式运行,与本地交互模式区分开。注意:该示例的 Compose 编排并未单独暴露测试服务端口,测试主要在本地或自定义 CI 流水线中执行。

npm run build:生产构建

npm run build

构建生产版本到 build 目录:

  • 在 production 模式下正确打包 React,并针对最佳性能做优化("correctly bundles React in production mode and optimizes the build for the best performance");
  • 产物会被压缩(minified),文件名包含内容哈希(hashes),便于浏览器长期缓存。

生产构建产物是前端容器生产阶段的输入。Dockerfile 中 builder 阶段执行 RUN npm run build,随后最终阶段以 Nginx 提供服务:

FROM nginx:1.13-alpine
COPY --from=builder /code/build /usr/share/nginx/html

即 build 目录被整体复制进 Nginx 的静态站点根目录 /usr/share/nginx/html,生产环境由 Nginx 直接托管构建产物,无需 Node 运行时。

npm run eject:单向操作,谨慎使用

npm run eject

这是一次性(one-way)操作,一旦执行无法回退。它会将 CRA 封装的单点构建依赖移除,并把 webpack、Babel、ESLint 等配置文件与传递依赖全部拷贝进项目,从此由开发者全权掌控构建细节。执行后除 eject 外的所有脚本仍然可用,但它们会指向拷贝出的脚本,后续配置改动完全自主。

CRA 官方的立场是:不必强制使用 eject。默认的精选特性集足够支撑中小规模部署,只有在确实需要自定义构建配置时才考虑。对于本仓库的 Compose 场景,镜像内构建、卷挂载与代理配置均由 Dockerfile 与 compose.yaml 承担,开发者通常无需 eject。

开发代理:setupProxy.js 如何打通前后端联调

前端开发服务器默认运行在 3000 端口,而后端 Express 服务运行在独立容器中(映射宿主机 80 端口)。两者同源(same-origin)问题通过 setupProxy.js 解决:

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function(app) {
  app.use(
    "/api",
    createProxyMiddleware({
      target: "http://backend",
      pathRewrite: { "^/api": "" }
    })
  );
};

createProxyMiddleware 来自 http-proxy-middleware(声明于 package.json 的 devDependencies),规则要点:

  • 匹配以 /api 开头的请求;
  • 转发目标为 http://backend——这正是 Compose 内部网络中 backend 服务的 DNS 名称,前端容器通过 compose.yaml 加入 public 网络访问它;
  • pathRewrite: { "^/api": "" } 将路径前缀剥离,使 /api/ 落到后端根路由 /。

于是 App.js 中的 fetch("/api/") 实际抵达后端 Express 的根路由,后者(见 backend/src/server.js)执行 database.raw('select VERSION() version') 查询 MySQL 版本,并返回 {"message":"Hello from MySQL <版本号>"},前端将其渲染在页面上。整条链路即:浏览器 → 前端代理 → backend 容器 → MySQL 数据库。

多阶段 Dockerfile:开发、构建与生产的镜像策略

frontend/Dockerfile 采用 Dockerfile 1.4 语法(# syntax=docker/dockerfile:1.4),包含四个阶段:

阶段 基础 作用
development node:lts 安装依赖并执行 npm start,支持卷挂载热更新
builder 基于 development 执行 npm run build 产出静态构建物
dev-envs 基于 development 注入 git、vscode 用户与 Docker CLI 工具,面向 IDE 容器开发
最终阶段 nginx:1.13-alpine 托管 builder 阶段的 build 产物

开发阶段的构建缓存优化值得关注:先 COPY package.json 与 package-lock.json,再 RUN npm ci,最后才 COPY . /code。由于依赖声明文件远比源码稳定,这一顺序能最大化利用 Docker 层缓存——只有依赖变化时才重装 npm ci,源码改动仅触发最后一层重建,与后端 Dockerfile(backend/Dockerfile)中的注释建议一致。dev-envs 阶段则为容器内开发体验准备 vscode 用户、docker 用户组与 Docker CLI/buildx/compose 工具。

Compose 一键部署:从 up 到 down 的完整流程

顶层 react-express-mysql/README.md 给出了完整的部署命令流,其项目结构为:

react-express-mysql
├── backend/          # Node.js/Express 后端(Dockerfile、src/)
├── db/               # password.txt 数据库口令
├── frontend/         # React 前端(Dockerfile、src/)
├── compose.yaml      # 三服务编排
└── README.md

启动:docker compose up -d

在 react-express-mysql 目录下执行:

docker compose up -d

Compose 会按 depends_on 顺序创建 db → backend → frontend,并完成镜像构建(首次启动时如镜像不存在会自动构建,输出会提示 "Image for service frontend was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build")。启动成功后用 docker ps 验证:

CONTAINER ID   IMAGE                        PORTS                                                  NAMES
f3e1183e709e   react-express-mysql_frontend 0.0.0.0:3000->3000/tcp                              react-express-mysql_frontend_1
9422da53da76   react-express-mysql_backend  0.0.0.0:80->80/tcp, 0.0.0.0:9229-9230->9229-9230/tcp  react-express-mysql_backend_1
a434bce6d2be   mysql:8.0.19                  3306/tcp, 33060/tcp                                  react-express-mysql_db_1

浏览器访问 http://localhost:3000 即可看到前端页面(前提是宿主机 3000 端口未被占用,Compose 文件与 README 均对此有明确提示)。

验证后端与数据库

后端容器将 80 端口映射到宿主机 80,可直接用 curl 验证数据链路:

curl localhost:80
# {"message":"Hello from MySQL 8.0.19"}

返回的版本号来自 backend/src/server.js 中的 select VERSION() 查询,说明 Express 已成功连上 MySQL。9229(Node 调试)与 9230(测试调试)端口在 backend/Dockerfile 中通过 EXPOSE $PORT 9229 9230 声明,并由 compose.yaml 映射到宿主机,供调试器与测试进程使用。

数据库口令与架构兼容性说明

数据库口令存放在 db/password.txt,通过 Compose 的 secrets 机制注入容器:backend 读取 DATABASE_PASSWORD=/run/secrets/db-password(config.js 中 readFileSync 读取该文件内容作为密码),db 服务则通过 MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password 读取。这与以环境变量明文传参的方式相比,口令不会直接暴露在容器配置中。

关于数据库镜像,compose.yaml 默认使用 mariadb:10.6.4-focal,原因在于其同时支持 AMD64 与 ARM64 两种架构(README 的 INFO 提示也说明了这一点);若确实需要使用 MySQL,取消注释 #image: mysql:8.0.27 即可。同时 db 服务配置了 command: '--default-authentication-plugin=mysql_native_password' 与数据卷 db-data:/var/lib/mysql,确保认证插件兼容与数据持久化。

停止与清理:docker compose down

docker compose down

命令会依次停止并移除三个容器,最后删除 react-express-mysql_default 网络:

Stopping react-express-mysql_frontend_1 ... done
Stopping react-express-mysql_backend_1  ... done
Stopping react-express-mysql_db_1       ... done
Removing react-express-mysql_frontend_1 ... done
Removing react-express-mysql_backend_1  ... done
Removing react-express-mysql_db_1       ... done
Removing network react-express-mysql_default

从源码理解运行细节:端口、健康检查与优雅停机

几个影响实际运行的关键实现细节值得留意:

  • 端口约定:前端开发服务器固定 3000(ENV PORT=3000);后端默认端口在本地开发时为 8080(避免非 root 权限问题),容器内则为 80(config.js 的注释对此做了说明)。
  • 健康检查:backend 镜像定义了 HEALTHCHECK --interval=30s CMD node healthcheck.js,每 30 秒探测一次;docker ps 中 backend 显示 (healthy) 即来自该机制,前端可通过 depends_on 感知后端就绪。
  • 优雅停机:backend/src/index.js 监听 SIGINT 与 SIGTERM 信号,在 docker stop 时先关闭 HTTP server 再退出,避免容器被强制杀掉导致连接中断。
  • 日志策略:后端使用 morgan("common") 将访问日志输出到 stdout(server.js),符合容器日志最佳实践,便于 docker compose logs 查看。

小结

从 Create React App 的四个标准脚本出发,本文梳理了 react-express-mysql/frontend 的完整开发与部署路径:本地 npm start 热更新、npm test 交互测试、npm run build 生产构建,以及通过 setupProxy.js 将前端 /api 请求代理到 Compose 内部网络的 backend 服务;多阶段 Dockerfile 则在同一份代码上同时支撑开发镜像(热更新)、构建镜像与 Nginx 生产镜像。结合 compose.yaml 的网络、卷、secrets 与端口映射设计,你可以在任何具备 Docker 环境的主机上快速复现这套 React + Express + MySQL 的全栈示例,并将其作为自有项目前端容器化改造的起点。

登录后查看全文
awesome-compose