mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-09 21:49:37 +00:00
fix(frontend): stabilize Windows development startup (#5036)
* Stabilize Windows frontend development startup Next.js Turbopack can infer a user-level workspace root and hit an internal Rust task panic on Windows. Keep the frontend root explicit and select Webpack on Windows while preserving Turbopack elsewhere. Constraint: The existing non-Windows development workflow should keep Turbopack. Rejected: Disable Turbopack on every platform | unnecessary performance regression. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Revisit the Windows Webpack fallback after the upstream Turbopack panic is resolved. Tested: Prettier, ESLint, TypeScript, 139 frontend test files with 1060 tests, and Windows pnpm dev smoke test on port 3010. Not-tested: Production build and browser E2E suite. Related: #4957 * Make the frontend bundler fallback diagnosable Keep the Windows Webpack fallback while allowing local diagnosis to select either supported Next.js bundler. Resolve the Turbopack root through the Node 20-compatible URL API so the explicit workspace boundary remains effective across the declared Next.js engine range. Constraint: Windows defaults to Webpack because of the observed Turbopack panic, while non-Windows defaults to Turbopack. Rejected: Accept arbitrary bundler values | Next.js only supports the two explicit flags and invalid values should fail early. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Revisit the default Windows fallback when the upstream TaskGuard issue is resolved; the override is intentionally retained for diagnosis. Tested: Frontend format, ESLint, TypeScript, 139-file/1062-test Rstest suite, target launcher tests, and next.config.js absolute-root load check. Not-tested: Production build and browser E2E suite.
This commit is contained in:
parent
ed336ec3dd
commit
bdd68469c1
@ -151,7 +151,7 @@ cd backend && make lint # ruff check
|
||||
cd backend && make format # ruff format
|
||||
|
||||
# Frontend (see frontend/AGENTS.md for the full set)
|
||||
cd frontend && pnpm dev # Dev server with Turbopack (port 3000)
|
||||
cd frontend && pnpm dev # Dev server: Webpack on Windows, Turbopack elsewhere (override with DEER_FLOW_DEV_BUNDLER)
|
||||
cd frontend && pnpm check # Lint + type check (run before committing)
|
||||
cd frontend && pnpm test # Unit tests
|
||||
```
|
||||
@ -188,7 +188,7 @@ cd frontend && pnpm rstest run <pattern> # e.g. pnpm rstest run my-component
|
||||
### Logs
|
||||
|
||||
- Docker stack: `make docker-logs` (or `docker compose -f docker/... logs -f <svc>`).
|
||||
- Local `make dev`: each service logs to its own terminal pane. Frontend Turbopack
|
||||
- Local `make dev`: each service logs to its own terminal pane. Frontend dev-server
|
||||
errors surface in the browser console at `localhost:3000`; backend tracebacks appear
|
||||
in the Gateway terminal.
|
||||
|
||||
|
||||
@ -17,21 +17,23 @@ DeerFlow Frontend is a Next.js 16 web interface for an AI agent system. It commu
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------- | ------------------------------------------------- |
|
||||
| `pnpm dev` | Dev server with Turbopack (http://localhost:3000) |
|
||||
| `pnpm build` | Production build |
|
||||
| `pnpm check` | Lint + type check (run before committing) |
|
||||
| `pnpm lint` | ESLint only |
|
||||
| `pnpm lint:fix` | ESLint with auto-fix |
|
||||
| `pnpm format` | Prettier check (`pnpm format:write` to apply) |
|
||||
| `pnpm test` | Run unit tests with Rstest |
|
||||
| `pnpm test:e2e` | Run E2E tests with Playwright (Chromium) |
|
||||
| `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) |
|
||||
| `pnpm start` | Start production server |
|
||||
| Command | Purpose |
|
||||
| ---------------- | ------------------------------------------------------------------- |
|
||||
| `pnpm dev` | Platform-aware dev server (Webpack on Windows, Turbopack elsewhere) |
|
||||
| `pnpm build` | Production build |
|
||||
| `pnpm check` | Lint + type check (run before committing) |
|
||||
| `pnpm lint` | ESLint only |
|
||||
| `pnpm lint:fix` | ESLint with auto-fix |
|
||||
| `pnpm format` | Prettier check (`pnpm format:write` to apply) |
|
||||
| `pnpm test` | Run unit tests with Rstest |
|
||||
| `pnpm test:e2e` | Run E2E tests with Playwright (Chromium) |
|
||||
| `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) |
|
||||
| `pnpm start` | Start production server |
|
||||
|
||||
Unit tests live under `tests/unit/` and mirror the `src/` layout (e.g., `tests/unit/core/api/stream-mode.test.ts` tests `src/core/api/stream-mode.ts`). Powered by Rstest; import source modules via the `@/` path alias.
|
||||
|
||||
Use `DEER_FLOW_DEV_BUNDLER=turbo` or `DEER_FLOW_DEV_BUNDLER=webpack` with `pnpm dev` to override the platform default when diagnosing a local Next.js bundler issue.
|
||||
|
||||
Rstest runs them as two projects (`rstest.config.ts`). `*.test.ts` / `*.test.tsx` run in a plain **node** environment — that is nearly the whole suite, and it is the default for anything that is pure logic. `*.dom.test.ts` / `*.dom.test.tsx` run in **happy-dom**, for tests that need a document: hooks driven through `renderHook` from `@testing-library/react`, and components. Keep the split — a DOM environment costs roughly 3x the runtime of the node suite, so tests that do not render should not opt into it. A hook whose behavior only exists under real React (effect ordering, cleanup on unmount, re-render on store change) belongs in a `.dom.test.*` file rather than a node test that mocks `react` itself.
|
||||
|
||||
E2E tests live under `tests/e2e/` and use Playwright with Chromium. They mock all backend APIs via `page.route()` network interception and test real page interactions (navigation, chat input, streaming responses). Config: `playwright.config.ts`.
|
||||
|
||||
@ -127,24 +127,24 @@ src/
|
||||
|
||||
## Scripts
|
||||
|
||||
| Command | Description |
|
||||
| ------------------- | --------------------------------------- |
|
||||
| `pnpm dev` | Start development server with Turbopack |
|
||||
| `pnpm build` | Build for production |
|
||||
| `pnpm start` | Start production server |
|
||||
| `pnpm test` | Run unit tests with Rstest |
|
||||
| `pnpm test:e2e` | Run E2E tests with Playwright |
|
||||
| `pnpm format` | Check formatting with Prettier |
|
||||
| `pnpm format:write` | Apply formatting with Prettier |
|
||||
| `pnpm lint` | Run ESLint |
|
||||
| `pnpm lint:fix` | Fix ESLint issues |
|
||||
| `pnpm typecheck` | Run TypeScript type checking |
|
||||
| `pnpm check` | Run both lint and typecheck |
|
||||
| Command | Description |
|
||||
| ------------------- | ------------------------------------------------------------------ |
|
||||
| `pnpm dev` | Start development server (Webpack on Windows, Turbopack elsewhere) |
|
||||
| `pnpm build` | Build for production |
|
||||
| `pnpm start` | Start production server |
|
||||
| `pnpm test` | Run unit tests with Rstest |
|
||||
| `pnpm test:e2e` | Run E2E tests with Playwright |
|
||||
| `pnpm format` | Check formatting with Prettier |
|
||||
| `pnpm format:write` | Apply formatting with Prettier |
|
||||
| `pnpm lint` | Run ESLint |
|
||||
| `pnpm lint:fix` | Fix ESLint issues |
|
||||
| `pnpm typecheck` | Run TypeScript type checking |
|
||||
| `pnpm check` | Run both lint and typecheck |
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Uses pnpm workspaces (see `packageManager` in package.json)
|
||||
- Turbopack enabled by default in development for faster builds
|
||||
- Turbopack is used by default in development except on Windows, where Webpack avoids known Turbopack runtime instability. Set `DEER_FLOW_DEV_BUNDLER=turbo` or `DEER_FLOW_DEV_BUNDLER=webpack` to override the platform default for local diagnosis.
|
||||
- Environment validation can be skipped with `SKIP_ENV_VALIDATION=1` (useful for Docker)
|
||||
- Backend API URLs are optional; nginx proxy is used by default in development
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
||||
* for Docker builds.
|
||||
@ -25,6 +27,9 @@ const config = {
|
||||
locales: ["en", "zh"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
turbopack: {
|
||||
root: fileURLToPath(new URL(".", import.meta.url)),
|
||||
},
|
||||
devIndicators: false,
|
||||
allowedDevOrigins: getAllowedDevOrigins(),
|
||||
async rewrites() {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"demo:save": "node scripts/save-demo.js",
|
||||
"build": "next build",
|
||||
"check": "eslint . --ext .ts,.tsx && tsc --noEmit",
|
||||
"dev": "next dev --turbo",
|
||||
"dev": "node scripts/dev.mjs",
|
||||
"format": "prettier --check .",
|
||||
"format:write": "prettier --write .",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
|
||||
71
frontend/scripts/dev.mjs
Normal file
71
frontend/scripts/dev.mjs
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* @param {string} platform
|
||||
* @param {Record<string, string | undefined>} env
|
||||
*/
|
||||
export function getDevBundler(platform = process.platform, env = process.env) {
|
||||
const override = env.DEER_FLOW_DEV_BUNDLER?.trim();
|
||||
if (override) {
|
||||
if (override !== "turbo" && override !== "webpack") {
|
||||
throw new Error(
|
||||
'DEER_FLOW_DEV_BUNDLER must be either "turbo" or "webpack"',
|
||||
);
|
||||
}
|
||||
return override;
|
||||
}
|
||||
return platform === "win32" ? "webpack" : "turbo";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} platform
|
||||
* @param {string[]} extraArgs
|
||||
* @param {Record<string, string | undefined>} env
|
||||
*/
|
||||
export function getNextDevArgs(
|
||||
platform = process.platform,
|
||||
extraArgs = [],
|
||||
env = process.env,
|
||||
) {
|
||||
const nextArgs = extraArgs[0] === "--" ? extraArgs.slice(1) : extraArgs;
|
||||
return ["dev", `--${getDevBundler(platform, env)}`, ...nextArgs];
|
||||
}
|
||||
|
||||
function startDevServer() {
|
||||
const frontendDir = fileURLToPath(new URL("..", import.meta.url));
|
||||
const nextBin = fileURLToPath(
|
||||
new URL("../node_modules/next/dist/bin/next", import.meta.url),
|
||||
);
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[nextBin, ...getNextDevArgs(process.platform, process.argv.slice(2))],
|
||||
{
|
||||
cwd: frontendDir,
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
|
||||
child.on("error", (error) => {
|
||||
console.error(`Failed to start Next.js: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exitCode = code ?? 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[1] &&
|
||||
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
) {
|
||||
startDevServer();
|
||||
}
|
||||
40
frontend/tests/unit/scripts/dev.test.ts
Normal file
40
frontend/tests/unit/scripts/dev.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { describe, expect, test } from "@rstest/core";
|
||||
|
||||
import { getDevBundler, getNextDevArgs } from "../../../scripts/dev.mjs";
|
||||
|
||||
describe("frontend dev launcher", () => {
|
||||
test("uses webpack on Windows to avoid Turbopack runtime instability", () => {
|
||||
expect(getDevBundler("win32")).toBe("webpack");
|
||||
expect(getNextDevArgs("win32")).toEqual(["dev", "--webpack"]);
|
||||
});
|
||||
|
||||
test("allows an explicit bundler override on every platform", () => {
|
||||
expect(getDevBundler("win32", { DEER_FLOW_DEV_BUNDLER: "turbo" })).toBe(
|
||||
"turbo",
|
||||
);
|
||||
expect(
|
||||
getNextDevArgs("linux", [], { DEER_FLOW_DEV_BUNDLER: "webpack" }),
|
||||
).toEqual(["dev", "--webpack"]);
|
||||
});
|
||||
|
||||
test("rejects an unsupported bundler override", () => {
|
||||
expect(() =>
|
||||
getDevBundler("linux", { DEER_FLOW_DEV_BUNDLER: "invalid" }),
|
||||
).toThrow('DEER_FLOW_DEV_BUNDLER must be either "turbo" or "webpack"');
|
||||
});
|
||||
|
||||
test("passes through extra Next.js arguments", () => {
|
||||
expect(getNextDevArgs("win32", ["--", "--port", "3302"])).toEqual([
|
||||
"dev",
|
||||
"--webpack",
|
||||
"--port",
|
||||
"3302",
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps Turbopack for non-Windows development", () => {
|
||||
expect(getDevBundler("linux")).toBe("turbo");
|
||||
expect(getDevBundler("darwin")).toBe("turbo");
|
||||
expect(getNextDevArgs("linux")).toEqual(["dev", "--turbo"]);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user