diff --git a/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx b/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx index 200204a8ea1f..de267290262d 100644 --- a/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx +++ b/packages/document/main-doc/docs/en/guides/advanced-features/web-server.mdx @@ -252,10 +252,6 @@ const time: UnstableMiddleware = async (c: UnstableMiddlewareContext, next) => { export const unstableMiddleware: UnstableMiddleware[] = [time]; ``` -:::info -For detailed API and more usage, see [UnstableMiddleware](/apis/app/runtime/web-server/unstable_middleware). -::: - ### Hooks :::warning @@ -287,10 +283,6 @@ Best practices when using Hooks: 2. Handle Rewrite and Redirect in afterMatch. 3. Inject HTML content in afterRender. -:::info -For detailed API and more usage, see [Hook](/apis/app/runtime/web-server/hook). -::: - ## Migrate to the New Version of Custom Web Server ### Migration Background @@ -443,11 +435,11 @@ type AfterRenderContext = { Hook Context is mostly consistent with Middleware Context, so we need to pay extra attention to the additional parts of different Hooks. -| UnstableMiddleware | Hono | Description | -| :----------------- | :----------- | :----------------------------------------------------------------------------------------- | -| `router.redirect` | `c.redirect` | Refer to [Hono Context redirect](https://hono.dev/docs/api/context#redirect) documentation | -| `router.rewrite` | - | No corresponding capability provided at the moment | -| template API | `c.res` | Refer to [Hono Context res](https://hono.dev/docs/api/context#res) documentation | +| UnstableMiddleware | Hono | Description | +| :----------------- | :----------------- | :----------------------------------------------------------------------------------------- | +| `router.redirect` | `c.redirect` | Refer to [Hono Context redirect](https://hono.dev/docs/api/context#redirect) documentation | +| `router.rewrite` | `c.rewriteByEntry` | Rewrite to the specified entry. Routing or external urls are not supported | +| template API | `c.res` | Refer to [Hono Context res](https://hono.dev/docs/api/context#res) documentation | ### Differences in Next API diff --git a/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx b/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx index a14802d85ec4..8d09b9d33f70 100644 --- a/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx +++ b/packages/document/main-doc/docs/zh/guides/advanced-features/web-server.mdx @@ -429,11 +429,11 @@ type AfterRenderContext = { Hook Context 大部分和 Middleware Context 一致,因此我们要额外关注不同 Hook 多余的部分。 -| UnstableMiddleware | Hono | 说明 | -| :----------------- | :----------- | :---------------------------------------------------------------------------- | -| `router.redirect` | `c.redirect` | 参考 [Hono Context redirect](https://hono.dev/docs/api/context#redirect) 文档 | -| `router.rewrite` | - | 暂时没有提供对应的能力 | -| template API | `c.res` | 参考 [Hono Context res](https://hono.dev/docs/api/context#res) 文档 | +| UnstableMiddleware | Hono | 说明 | +| :----------------- | :----------------- | :---------------------------------------------------------------------------- | +| `router.redirect` | `c.redirect` | 参考 [Hono Context redirect](https://hono.dev/docs/api/context#redirect) 文档 | +| `router.rewrite` | `c.rewriteByEntry` | rewrite 到指定 entry,不支持路由或外部 url | +| template API | `c.res` | 参考 [Hono Context res](https://hono.dev/docs/api/context#res) 文档 | ### Next API 差异 diff --git a/packages/server/core/src/plugins/index.ts b/packages/server/core/src/plugins/index.ts index 3dd30f0dbb8a..629dfa7d8f3c 100644 --- a/packages/server/core/src/plugins/index.ts +++ b/packages/server/core/src/plugins/index.ts @@ -14,3 +14,4 @@ export { } from './default'; export { compatPlugin, handleSetupResult } from './compat'; export { injectConfigMiddlewarePlugin } from './middlewares'; +export { routerRewritePlugin } from './rewrite'; diff --git a/packages/server/core/src/plugins/rewrite.ts b/packages/server/core/src/plugins/rewrite.ts new file mode 100644 index 000000000000..26b14aa7e117 --- /dev/null +++ b/packages/server/core/src/plugins/rewrite.ts @@ -0,0 +1,38 @@ +import type { Context, Next } from 'hono'; +import type { ServerPlugin } from '../types'; + +declare module 'hono' { + interface Context { + rewriteByEntry: (entry: string) => void; + } +} + +export const routerRewritePlugin = (): ServerPlugin => ({ + name: '@Modern-js/plugin-router-rewrite-plugin', + setup(api) { + api.onPrepare(() => { + const { middlewares, routes } = api.getServerContext(); + if (!routes) { + return; + } + + middlewares.push({ + name: 'router-rewrite', + order: 'pre', + handler: async (c: Context, next: Next) => { + c.rewriteByEntry = (entry: string) => { + const rewriteRoute = routes + .filter(route => !route.isApi) + .find(route => route.entryName === entry); + + if (rewriteRoute) { + c.set('matchPathname', rewriteRoute.urlPath); + c.set('matchEntryName', entry); + } + }; + await next(); + }, + }); + }); + }, +}); diff --git a/packages/server/prod-server/src/apply.ts b/packages/server/prod-server/src/apply.ts index 2d21b67492b2..166993e3435a 100644 --- a/packages/server/prod-server/src/apply.ts +++ b/packages/server/prod-server/src/apply.ts @@ -9,6 +9,7 @@ import { injectConfigMiddlewarePlugin, onError, renderPlugin, + routerRewritePlugin, } from '@modern-js/server-core'; import { injectNodeSeverPlugin, @@ -59,6 +60,7 @@ export async function applyPlugins( const { middlewares, renderMiddlewares } = options.serverConfig || {}; const plugins = [ + routerRewritePlugin(), ...(nodeServer ? [injectNodeSeverPlugin({ nodeServer })] : []), ...createDefaultPlugins({ cacheConfig, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09722f7debcf..03a26ff04505 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4081,6 +4081,9 @@ importers: '@modern-js/runtime': specifier: workspace:* version: link:../../../packages/runtime/plugin-runtime + '@modern-js/server-runtime': + specifier: workspace:* + version: link:../../../packages/server/server-runtime fs-extra: specifier: ^10.1.0 version: 10.1.0 @@ -4090,6 +4093,12 @@ importers: react-dom: specifier: ^19.1.1 version: 19.1.1(react@19.1.1) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.8.8)(typescript@5.6.3) + tsconfig-paths: + specifier: ~3.14.1 + version: 3.14.1 devDependencies: '@modern-js/app-tools': specifier: workspace:* diff --git a/tests/integration/routes/modern.config.ts b/tests/integration/routes/modern.config.ts index 3984aa9abae2..5b6653c5e2b0 100644 --- a/tests/integration/routes/modern.config.ts +++ b/tests/integration/routes/modern.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ loaderFailureMode: 'clientRender', }, four: false, + rewrite: false, }, }, }); diff --git a/tests/integration/routes/package.json b/tests/integration/routes/package.json index 1bf965bfbf1b..feb4570c4a22 100644 --- a/tests/integration/routes/package.json +++ b/tests/integration/routes/package.json @@ -15,7 +15,10 @@ "@modern-js/runtime": "workspace:*", "fs-extra": "^10.1.0", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "@modern-js/server-runtime": "workspace:*", + "ts-node": "^10.9.2", + "tsconfig-paths": "~3.14.1" }, "devDependencies": { "@modern-js/app-tools": "workspace:*", @@ -24,6 +27,7 @@ "@types/node": "^20", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", + "typescript": "^5" } } diff --git a/tests/integration/routes/server/.eslintrc.js b/tests/integration/routes/server/.eslintrc.js new file mode 100644 index 000000000000..2538816f15b1 --- /dev/null +++ b/tests/integration/routes/server/.eslintrc.js @@ -0,0 +1,18 @@ +module.exports = { + root: true, + plugins: ['prettier'], + rules: { + // eslint-disable-next-line + 'prettier/prettier': 'error', + }, + overrides: [ + { + files: ['*.js', '*.jsx'], + extends: '@byted/eslint-config-standard', + }, + { + files: ['*.ts', '*.tsx'], + extends: '@byted/eslint-config-standard-ts', + }, + ], +}; diff --git a/tests/integration/routes/server/modern.server.ts b/tests/integration/routes/server/modern.server.ts new file mode 100644 index 000000000000..63fe6ae59e7a --- /dev/null +++ b/tests/integration/routes/server/modern.server.ts @@ -0,0 +1,21 @@ +import { + type MiddlewareHandler, + defineServerConfig, +} from '@modern-js/server-runtime'; + +const renderTiming: MiddlewareHandler = async (c, next) => { + if (c.req.path === '/rewrite') { + c.rewriteByEntry('one'); + } + + await next(); +}; + +export default defineServerConfig({ + renderMiddlewares: [ + { + name: 'render-timing', + handler: renderTiming, + }, + ], +}); diff --git a/tests/integration/routes/src/rewrite/App.css b/tests/integration/routes/src/rewrite/App.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/integration/routes/src/rewrite/App.tsx b/tests/integration/routes/src/rewrite/App.tsx new file mode 100644 index 000000000000..000d0f81c7c4 --- /dev/null +++ b/tests/integration/routes/src/rewrite/App.tsx @@ -0,0 +1,5 @@ +import './App.css'; + +const App = () =>

rewrite page

; + +export default App; diff --git a/tests/integration/routes/tsconfig.json b/tests/integration/routes/tsconfig.json index f2c10d4cd9e9..2bd10fd4bd4c 100644 --- a/tests/integration/routes/tsconfig.json +++ b/tests/integration/routes/tsconfig.json @@ -9,5 +9,5 @@ "@shared/*": ["./shared/*"] } }, - "include": ["src", "shared", "config"] + "include": ["src", "shared", "config", "server"] }