fix(frontend): default to Webpack over Turbopack in dev to avoid PostCSS worker leak on macOS (#5133)

* fix(frontend): default to Webpack over Turbopack in dev to avoid PostCSS worker leak on macOS

On macOS arm64, Turbopack + Next.js 16.2.11 + Tailwind CSS v4 causes an
unbounded spawn of PostCSS evaluator processes that consume high CPU and
memory and never return a response. Webpack is unaffected.

Change the no-override default in getDevBundler() from platform-dependent
Turbopack (all non-Windows) to Webpack. DEER_FLOW_DEV_BUNDLER=turbo
continues to work as an explicit opt-in for local diagnosis.

Fixes #5132

* docs(frontend): address webpack default review feedback

* docs(frontend): clarify webpack default rationale
This commit is contained in:
Zheng Feng 2026-09-01 22:56:23 +08:00 committed by GitHub
parent c56c7293f8
commit ddd9aec558
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 41 additions and 40 deletions

View File

@ -160,7 +160,7 @@ cd backend && make lint # ruff check
cd backend && make format # ruff format cd backend && make format # ruff format
# Frontend (see frontend/AGENTS.md for the full set) # Frontend (see frontend/AGENTS.md for the full set)
cd frontend && pnpm dev # Dev server: Webpack on Windows, Turbopack elsewhere (override with DEER_FLOW_DEV_BUNDLER) cd frontend && pnpm dev # Dev server: Webpack by default (override with DEER_FLOW_DEV_BUNDLER=turbo)
cd frontend && pnpm check # Lint + type check (run before committing) cd frontend && pnpm check # Lint + type check (run before committing)
cd frontend && pnpm test # Unit tests cd frontend && pnpm test # Unit tests
``` ```

View File

@ -17,22 +17,22 @@ DeerFlow Frontend is a Next.js 16 web interface for an AI agent system. It commu
## Commands ## Commands
| Command | Purpose | | Command | Purpose |
| ---------------- | ------------------------------------------------------------------- | | ---------------- | --------------------------------------------- |
| `pnpm dev` | Platform-aware dev server (Webpack on Windows, Turbopack elsewhere) | | `pnpm dev` | Start the development server with Webpack |
| `pnpm build` | Production build | | `pnpm build` | Production build |
| `pnpm check` | Lint + type check (run before committing) | | `pnpm check` | Lint + type check (run before committing) |
| `pnpm lint` | ESLint only | | `pnpm lint` | ESLint only |
| `pnpm lint:fix` | ESLint with auto-fix | | `pnpm lint:fix` | ESLint with auto-fix |
| `pnpm format` | Prettier check (`pnpm format:write` to apply) | | `pnpm format` | Prettier check (`pnpm format:write` to apply) |
| `pnpm test` | Run unit tests with Rstest | | `pnpm test` | Run unit tests with Rstest |
| `pnpm test:e2e` | Run E2E tests with Playwright (Chromium) | | `pnpm test:e2e` | Run E2E tests with Playwright (Chromium) |
| `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) | | `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) |
| `pnpm start` | Start production server | | `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. 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. Webpack is the default development bundler. Use `DEER_FLOW_DEV_BUNDLER=turbo` with `pnpm dev` to opt in to Turbopack 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. 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.

View File

@ -127,24 +127,24 @@ src/
## Scripts ## Scripts
| Command | Description | | Command | Description |
| ------------------- | ------------------------------------------------------------------ | | ------------------- | ------------------------------------- |
| `pnpm dev` | Start development server (Webpack on Windows, Turbopack elsewhere) | | `pnpm dev` | Start development server with Webpack |
| `pnpm build` | Build for production | | `pnpm build` | Build for production |
| `pnpm start` | Start production server | | `pnpm start` | Start production server |
| `pnpm test` | Run unit tests with Rstest | | `pnpm test` | Run unit tests with Rstest |
| `pnpm test:e2e` | Run E2E tests with Playwright | | `pnpm test:e2e` | Run E2E tests with Playwright |
| `pnpm format` | Check formatting with Prettier | | `pnpm format` | Check formatting with Prettier |
| `pnpm format:write` | Apply formatting with Prettier | | `pnpm format:write` | Apply formatting with Prettier |
| `pnpm lint` | Run ESLint | | `pnpm lint` | Run ESLint |
| `pnpm lint:fix` | Fix ESLint issues | | `pnpm lint:fix` | Fix ESLint issues |
| `pnpm typecheck` | Run TypeScript type checking | | `pnpm typecheck` | Run TypeScript type checking |
| `pnpm check` | Run both lint and typecheck | | `pnpm check` | Run both lint and typecheck |
## Development Notes ## Development Notes
- Uses pnpm workspaces (see `packageManager` in package.json) - Uses pnpm workspaces (see `packageManager` in package.json)
- 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. - Webpack is the default development bundler until the upstream Turbopack PostCSS worker leak is fixed in a stable Next.js release (#5132). Set `DEER_FLOW_DEV_BUNDLER=turbo` to opt in to Turbopack for local diagnosis, or `DEER_FLOW_DEV_BUNDLER=webpack` to select Webpack explicitly. Reconsider the default after the stable fix is verified on macOS arm64 and Linux.
- Environment validation can be skipped with `SKIP_ENV_VALIDATION=1` (useful for Docker) - 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 - Backend API URLs are optional; nginx proxy is used by default in development

View File

@ -5,10 +5,10 @@ import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
/** /**
* @param {string} platform * @param {string} _platform
* @param {Record<string, string | undefined>} env * @param {Record<string, string | undefined>} env
*/ */
export function getDevBundler(platform = process.platform, env = process.env) { export function getDevBundler(_platform = process.platform, env = process.env) {
const override = env.DEER_FLOW_DEV_BUNDLER?.trim(); const override = env.DEER_FLOW_DEV_BUNDLER?.trim();
if (override) { if (override) {
if (override !== "turbo" && override !== "webpack") { if (override !== "turbo" && override !== "webpack") {
@ -18,7 +18,11 @@ export function getDevBundler(platform = process.platform, env = process.env) {
} }
return override; return override;
} }
return platform === "win32" ? "webpack" : "turbo"; // Keep Webpack as the cross-platform default while #5132's Turbopack
// PostCSS worker leak remains unfixed in a stable Next.js release. Retain
// the platform parameter so restoring the platform-aware default stays a
// small change once the upstream fix is stable and verified on macOS/Linux.
return "webpack";
} }
/** /**

View File

@ -3,11 +3,6 @@ import { describe, expect, test } from "@rstest/core";
import { getDevBundler, getNextDevArgs } from "../../../scripts/dev.mjs"; import { getDevBundler, getNextDevArgs } from "../../../scripts/dev.mjs";
describe("frontend dev launcher", () => { 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", () => { test("allows an explicit bundler override on every platform", () => {
expect(getDevBundler("win32", { DEER_FLOW_DEV_BUNDLER: "turbo" })).toBe( expect(getDevBundler("win32", { DEER_FLOW_DEV_BUNDLER: "turbo" })).toBe(
"turbo", "turbo",
@ -32,9 +27,11 @@ describe("frontend dev launcher", () => {
]); ]);
}); });
test("keeps Turbopack for non-Windows development", () => { test("uses webpack by default on every platform", () => {
expect(getDevBundler("linux")).toBe("turbo"); expect(getDevBundler("win32")).toBe("webpack");
expect(getDevBundler("darwin")).toBe("turbo"); expect(getDevBundler("linux")).toBe("webpack");
expect(getNextDevArgs("linux")).toEqual(["dev", "--turbo"]); expect(getDevBundler("darwin")).toBe("webpack");
expect(getNextDevArgs("win32")).toEqual(["dev", "--webpack"]);
expect(getNextDevArgs("linux")).toEqual(["dev", "--webpack"]);
}); });
}); });