fix(dev): proxy content assets to cli server
The CLI content server serves the content directory at the URL root, so images and other content assets are requested as root-relative paths (e.g. /adventures/map.png). The dev proxy only forwarded /content, so those requests fell through to historyApiFallback and the browser got index.html instead of the asset. Forward any request that maps to a real file under content/ to the CLI server, keeping /static/ bundles on the web dev server.
This commit is contained in:
+7
-2
@@ -252,8 +252,13 @@ npm test -- --watch
|
|||||||
- Web 前端:rsbuild 开发服务器,端口 **5173**
|
- Web 前端:rsbuild 开发服务器,端口 **5173**
|
||||||
- CLI 内容服务器:`tttk serve`,端口 **3000**(内容 API + Journal)
|
- CLI 内容服务器:`tttk serve`,端口 **3000**(内容 API + Journal)
|
||||||
|
|
||||||
rsbuild 会把 `/__CONTENT_INDEX.json`、`/__COMPLETIONS.json`、`/__CONTENT_REGISTRY.json`、`/content`、`/.ttrpg` 代理到 CLI 的
|
rsbuild 会把 `/__CONTENT_INDEX.json`、`/__COMPLETIONS.json`、`/__CONTENT_REGISTRY.json`、`/.ttrpg`
|
||||||
3000 端口,因此必须先启动 CLI,否则前端拿不到内容。
|
以及内容目录中的静态资源(图片、CSS、CSV 等)代理到 CLI 的 3000 端口,因此必须先启动 CLI,
|
||||||
|
否则前端拿不到内容。
|
||||||
|
|
||||||
|
CLI 把内容目录挂在 URL 根路径下(例如 `/adventures/map.png`,**不是** `/content/...`),
|
||||||
|
所以 rsbuild 会按“请求路径是否对应内容目录中的真实文件”来决定是否代理;`/static/` 下的
|
||||||
|
前端构建产物仍由 web dev server 自己提供。
|
||||||
|
|
||||||
### Web 开发
|
### Web 开发
|
||||||
|
|
||||||
|
|||||||
+55
-7
@@ -3,6 +3,43 @@ import { pluginBabel } from "@rsbuild/plugin-babel";
|
|||||||
import { pluginSolid } from "@rsbuild/plugin-solid";
|
import { pluginSolid } from "@rsbuild/plugin-solid";
|
||||||
import tailwindcss from "@tailwindcss/postcss";
|
import tailwindcss from "@tailwindcss/postcss";
|
||||||
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
||||||
|
import { statSync } from "node:fs";
|
||||||
|
import { join, resolve } from "node:path";
|
||||||
|
|
||||||
|
// The CLI content server (`npm run dev:cli`, i.e. `tttk serve ./content`) keeps
|
||||||
|
// port 3000. It serves the content directory at the URL *root*, so content
|
||||||
|
// assets are requested as root-relative paths (e.g. `/adventures/map.png`),
|
||||||
|
// not under `/content`.
|
||||||
|
const CLI_TARGET = "http://localhost:3000";
|
||||||
|
const CONTENT_DIR = resolve(process.cwd(), "content");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when a request targets a real file inside the content directory.
|
||||||
|
*
|
||||||
|
* Mirrors the CLI server's static handling: only files are served, so a
|
||||||
|
* directory path falls through to the SPA fallback. Requests for content
|
||||||
|
* assets must be proxied to the CLI; otherwise they reach
|
||||||
|
* `historyApiFallback` and the browser receives `index.html` instead of the
|
||||||
|
* asset (which is why images failed to render in dev).
|
||||||
|
*/
|
||||||
|
function isContentFile(pathname: string): boolean {
|
||||||
|
// The web dev server owns its own bundled assets under /static/.
|
||||||
|
if (pathname.startsWith("/static/")) return false;
|
||||||
|
|
||||||
|
let relativePath: string;
|
||||||
|
try {
|
||||||
|
relativePath = decodeURIComponent(pathname).replace(/^\/+/, "");
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!relativePath) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return statSync(join(CONTENT_DIR, relativePath)).isFile();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -51,14 +88,25 @@ export default defineConfig({
|
|||||||
// Dev web server runs on its own port; the CLI content server keeps
|
// Dev web server runs on its own port; the CLI content server keeps
|
||||||
// port 3000 (see the proxy targets below).
|
// port 3000 (see the proxy targets below).
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: [
|
||||||
// Proxy content API requests to the CLI dev server (npm run tttk)
|
{
|
||||||
"/__CONTENT_INDEX.json": "http://localhost:3000",
|
// Content API requests served by the CLI dev server (npm run dev:cli).
|
||||||
"/__COMPLETIONS.json": "http://localhost:3000",
|
context: [
|
||||||
"/__CONTENT_REGISTRY.json": "http://localhost:3000",
|
"/__CONTENT_INDEX.json",
|
||||||
"/content": "http://localhost:3000",
|
"/__COMPLETIONS.json",
|
||||||
"/.ttrpg": "http://localhost:3000",
|
"/__CONTENT_REGISTRY.json",
|
||||||
|
"/.ttrpg",
|
||||||
|
],
|
||||||
|
target: CLI_TARGET,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Content assets (images, css, csv, …) live at the URL root on the CLI
|
||||||
|
// server, so forward any request that maps to a real file under
|
||||||
|
// content/ instead of letting it hit the SPA fallback.
|
||||||
|
context: (pathname: string) => isContentFile(pathname),
|
||||||
|
target: CLI_TARGET,
|
||||||
|
},
|
||||||
|
],
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
* Dev 工作流:运行 CLI 服务器并在 rsbuild 配置中设置代理
|
* Dev 工作流:运行 CLI 服务器并在 rsbuild 配置中设置代理
|
||||||
* > npm run tttk serve ./content
|
* > npm run tttk serve ./content
|
||||||
* > npm run dev
|
* > npm run dev
|
||||||
* rsbuild 会将 /__CONTENT_INDEX.json、/__COMPLETIONS.json、/__CONTENT_REGISTRY.json、/content/ 代理到 CLI 服务器
|
* rsbuild 会将 /__CONTENT_INDEX.json、/__COMPLETIONS.json、/__CONTENT_REGISTRY.json、
|
||||||
|
* /.ttrpg 以及内容目录中的静态资源代理到 CLI 服务器
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
Reference in New Issue
Block a user