diff --git a/.changeset/tender-cups-dream.md b/.changeset/tender-cups-dream.md new file mode 100644 index 000000000000..fdb3374bc5ef --- /dev/null +++ b/.changeset/tender-cups-dream.md @@ -0,0 +1,7 @@ +--- +'@modern-js/runtime': patch +'@modern-js/server-core': patch +--- + +feat: support ssr pass more context +feat: 支持传递更多的 ssr context diff --git a/packages/document/main-doc/docs/en/configure/app/server/ssr.mdx b/packages/document/main-doc/docs/en/configure/app/server/ssr.mdx index c60fd5f9bafa..a08ea58b8ff7 100644 --- a/packages/document/main-doc/docs/en/configure/app/server/ssr.mdx +++ b/packages/document/main-doc/docs/en/configure/app/server/ssr.mdx @@ -30,6 +30,7 @@ When the value type is `Object`, the following properties can be configured: - `inlineScript`: `boolean = true`, by default, SSR data is injected into HTML as inline scripts and assigned directly to global variables. Configure `false` to distribute JSON instead of assigning to global variables. - `disablePrerender`: `boolean = fasle`, To ensure compatibility with the old data request method (`useLoader`), by default, Modern.js performs pre-rendering of components. However, if developers want to reduce one rendering when there is no use of the useLoader API in your project, you can set the configuration `disablePrerender=true`. +- `unsafeHeaders`: `string[] = []`, For safety reasons, Modern.js does not add excessive content to SSR_DATA. Developers can use this configuration to specify the headers that need to be injected. ```ts title="modern.config.ts" export default defineConfig({ @@ -38,6 +39,7 @@ export default defineConfig({ forceCSR: true, mode: 'stream', inlineScript: false, + unsafeHeaders: ['User-Agent'], }, }, }); diff --git a/packages/document/main-doc/docs/zh/configure/app/server/ssr.mdx b/packages/document/main-doc/docs/zh/configure/app/server/ssr.mdx index 57c0c176df26..6f70f25d2056 100644 --- a/packages/document/main-doc/docs/zh/configure/app/server/ssr.mdx +++ b/packages/document/main-doc/docs/zh/configure/app/server/ssr.mdx @@ -30,6 +30,7 @@ export default defineConfig({ - `inlineScript`:`boolean = true`,默认情况下,SSR 的数据会以内联脚本的方式注入到 HTML 中,并且直接赋值给全局变量。配置为 `false` 后,会下发 JSON,而不是赋值给全局变量。 - `disablePrerender`: `boolean = fasle`, 为了兼容旧数据请求方式 - `useLoader`, 默认情况下 Modern.js 会对组件进行一次预渲染即有两次渲染。 开发者在保证项目中没有使用 useLoader Api 情况下, 可通过配置 `disablePrerender=true`来减少一次渲染。 +- `unsafeHeaders`: `string[] = []`, 为了安全考虑,Modern.js 不会往 SSR_DATA 添加过多的内容。开发者可以通过该配置,对需要注入的 headers 进行配置。 ```ts title="modern.config.ts" export default defineConfig({ @@ -39,6 +40,7 @@ export default defineConfig({ mode: 'stream', inlineScript: false, disablePrerender: true, + unsafeHeaders: ['User-Agent'], }, }, }); diff --git a/packages/runtime/plugin-runtime/src/ssr/cli/index.ts b/packages/runtime/plugin-runtime/src/ssr/cli/index.ts index de0b19a3587f..bf17d009d0da 100644 --- a/packages/runtime/plugin-runtime/src/ssr/cli/index.ts +++ b/packages/runtime/plugin-runtime/src/ssr/cli/index.ts @@ -207,6 +207,10 @@ export const ssrPlugin = (): CliPlugin => ({ typeof config.server?.ssr === 'object' ? Boolean(config.server.ssr.disablePrerender) : false; + const unsafeHeaders = + typeof config.server?.ssr === 'object' + ? config.server.ssr.unsafeHeaders + : undefined; plugins.push({ name: PLUGIN_IDENTIFIER, @@ -224,6 +228,7 @@ export const ssrPlugin = (): CliPlugin => ({ typeof enableInlineStyles === 'function' ? undefined : enableInlineStyles, + unsafeHeaders, }), }); } diff --git a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/entry.ts b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/entry.ts index 650a599b6756..b9ecc678355e 100644 --- a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/entry.ts +++ b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/entry.ts @@ -11,6 +11,7 @@ import { ModernSSRReactComponent, SSRPluginConfig, SSRServerContext, + RenderResult, } from '../types'; import prefetch from '../../prefetch'; import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID } from '../constants'; @@ -26,7 +27,6 @@ import { createReplaceHtml, createReplaceSSRDataScript, } from './buildHtml'; -import { RenderResult } from './type'; type EntryOptions = { ctx: SSRServerContext; @@ -39,11 +39,20 @@ const buildTemplateData = ( data: Record, renderLevel: RenderLevel, tracker: SSRTracker, + config: SSRPluginConfig, ) => { - const { request, enableUnsafeCtx } = context; - const unsafeContext = { - headers: request.headers, - }; + const { request } = context; + const { unsafeHeaders } = config; + + const headers = unsafeHeaders + ? Object.fromEntries( + Object.entries(request.headers).filter(([key, _]) => { + return unsafeHeaders + ?.map(header => header.toLowerCase()) + ?.includes(key.toLowerCase()); + }), + ) + : undefined; return { data, @@ -54,7 +63,7 @@ const buildTemplateData = ( pathname: request.pathname, host: request.host, url: request.url, - ...(enableUnsafeCtx ? unsafeContext : {}), + headers, }, reporter: { sessionId: tracker.sessionId, @@ -142,6 +151,7 @@ export default class Entry { prefetchData, this.result.renderLevel, this.tracker, + this.pluginConfig, ); const ssrDataScripts = this.getSSRDataScript(templateData, routerData); diff --git a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/loadable.ts b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/loadable.ts index 04e6f6640c35..591a4639709e 100644 --- a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/loadable.ts +++ b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/loadable.ts @@ -1,8 +1,7 @@ import { type ChunkAsset, ChunkExtractor } from '@loadable/server'; import { ReactElement } from 'react'; import { attributesToString } from '../utils'; -import { SSRPluginConfig } from '../types'; -import { RenderResult } from './type'; +import { SSRPluginConfig, RenderResult } from '../types'; import type { Collector } from './render'; const extname = (uri: string): string => { diff --git a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/styledComponent.ts b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/styledComponent.ts index b15a9b1b21a7..6a7607e1784e 100644 --- a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/styledComponent.ts +++ b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/styledComponent.ts @@ -1,6 +1,6 @@ import { ServerStyleSheet } from 'styled-components'; import { ReactElement } from 'react'; -import type { RenderResult } from './type'; +import type { RenderResult } from '../types'; import type { Collector } from './render'; class StyledCollector implements Collector { diff --git a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/type.ts b/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/type.ts deleted file mode 100644 index f9c2e36fba59..000000000000 --- a/packages/runtime/plugin-runtime/src/ssr/serverRender/renderToString/type.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum RenderLevel { - CLIENT_RENDER, - SERVER_PREFETCH, - SERVER_RENDER, -} - -export type RenderResult = { - renderLevel: RenderLevel; - html?: string; - chunksMap: { - js: string; - css: string; - }; -}; diff --git a/packages/runtime/plugin-runtime/src/ssr/serverRender/types.ts b/packages/runtime/plugin-runtime/src/ssr/serverRender/types.ts index 3b0ad8818d57..03b47476978f 100644 --- a/packages/runtime/plugin-runtime/src/ssr/serverRender/types.ts +++ b/packages/runtime/plugin-runtime/src/ssr/serverRender/types.ts @@ -1,10 +1,24 @@ import { ServerUserConfig } from '@modern-js/app-tools'; import type { BaseSSRServerContext } from '@modern-js/types'; import type { RuntimeContext } from '../../core'; -import { RenderLevel } from './renderToString/type'; import type { BuildHtmlCb } from './renderToString/buildHtml'; import type { SSRTracker } from './tracker'; +export enum RenderLevel { + CLIENT_RENDER, + SERVER_PREFETCH, + SERVER_RENDER, +} + +export type RenderResult = { + renderLevel: RenderLevel; + html?: string; + chunksMap: { + js: string; + css: string; + }; +}; + export type SSRServerContext = BaseSSRServerContext & { request: BaseSSRServerContext['request'] & { userAgent: string; @@ -15,7 +29,7 @@ export type SSRServerContext = BaseSSRServerContext & { tracker: SSRTracker; }; export type ModernSSRReactComponent = React.ComponentType; -export { RuntimeContext, RenderLevel }; +export { RuntimeContext }; export type SSRPluginConfig = { crossorigin?: boolean | 'anonymous' | 'use-credentials'; @@ -24,6 +38,7 @@ export type SSRPluginConfig = { enableInlineScripts?: boolean | RegExp; disablePrerender?: boolean; chunkLoadingGlobal?: string; + unsafeHeaders?: string[]; } & Exclude; export type ServerRenderOptions = { diff --git a/packages/server/core/src/types/config/server.ts b/packages/server/core/src/types/config/server.ts index 95b039469f68..90920fd9bf3d 100644 --- a/packages/server/core/src/types/config/server.ts +++ b/packages/server/core/src/types/config/server.ts @@ -41,6 +41,7 @@ export type SSR = preload?: boolean | SSRPreload; inlineScript?: boolean; disablePrerender?: boolean; + unsafeHeaders?: string[]; }; export type SSRByEntries = Record; diff --git a/tests/integration/ssr/fixtures/base/modern.config.ts b/tests/integration/ssr/fixtures/base/modern.config.ts index a29e321ba332..2c7422eff7cf 100644 --- a/tests/integration/ssr/fixtures/base/modern.config.ts +++ b/tests/integration/ssr/fixtures/base/modern.config.ts @@ -7,6 +7,7 @@ export default applyBaseConfig({ server: { ssr: { disablePrerender: true, + unsafeHeaders: ['Host'], }, }, tools: { diff --git a/tests/integration/ssr/tests/base.test.ts b/tests/integration/ssr/tests/base.test.ts index f87bc6b46073..e2982896c96d 100644 --- a/tests/integration/ssr/tests/base.test.ts +++ b/tests/integration/ssr/tests/base.test.ts @@ -17,6 +17,9 @@ async function basicUsage(page: Page, appPort: number) { waitUntil: ['networkidle0'], }); await (expect(page) as any).toMatchTextContent('user1-18'); + + const content = await page.content(); + await (expect(content) as any).toMatch('"headers":{"host":'); } async function errorThrown(page: Page, appPort: number) {