Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ssr types and refactor some logic #11784

Merged
merged 6 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions examples/ssr-demo/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {
IServerLoaderArgs,
Link,
useClientLoaderData,
useServerInsertedHTML,
Expand All @@ -18,7 +18,7 @@ import umiLogo from './umi.png';

export default function HomePage() {
const clientLoaderData = useClientLoaderData();
const serverLoaderData = useServerLoaderData();
const serverLoaderData = useServerLoaderData<typeof serverLoader>();

useServerInsertedHTML(() => {
return <div>inserted html</div>;
Expand Down Expand Up @@ -51,7 +51,8 @@ export async function clientLoader() {
return { message: 'data from client loader of index.tsx' };
}

export async function serverLoader() {
export async function serverLoader({ request }: IServerLoaderArgs) {
const { url } = request;
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
return { message: 'data from server loader of index.tsx' };
return { message: `data from server loader of index.tsx, url: ${url}` };
}
3 changes: 3 additions & 0 deletions examples/ssr-demo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./src/.umi/tsconfig.json"
}
17 changes: 15 additions & 2 deletions packages/preset-umi/src/features/ssr/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type {
Compiler,
} from '@umijs/bundler-webpack/compiled/webpack';
import { EnableBy } from '@umijs/core/dist/types';
import { fsExtra, importLazy, logger } from '@umijs/utils';
import { fsExtra, importLazy, logger, winPath } from '@umijs/utils';
import assert from 'assert';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { dirname, join } from 'path';
import type { IApi } from '../../types';
import { absServerBuildPath } from './utils';

Expand Down Expand Up @@ -61,6 +61,11 @@ export default (api: IApi) => {
},
]);

const serverPackagePath = dirname(
require.resolve('@umijs/server/package.json'),
);
const ssrTypesPath = join(serverPackagePath, './dist/types');

api.onGenerateFiles(() => {
// react-shim.js is for esbuild to build umi.server.js
api.writeTmpFile({
Expand Down Expand Up @@ -93,6 +98,14 @@ export function useServerInsertedHTML(callback: () => React.ReactNode): void {
addInsertedServerHTMLCallback(callback);
}
}
`,
});

// types
api.writeTmpFile({
path: 'types.d.ts',
content: `
export type { IServerLoaderArgs, UmiRequest } from '${winPath(ssrTypesPath)}'
`,
});
});
Expand Down
6 changes: 4 additions & 2 deletions packages/preset-umi/src/features/ssr/webpack/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as bundlerWebpack from '@umijs/bundler-webpack';
import type WebpackChain from '@umijs/bundler-webpack/compiled/webpack-5-chain';
import { Env } from '@umijs/bundler-webpack/dist/types';
import { lodash, logger } from '@umijs/utils';
import { dirname, resolve } from 'path';
import { IApi } from '../../../types';
import { absServerBuildPath } from '../utils';
import { Env } from "@umijs/bundler-webpack/dist/types";

export const build = async (api: IApi, opts: any) => {
logger.wait('[SSR] Compiling...');
Expand Down Expand Up @@ -47,7 +47,9 @@ export const build = async (api: IApi, opts: any) => {
memo.output
.path(dirname(absOutputFile))
// 避免多 chunk 时的命名冲突,虽然 ssr 在项目里禁用了 import() 语法,但 node_modules 下可能存在的 import() 没有被 babel 插件覆盖到
.filename(useHash ? '[name].[contenthash:8].server.js' : '[name].server.js')
.filename(
useHash ? '[name].[contenthash:8].server.js' : '[name].server.js',
)
.chunkFilename(
useHash ? '[name].[contenthash:8].server.js' : '[name].server.js',
)
Expand Down
7 changes: 5 additions & 2 deletions packages/renderer-react/src/appContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ export function useRouteProps<T extends Record<string, any> = any>() {
return props as T;
}

export function useServerLoaderData() {
type ServerLoaderFunc = (...args: any[]) => Promise<any> | any;
export function useServerLoaderData<T extends ServerLoaderFunc = any>() {
const route = useRouteData();
const appData = useAppData();
return { data: appData.serverLoaderData[route.route.id] };
return {
data: appData.serverLoaderData[route.route.id] as Awaited<ReturnType<T>>,
};
}

export function useClientLoaderData() {
Expand Down
9 changes: 7 additions & 2 deletions packages/renderer-react/src/browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,9 @@ const getBrowser = (
// use ?. since routes patched with patchClientRoutes is not exists in opts.routes
if (!isFirst && opts.routes[id]?.hasServerLoader) {
// 在有basename的情况下__serverLoader的请求路径需要加上basename
fetch((basename.endsWith('/') ? basename : basename + '/') + '__serverLoader?route=' + id, {
credentials: 'include'
const url = `${withEndSlash(basename)}'__serverLoader?route='${id}`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此合并冲突多余的引号在 #11726 删除。

fetch(url, {
credentials: 'include',
})
.then((d) => d.json())
.then((data) => {
Expand Down Expand Up @@ -350,3 +351,7 @@ export function renderClient(opts: RenderClientOpts) {
// @ts-ignore
ReactDOM.render(<Browser />, rootElement);
}

function withEndSlash(str: string) {
return str.endsWith('/') ? str : `${str}/`;
}
54 changes: 34 additions & 20 deletions packages/server/src/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@ import React, { ReactElement } from 'react';
import * as ReactDomServer from 'react-dom/server';
import { matchRoutes } from 'react-router-dom';
import { Writable } from 'stream';
import type { IRoutesById } from './types';
import type { IRoutesById, IServerLoaderArgs, UmiRequest } from './types';

interface RouteLoaders {
[key: string]: () => Promise<any>;
}

export type ServerInsertedHTMLHook = (callbacks: () => React.ReactNode) => void;

// serverLoader的参数类型
export interface IServerLoaderArgs {
request: Request;
interface CreateRequestServerlessOptions {
/**
* only return body html
* @example <div id="root">{app}</div> ...
*/
withoutHTML?: boolean;
/**
* folder path for `build-manifest.json`
*/
sourceDir?: string;
}

interface CreateRequestHandlerOptions {
interface CreateRequestHandlerOptions extends CreateRequestServerlessOptions {
routesWithServerLoader: RouteLoaders;
PluginManager: any;
manifest:
Expand All @@ -28,8 +35,6 @@ interface CreateRequestHandlerOptions {
createHistory: (opts: any) => any;
helmetContext?: any;
ServerInsertedHTMLContext: React.Context<ServerInsertedHTMLHook | null>;
withoutHTML?: boolean;
sourceDir?: string;
}

const createJSXProvider = (
Expand Down Expand Up @@ -214,21 +219,22 @@ export default function createRequestHandler(
return async function (req: any, res: any, next: any) {
// 切换路由场景下,会通过此 API 执行 server loader
if (req.url.startsWith('/__serverLoader') && req.query.route) {
const loaderArgs: IServerLoaderArgs = {
request: req,
};
const data = await executeLoader(
req.query.route,
opts.routesWithServerLoader,
{ request: req },
loaderArgs,
);
res.status(200).json(data);
return;
}

const request = new Request(
req.protocol + '://' + req.get('host') + req.originalUrl,
{
headers: req.headers,
},
);
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
const request = new Request(fullUrl, {
headers: req.headers,
});
const jsx = await jsxGeneratorDeferrer(req.url, { request });

if (!jsx) return next();
Expand Down Expand Up @@ -259,14 +265,21 @@ export default function createRequestHandler(

// 新增的给CDN worker用的SSR请求handle
export function createUmiHandler(opts: CreateRequestHandlerOptions) {
return async function (req: Request, params?: CreateRequestHandlerOptions) {
return async function (
req: UmiRequest,
params?: CreateRequestHandlerOptions,
) {
const jsxGeneratorDeferrer = createJSXGenerator({
...opts,
...params,
});
const jsx = await jsxGeneratorDeferrer(new URL(req.url).pathname, {
const loaderArgs: IServerLoaderArgs = {
request: req,
});
};
const jsx = await jsxGeneratorDeferrer(
new URL(req.url).pathname,
loaderArgs,
);

if (!jsx) {
throw new Error('no page resource');
Expand All @@ -277,12 +290,13 @@ export function createUmiHandler(opts: CreateRequestHandlerOptions) {
}

export function createUmiServerLoader(opts: CreateRequestHandlerOptions) {
return async function (req: Request) {
return async function (req: UmiRequest) {
const query = Object.fromEntries(new URL(req.url).searchParams);
// 切换路由场景下,会通过此 API 执行 server loader
return await executeLoader(query.route, opts.routesWithServerLoader, {
const loaderArgs: IServerLoaderArgs = {
request: req,
});
};
return executeLoader(query.route, opts.routesWithServerLoader, loaderArgs);
};
}

Expand Down
9 changes: 9 additions & 0 deletions packages/server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ export interface IRoutesById {
export interface IRouteCustom extends IRoute {
[key: string]: any;
}

export type UmiRequest = Partial<Request> & Pick<Request, 'url' | 'headers'>;

/**
* serverLoader 的参数类型
*/
export interface IServerLoaderArgs {
request: UmiRequest;
}
Loading