Skip to content

Commit

Permalink
feat: add ssr types and refactor some logic (#11784)
Browse files Browse the repository at this point in the history
* refactor: PR #11755 comment

* feat: export serverLoader types

* example: serverLoader case

* feat: add `useServerLoaderData` type

* refactor: PR #11724 comment
  • Loading branch information
fz6m authored Nov 6, 2023
1 parent af457bc commit 48b5d3c
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 32 deletions.
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}`;
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;
}

0 comments on commit 48b5d3c

Please sign in to comment.