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: new ssr #12489

Merged
merged 36 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a1e5cee
feat(preset-umi): unify request handler for ssr and always use stream…
PeachScript Mar 28, 2024
9d2e626
Merge remote-tracking branch 'origin/master' into feature/new-unio-ssr
PeachScript Apr 1, 2024
ae9dc25
feat: SSR support useServerInsertedHTML (#12247)
MadCcc Apr 1, 2024
c9530cf
fix: metadata and hydrate root mismatched between csr and ssr (#12220)
Jinbao1001 Apr 1, 2024
67f7e90
fix: hydrate logic for ssr (#12255)
Jinbao1001 Apr 2, 2024
a7dd292
release: 4.0.0-canary.20240402.1
PeachScript Apr 2, 2024
7ccf522
fix: wrong react-dom server api for worker ssr mode (#12263)
PeachScript Apr 11, 2024
09ec592
feat: align compile time and runtime plugin api between csr and ssr (…
Jinbao1001 Apr 19, 2024
e8735e4
feat: qiankun plugin compatible with ssr runtime (#12295)
bravepg Apr 19, 2024
e0972d3
feat: use prerender html directly in ssg (#12317)
MadCcc Apr 22, 2024
a82a386
chore: 优先从环境变量读取 manifest 路径 (#12354)
Jinbao1001 May 6, 2024
fa7d9fa
fix: ssr manifest 正确读取环境变量 (#12357)
Jinbao1001 May 7, 2024
eb9918e
refactor: improve platform checking logic for qiankun slave (#12331)
bravepg May 7, 2024
b757a04
chore: use process.env.SSR_RESOURCE_DIR replace SSR_RESOURCE_DIR (#12…
Jinbao1001 May 9, 2024
ec2be86
feat: provide useLoaderData for fallback serverLoader (#12339)
Jinbao1001 May 9, 2024
2076690
chore: renderFromRoot to __SPECIAL_HTML_DO_NOT_USE_OR_YOU_WILL_BE_FIR…
Jinbao1001 May 13, 2024
0b8b4f4
refactor: add renderFromRoot for tern theme (#12385)
Jinbao1001 May 13, 2024
1f84be6
feat: mako for ssr (#12409)
Jinbao1001 May 22, 2024
db15285
feat: add mako hooks (#12412)
Jinbao1001 May 22, 2024
96e855c
refactor(preset-umi): handle illegal route absPath in route preload (…
PeachScript May 27, 2024
2409a4c
fix: renderClient opts miss internal vars (#12419)
Jinbao1001 May 27, 2024
cc2eb49
release: 4.2.6-alpha.1
Jinbao1001 May 27, 2024
1987617
release: 4.2.6-alpha.2
Jinbao1001 May 27, 2024
0b3c81d
release: 4.2.6-alpha.3
Jinbao1001 May 27, 2024
4c6d6a6
release: 4.2.6-alpha.4
Jinbao1001 May 27, 2024
ca9fabb
Merge remote-tracking branch 'origin/master' into feature/new-unio-ssr
Jinbao1001 May 30, 2024
19b732f
feat: mako build and ssr finished
Jinbao1001 Jun 25, 2024
ac64d1c
chore: merge
Jinbao1001 Jun 25, 2024
2d844a4
chore: delete code
Jinbao1001 Jun 25, 2024
7d0f2f8
chore: update lock
Jinbao1001 Jun 25, 2024
4bb77aa
chore: update lock
Jinbao1001 Jun 25, 2024
34d6d45
chore: update lock
Jinbao1001 Jun 25, 2024
a113db8
chore: update lock
Jinbao1001 Jun 25, 2024
da9315b
chore: change plugins to makoPlugins
Jinbao1001 Jun 27, 2024
63e13e9
chore(deps): update mako version
Jinbao1001 Jun 27, 2024
9f483d8
chore: merge
Jinbao1001 Jun 27, 2024
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
4 changes: 2 additions & 2 deletions examples/max/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"@umijs/max": "workspace:*",
"antd": "^4.23.2",
"dayjs": "^1.11.7",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"cross-env": "^7.0.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/mf-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
},
"dependencies": {
"@umijs/max": "workspace:*",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"cross-env": "^7.0.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/mf-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
},
"dependencies": {
"@umijs/max": "workspace:*",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"cross-env": "^7.0.3",
Expand Down
4 changes: 2 additions & 2 deletions examples/qiankun-master/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"dependencies": {
"@umijs/max": "workspace:*",
"qiankun": "^2.10.1",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"cross-env": "^7.0.3"
Expand Down
4 changes: 2 additions & 2 deletions examples/qiankun-slave-app2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@umijs/max": "workspace:*",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
}
}
4 changes: 2 additions & 2 deletions examples/qiankun-slave/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
},
"dependencies": {
"@umijs/max": "workspace:*",
"react": "18.1.0",
"react-dom": "18.1.0"
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"cross-env": "^7.0.3",
Expand Down
19 changes: 18 additions & 1 deletion examples/ssr-demo/.umirc.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
export default {
svgr: {},
hash: true,
mfsu: false,
routePrefetch: {},
manifest: {},
clientLoader: {},
mako: {
plugins: [
{
load: () => {},
},
],
},
ssr: {
serverBuildPath: './umi.server.js',
builder: 'mako',
},
exportStatic: {},
styles: [`body { color: red; }`],

metas: [
{
name: 'test',
content: 'content',
},
],
};
6 changes: 6 additions & 0 deletions examples/ssr-demo/mako.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"optimization": {
"skipModules": true,
"concatenateModules": false
}
}
2 changes: 2 additions & 0 deletions examples/ssr-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"start:prod": "node ./production-server.js"
},
"dependencies": {
"@ant-design/cssinjs": "^1.18.5",
"antd": "^5",
"express": "4.18.2",
"umi": "workspace:*"
}
Expand Down
23 changes: 23 additions & 0 deletions examples/ssr-demo/src/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import { useState } from 'react';
import { Outlet, useServerInsertedHTML } from 'umi';

export default function Layout() {
const [cssCache] = useState(() => createCache());

useServerInsertedHTML(() => {
const style = extractStyle(cssCache, { plain: true });
return (
<style
id="antd-cssinjs"
dangerouslySetInnerHTML={{ __html: style }}
></style>
);
});

return (
<StyleProvider cache={cssCache}>
<Outlet />
</StyleProvider>
);
}
Comment on lines +1 to +23
Copy link

Choose a reason for hiding this comment

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

避免使用 dangerouslySetInnerHTML

代码中使用了 dangerouslySetInnerHTML 来设置内联样式,这可能会导致跨站脚本攻击(XSS)。建议寻找其他方法来设置样式,以提高应用的安全性。

- <style id="antd-cssinjs" dangerouslySetInnerHTML={{ __html: style }}></style>
+ <style id="antd-cssinjs">{style}</style>
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import { useState } from 'react';
import { Outlet, useServerInsertedHTML } from 'umi';
export default function Layout() {
const [cssCache] = useState(() => createCache());
useServerInsertedHTML(() => {
const style = extractStyle(cssCache, { plain: true });
return (
<style
id="antd-cssinjs"
dangerouslySetInnerHTML={{ __html: style }}
></style>
);
});
return (
<StyleProvider cache={cssCache}>
<Outlet />
</StyleProvider>
);
}
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import { useState } from 'react';
import { Outlet, useServerInsertedHTML } from 'umi';
export default function Layout() {
const [cssCache] = useState(() => createCache());
useServerInsertedHTML(() => {
const style = extractStyle(cssCache, { plain: true });
return (
<style id="antd-cssinjs">{style}</style>
);
});
return (
<StyleProvider cache={cssCache}>
<Outlet />
</StyleProvider>
);
}
Tools
Biome

[error] 13-13: Avoid passing content using the dangerouslySetInnerHTML prop. (lint/security/noDangerouslySetInnerHtml)

Setting content using code can expose users to cross-site scripting (XSS) attacks

29 changes: 23 additions & 6 deletions examples/ssr-demo/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Input } from 'antd';
import { useId } from 'react';
import {
ClientLoader,
Link,
MetadataLoader,
ServerLoader,
useClientLoaderData,
useLoaderData,
useServerInsertedHTML,
useServerLoaderData,
} from 'umi';
Expand All @@ -20,18 +24,29 @@ import umiLogo from './umi.png';
export default function HomePage() {
const clientLoaderData = useClientLoaderData();
const serverLoaderData = useServerLoaderData<typeof serverLoader>();
const loaderData = useLoaderData<typeof serverLoader>();

useServerInsertedHTML(() => {
return <div>inserted html</div>;
return (
<style
dangerouslySetInnerHTML={{
__html: `.server_inserted_style { color: #1677ff }`,
}}
Comment on lines +32 to +34
Copy link

Choose a reason for hiding this comment

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

避免使用 dangerouslySetInnerHTML

直接在组件内部使用 dangerouslySetInnerHTML 可能会增加跨站脚本攻击(XSS)的风险。建议使用更安全的方法来插入或更新页面内容。

Tools
Biome

[error] 32-32: Avoid passing content using the dangerouslySetInnerHTML prop. (lint/security/noDangerouslySetInnerHtml)

Setting content using code can expose users to cross-site scripting (XSS) attacks

></style>
);
});

const id = useId();

return (
<div>
<h1 className="title">Hello~</h1>
<p className="server_inserted_style">id: {id}</p>
<p className={styles.blue}>This is index.tsx</p>
<p className={cssStyle.title}>I should be pink</p>
<p className={cssStyle.blue}>I should be cyan</p>
<Button />
<Input placeholder="这个样式不应该闪烁" />
<img src={bigImage} alt="" />
<img src={umiLogo} alt="umi" />
<Link to="/users/user">/users/user</Link>
Expand All @@ -43,19 +58,21 @@ export default function HomePage() {
</div>
<p>client loader data: {JSON.stringify(clientLoaderData)}</p>
<p>server loader data: {JSON.stringify(serverLoaderData)}</p>
<p>merge loader data: {JSON.stringify(loaderData)}</p>
</div>
);
}

export async function clientLoader() {
export const clientLoader: ClientLoader = async ({}) => {
Copy link

Choose a reason for hiding this comment

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

修正空对象模式

clientLoader 函数中,空对象模式 ({}) 并没有实际的用途,应该移除或替换为具体的参数。

- export const clientLoader: ClientLoader = async ({}) => {
+ export const clientLoader: ClientLoader = async () => {
  await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
  return { clientMessage: 'data from client loader of index.tsx' };
};
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const clientLoader: ClientLoader = async ({}) => {
export const clientLoader: ClientLoader = async () => {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
return { clientMessage: 'data from client loader of index.tsx' };
};

await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
return { message: 'data from client loader of index.tsx' };
}
return { clientMessage: 'data from client loader of index.tsx' };
};
clientLoader.hydrate = true;

export const serverLoader: ServerLoader = async (req) => {
const url = req!.request.url;
const url = req?.request?.url;
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
return { message: `data from server loader of index.tsx, url: ${url}` };
return { serverMessage: `data from server loader of index.tsx, url: ${url}` };
};

// SEO-设置页面的TDK
Expand Down
Empty file added index.js
Empty file.
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/qs": "^6.9.7",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/resolve": "^1.20.2",
"@types/rimraf": "3.0.2",
"@types/tunnel": "^0.0.3",
Expand Down Expand Up @@ -101,8 +101,8 @@
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-packagejson": "^2.4.3",
"qs": "^6.11.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-text-loop-next": "0.0.3",
"regenerator-runtime": "^0.13.11",
"resolve": "^1.22.0",
Expand Down Expand Up @@ -142,8 +142,8 @@
]
},
"overrides": {
"browserslist": "$browserslist",
"@parcel/watcher": "2.1.0"
"@parcel/watcher": "2.1.0",
"browserslist": "$browserslist"
}
},
"_local": "This flag is used to check if the development in local, Please do not delete."
Expand Down
2 changes: 2 additions & 0 deletions packages/bundler-webpack/src/config/definePlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ test('normal', () => {
NODE_ENV: '"test"',
PUBLIC_PATH: '"/"',
},
'process.env.SSR_MANIFEST': 'process.env.SSR_MANIFEST',
});
});

Expand All @@ -31,6 +32,7 @@ test('env variables', () => {
UMI_APP_FOO: '"BAR"',
PUBLIC_PATH: '"/"',
},
'process.env.SSR_MANIFEST': 'process.env.SSR_MANIFEST',
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/bundler-webpack/src/config/definePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function resolveDefine(opts: IOpts) {

return {
'process.env': env,
'process.env.SSR_MANIFEST': 'process.env.SSR_MANIFEST',
Copy link
Member

Choose a reason for hiding this comment

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

忘了当时的情况了,这个能实现到 ssr plugin 里吗

...define,
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/mfsu/src/mfsu/strategyStaticAnalyze.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { logger, printHelp, winPath } from '@umijs/utils';
import type { Configuration } from 'webpack';
import { checkMatch } from '../babelPlugins/awaitImport/checkMatch';
import mfImport from '../babelPlugins/awaitImport/MFImport';
import { StaticDepInfo } from '../staticDepInfo/staticDepInfo';
import { extractBabelPluginImportOptions } from '../utils/webpackUtils';
import { IBuildDepPluginOpts } from '../webpackPlugins/buildDepPlugin';
import type { IMFSUStrategy, MFSU } from './mfsu';
import type { Configuration } from 'webpack';
import { extractBabelPluginImportOptions } from '../utils/webpackUtils';

export class StaticAnalyzeStrategy implements IMFSUStrategy {
private readonly mfsu: MFSU;
Expand Down
8 changes: 8 additions & 0 deletions packages/plugins/libs/qiankun/master/masterRuntimePlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ function patchMicroAppRouteComponent(routes: any[]) {
}

export async function render(oldRender: typeof noop) {
// 在 ssr 的场景下,直接返回旧的 render
if (typeof window === 'undefined') {
return oldRender();
}
const runtimeOptions = await getMasterRuntime();
let masterOptions: MasterOptions = {
...getMasterOptions(),
Expand Down Expand Up @@ -138,6 +142,10 @@ export async function render(oldRender: typeof noop) {
}

export function patchClientRoutes({ routes }: { routes: any[] }) {
// 在 ssr 的场景下,不执行主应用的 patchClientRoutes
if (typeof window === 'undefined') {
return;
}
const microAppRoutes = [].concat(
deepFilterLeafRoutes(routes),
deepFilterLeafRoutes(microAppRuntimeRoutes),
Expand Down
4 changes: 4 additions & 0 deletions packages/plugins/libs/qiankun/slave/slaveRuntimePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { createHistory } from '@@/core/history';
import qiankunRender, { contextOptsStack } from './lifecycles';

export function render(oldRender: any) {
// 在 ssr 的场景下,直接返回旧的 render
if (typeof window === 'undefined') {
return oldRender();
}
return qiankunRender().then(oldRender);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/src/initial-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function InitialStateProvider(props: any) {
appLoaded.current = true;
}
}, [loading]);
if (loading && !appLoaded.current) {
if (loading && !appLoaded.current && typeof window !== 'undefined') {
return <Loading />;
}
return props.children;
Expand Down
14 changes: 14 additions & 0 deletions packages/plugins/src/qiankun/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,18 @@ export { MicroAppWithMemoHistory } from './MicroAppWithMemoHistory';
`,
});
});

api.chainWebpack((config, { ssr }) => {
// 在 ssr 的场景下,把 qiankun external 到一个任意模块
// 这样就不会把 qiankun 的依赖构建进产物中
Copy link
Member

Choose a reason for hiding this comment

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

这里感觉还是得有个本质解,现在的方式只是绕过,注释里至少要包含两个问题:

  1. 为什么不能引入 qiankun 的依赖
  2. 为什么 ssr 没用到 qiankun 却又会把 qiankun 打进来

以及本质解应该是满足某个前提 ssr 就不受影响,也不用 externals 来绕,可以留成 TODO

if (ssr) {
const originalExternals = config.get('externals');
config.externals({
...originalExternals,
qiankun: 'fs',
});
}

return config;
});
};
19 changes: 13 additions & 6 deletions packages/plugins/src/qiankun/slave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ export interface IRuntimeConfig {
];
});

api.chainWebpack((config) => {
api.chainWebpack((config, { ssr }) => {
// ssr 场景下,通过 cjs 的方式来使用模块,跳过 umd方式的构建
if (ssr) {
return;
}
assert(api.pkg.name, 'You should have name in package.json.');
// 默认不修改 library chunk 的 name,从而确保可以通过 window[appName] 访问到导出
// mfsu 关闭的时候才可以修改,否则可能导致配合 mfsu 时,子应用的 umd chunk 无法被正确加载
Expand Down Expand Up @@ -223,11 +227,14 @@ export interface IRuntimeConfig {

api.addEntryCode(() => [
`
export const bootstrap = qiankun_genBootstrap(render);
export const mount = qiankun_genMount('${api.config.mountElementId}');
export const unmount = qiankun_genUnmount('${api.config.mountElementId}');
export const update = qiankun_genUpdate();
if (!window.__POWERED_BY_QIANKUN__) {
const qiankun_noop = () => new Error('qiankun lifecycle is not available for server runtime!');
const isServer = typeof window === 'undefined';
export const bootstrap = isServer ? qiankun_noop: qiankun_genBootstrap(render);
export const mount = isServer ? qiankun_noop : qiankun_genMount('${api.config.mountElementId}');
export const unmount = isServer ? qiankun_noop : qiankun_genUnmount('${api.config.mountElementId}');
export const update = isServer ? qiankun_noop : qiankun_genUpdate();
// 增加 ssr 的判断
if (!isServer && !window.__POWERED_BY_QIANKUN__) {
bootstrap().then(mount);
}
`,
Expand Down
Loading