项目打包后本地预览 + 代理转发防跨域:基于 Express + http-proxy-middleware
直接启动 Vite 开发服务器,HMR 热更新、调试方便,但并非真实生产环境。Express 托管静态资源 + 接口代理 + 自动构建 + 自动打开浏览器。,所以在 Windows、macOS、Linux 下都能正常运行。本文会详细介绍如何实现这一目标,最后给出一个。这种方式是模拟生产环境部署,使用打包后的。,既能托管打包后的文件,又能实现接口代理。,可以让前端测试生产环境版本更加高效。做了接口代
·
在开发 uni-app + Vue3 的 H5 项目时,我们经常会遇几个需求:
- 打包后想在本地预览,保证生产环境下资源加载正常;
- API 请求需要转发到后端,防止跨域;
- 希望能 一键启动本地 HTTPS 服务器,支持电脑和手机访问。
本文结合 Windows + iOS 手机 场景,介绍完整流程。
一、问题背景
1. 打包后的 H5 项目预览
通常我们有两种方式:
- 开发模式:
npm run dev:h5
支持 HMR、热更新,但不是生产环境。
- 打包 + 预览:
npm run build:h5
npm run preview
模拟真实生产环境,查看打包后的 dist/build/h5
文件。
痛点:
vite preview
下的 API 代理失效;- HTTP 页面访问 HTTPS 后端接口会出现跨域或安全警告;
- 局域网手机访问不支持 HTTPS,浏览器报
NET::ERR_CERT_AUTHORITY_INVALID
。
2. 跨域问题
浏览器同源策略会阻止从 HTTP 页面请求 HTTPS 接口:
Access to fetch at 'https://api.xxx.com'
from origin 'http://localhost:8080' has been blocked by CORS policy
解决方案:
- 后端配置 CORS(成本高,需协调后端)
- 本地代理转发(推荐,透明代理绕过浏览器限制)
二、解决方案核心思路
- 打包项目 → 生成
dist/build/h5
- Express 本地服务器托管静态资源
- http-proxy-middleware 转发 API
- HTTPS 支持 + mkcert 生成自签证书
- 支持电脑和手机局域网访问
- 一键启动 + 自动打开浏览器
三、准备工作
1. 安装依赖
npm install express http-proxy-middleware serve-static open --save-dev
依赖说明:
- express:本地服务器
- http-proxy-middleware:接口代理
- serve-static:托管静态文件
- open:自动打开浏览器
- mkcert:生成受信任自签证书
2. Windows 下生成 HTTPS 证书
参考:https://cloud.tencent.com/developer/article/2191830
下载 mkcert-v1.4.4-windows-amd64.exe
- 打开 CMD 或 PowerShell
- 安装 mkcert 根证书(第一次使用)
mkcert-v1.4.4-windows-amd64.exe -install
- 查看证书所处位置
mkcert-v1.4.4-windows-amd64.exe -CAROOT
//C:\Users\Administrator\AppData\Local\mkcert
- 生成包含局域网 IP 的证书(假设本机局域网 IP 为 192.168.31.161):
mkcert-v1.4.4-windows-amd64.exe localhost 127.0.0.1 192.168.31.161
localhost+2.pem // 证书
localhost+2-key.pem // 私钥
rootCA.pem // 根证书
⚠️ 注意:
- iOS 手机只需要安装
rootCA.pem
,不要安装具体域名证书。- 手机访问局域网 HTTPS 时,浏览器会自动信任由 rootCA 签发的证书。
3. 安装根证书到 iOS 手机
- 将
rootCA.pem
发送到手机(AirDrop / 邮件 / 下载) - 用 Safari 打开,iOS 会提示“已下载配置描述文件”
- 打开 设置 → 通用 → VPN 与设备管理 → 已下载的描述文件 → 安装证书
- 安装完成后,打开 设置 → 通用 → 关于本机 → 证书信任设置 → 打开“完全信任此根证书”
✅ 这样手机就能访问
https://192.168.31.161:8080
而不会报错。
四、一键 HTTPS 本地预览脚本
在项目根目录创建 scripts/preview.js
:
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
import serveStatic from "serve-static";
import open from "open";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import { execSync } from "child_process";
import os from "os";
import fs from "fs";
import https from "https";
// 修复 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// -------------------------
// 1. 打包项目
// -------------------------
console.log("📦 开始打包 H5...");
try {
execSync("npm run build:h5", { stdio: "inherit" });
console.log("✅ 打包完成!");
} catch (error) {
console.error("❌ 打包失败:", error);
process.exit(1);
}
// -------------------------
// 2. 获取本机局域网 IP
// -------------------------
function getLocalIP() {
const interfaces = os.networkInterfaces();
for (const dev of Object.values(interfaces)) {
if (!dev) continue;
for (const detail of dev) {
if (detail.family === "IPv4" && !detail.internal) {
return detail.address;
}
}
}
return "localhost";
}
const localIP = getLocalIP();
// -------------------------
// 3. 启动 HTTPS 本地服务器
// -------------------------
const DIST_DIR = resolve(__dirname, "../dist/build/h5");
const PORT = 8080;
// API 代理配置
const PROXY_CONFIG = {
"/api": {
target: "https://xxx目标服务器地址",
changeOrigin: true,
pathRewrite: { "^/api": "" },
},
};
const app = express();
// 挂载代理
for (const [path, options] of Object.entries(PROXY_CONFIG)) {
app.use(path, createProxyMiddleware(options));
}
// 托管静态资源
app.use(
serveStatic(DIST_DIR, {
index: 'index.html',
fallthrough: true, // 重要:让静态文件服务在找不到文件时继续
})
);
// SPA 路由回退处理
// 问题:History 模式直接访问 /pages/user-detail 会 404
// 原理:服务器对包含 /pages/ 的路径返回 index.html,让前端路由接管
// 参数处理:前端通过 window.location 获取完整 URL 路径和参数
app.use((req, res, next) => {
if (req.path.startsWith('/api')) return next(); // API 请求跳过
if (req.path.includes('/pages/')) {
console.log(`🔄 SPA 路由回退: ${req.originalUrl} -> index.html`);
// 此时浏览器地址栏保持原始 URL 不变:
// https://localhost:8080/pages/user-detail?userId=123&status=Online
// 前端应用启动后可以通过以下方式获取完整路径和参数:
// const fullPath = window.location.pathname; // /pages/user-detail
// const searchParams = window.location.search; // ?userId=123&status=Online
// const fullUrl = window.location.href; // 完整 URL
// 然后使用 router.replace(fullPath + searchParams) 跳转到对应页面
return res.sendFile(resolve(DIST_DIR, 'index.html'));
}
next(); // 其他请求继续(静态资源等)
});
// HTTPS 证书路径
const options = {
key: fs.readFileSync("./cert/localhost+2-key.pem"),
cert: fs.readFileSync("./cert/localhost+2.pem"),
};
// 启动 HTTPS 服务
https.createServer(options, app).listen(PORT, "0.0.0.0", () => {
const url = `https://${localIP}:${PORT}`;
console.log(`🚀 HTTPS 本地服务器已启动: ${url}`);
open(url); // 自动在电脑打开浏览器
console.log(`📱 局域网手机可访问: ${url}`);
});
5. package.json 脚本配置
"scripts": {
"preview": "node scripts/preview.js"
}
6. 启动一键 HTTPS 预览
npm run preview
输出示例:
📦 开始打包 H5...
✅ 打包完成!
🚀 HTTPS 本地服务器已启动: https://192.168.31.161:8080
📱 局域网手机可访问: https://192.168.31.161:8080
- 电脑打开浏览器 → https://localhost:8080
- 手机浏览器打开 → https://192.168.31.161:8080(需信任 rootCA.pem)
五、总结
通过上述方案,你可以实现:
- 打包 H5 + 本地 HTTPS 预览
- 支持接口代理,解决跨域
- 局域网手机访问 HTTPS
- 一键启动 + 自动打开浏览器
核心原理:
- mkcert 生成自签名证书
- iOS 信任 rootCA.pem
- Express 托管静态资源 + http-proxy-middleware 转发 API
这样,你就能在开发阶段 完整模拟生产环境,同时解决跨域和 HTTPS 访问问题。
更多推荐
所有评论(0)