diff --git a/docs/docs/docs/guides/env-variables.md b/docs/docs/docs/guides/env-variables.md index 8f18c40e7db8..9114a8e4c38a 100644 --- a/docs/docs/docs/guides/env-variables.md +++ b/docs/docs/docs/guides/env-variables.md @@ -198,6 +198,14 @@ $ UMI_PLUGINS=./path/to/plugin1,./path/to/plugin2 umi dev $ UMI_PRESETS=./path/to/preset1,./path/to/preset2 umi dev ``` +### UMI_DEV_SERVER_COMPRESS + +默认 Umi 开发服务器自带 [compress](https://github.com/expressjs/compression) 压缩中间件,这会使开发时 SSE 数据的传输 [无法流式获取](https://github.com/umijs/umi/issues/12144) ,通过指定 `UMI_DEV_SERVER_COMPRESS=none` 来关闭 compress 压缩功能: + +```bash + UMI_DEV_SERVER_COMPRESS=none umi dev +``` + ### WEBPACK_FS_CACHE_DEBUG 开启 webpack 的物理缓存 debug 日志。 diff --git a/examples/with-no-compress-for-sse/.umirc.ts b/examples/with-no-compress-for-sse/.umirc.ts new file mode 100644 index 000000000000..ff8b4c56321a --- /dev/null +++ b/examples/with-no-compress-for-sse/.umirc.ts @@ -0,0 +1 @@ +export default {}; diff --git a/examples/with-no-compress-for-sse/package.json b/examples/with-no-compress-for-sse/package.json new file mode 100644 index 000000000000..949318ff8d8f --- /dev/null +++ b/examples/with-no-compress-for-sse/package.json @@ -0,0 +1,15 @@ +{ + "name": "@example/with-no-compress-for-sse", + "private": true, + "scripts": { + "build": "umi build", + "dev": "umi dev", + "dev:nocompress": "cross-env UMI_DEV_SERVER_COMPRESS=none npm run dev" + }, + "dependencies": { + "umi": "workspace:*" + }, + "devDependencies": { + "cross-env": "^7.0.3" + } +} diff --git a/examples/with-no-compress-for-sse/pages/index.tsx b/examples/with-no-compress-for-sse/pages/index.tsx new file mode 100644 index 000000000000..2ef38e04f2ef --- /dev/null +++ b/examples/with-no-compress-for-sse/pages/index.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from 'react'; + +class Event { + data: string; + timeString: string; + + constructor(data: string) { + this.data = data; + this.timeString = new Date().toLocaleTimeString(); + } +} + +export default function HomePage() { + const [events, setEvents] = useState([]); + + useEffect(() => { + console.log('开始请求'); + const eventSource = new EventSource('/events/number'); + let startEvent = new Event('开始请求'); + setEvents((prev) => [...prev, startEvent]); + eventSource.onmessage = function (e: any) { + let item = new Event(e.data); + setEvents((prev) => [...prev, item]); + }; + eventSource.onerror = (e) => { + console.log('EventSource failed:', e); + eventSource.close(); + }; + }, []); + + return ( +
+

{`演示:当默认存在 compress 时,数据无法流式获取。`}

+ + + + + + + + + {events.map((event, index) => ( + + + + + ))} + +
事件内容接收时间
{event.data}{event.timeString}
+
+ ); +} diff --git a/examples/with-no-compress-for-sse/plugin.ts b/examples/with-no-compress-for-sse/plugin.ts new file mode 100644 index 000000000000..e3d4901cdbbb --- /dev/null +++ b/examples/with-no-compress-for-sse/plugin.ts @@ -0,0 +1,8 @@ +import { IApi } from 'umi'; +import { sseMiddleware } from './sse-middleware'; + +export default (api: IApi) => { + api.onBeforeMiddleware(({ app }) => { + sseMiddleware(app); + }); +}; diff --git a/examples/with-no-compress-for-sse/readme.md b/examples/with-no-compress-for-sse/readme.md new file mode 100644 index 000000000000..15e5513baac3 --- /dev/null +++ b/examples/with-no-compress-for-sse/readme.md @@ -0,0 +1,9 @@ +# with-no-compress-for-sse + +### 背景 + +[来源](https://github.com/umijs/umi/issues/12144),在开发环境下由于 umi dev server 内置了 `compress` 中间件,导致 SSE 流在开发时传递不符合预期。 + +### 解决 + +本示例演示了此问题,并通过启动时附加 `UMI_DEV_SERVER_COMPRESS=none` 来关闭 `compress` 中间件,使 SSE 在本地开发时正常运作。 diff --git a/examples/with-no-compress-for-sse/sse-middleware.ts b/examples/with-no-compress-for-sse/sse-middleware.ts new file mode 100644 index 000000000000..bddbfcb10b11 --- /dev/null +++ b/examples/with-no-compress-for-sse/sse-middleware.ts @@ -0,0 +1,29 @@ +import type { Express } from '@umijs/bundler-utils/compiled/express'; + +export const sseMiddleware = (app: Express) => { + app.get('/events/number', (req, res) => { + console.log('new connection'); + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + + let counter = 1; + const intervalId = setInterval(() => { + if (counter === 5) { + clearInterval(intervalId); + res.end(`data: 事件${counter}\n\n`); + return; + } + res.write(`data: 事件${counter}\n\n`); + + counter++; + }, 1000); + + req.on('close', () => { + clearInterval(intervalId); + res.end(); + }); + }); +}; diff --git a/packages/bundler-webpack/src/server/server.ts b/packages/bundler-webpack/src/server/server.ts index 2553b66e7ded..b6296e867bae 100644 --- a/packages/bundler-webpack/src/server/server.ts +++ b/packages/bundler-webpack/src/server/server.ts @@ -44,8 +44,10 @@ export async function createServer(opts: IOpts): Promise { }), ); - // compression - app.use(require('@umijs/bundler-webpack/compiled/compression')()); + // See https://github.com/umijs/umi/issues/12144 + if (process.env.UMI_DEV_SERVER_COMPRESS !== 'none') { + app.use(require('@umijs/bundler-webpack/compiled/compression')()); + } // debug all js file app.use((req, res, next) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6852c0884cf0..c40b3bfe9894 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1383,6 +1383,16 @@ importers: specifier: ^18.0.10 version: 18.0.10 + examples/with-no-compress-for-sse: + dependencies: + umi: + specifier: workspace:* + version: link:../../packages/umi + devDependencies: + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + examples/with-react-17: dependencies: react: