chore: merge master into feature

This commit is contained in:
栗嘉男 2024-12-25 10:24:00 +08:00
commit 1bb72293c4
19 changed files with 254 additions and 88 deletions

View File

@ -9,11 +9,10 @@ import {
} from '@ant-design/cssinjs';
import { HappyProvider } from '@ant-design/happy-work-theme';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App, unstableSetRender } from 'antd';
import { theme as antdTheme, App } from 'antd';
import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import { createRoot } from 'react-dom/client';
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
@ -23,6 +22,8 @@ import SiteThemeProvider from '../SiteThemeProvider';
import type { SiteContextProps } from '../slots/SiteContext';
import SiteContext from '../slots/SiteContext';
import '@ant-design/v5-patch-for-react-19';
const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch'));
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
@ -31,14 +32,6 @@ type SiteState = Partial<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768;
export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER';
unstableSetRender((node, container) => {
const root = createRoot(container);
root.render(node);
return async () => {
root.unmount();
};
});
// const styleCache = createCache();
// if (typeof global !== 'undefined') {
// (global as any).styleCache = styleCache;

View File

@ -2,7 +2,6 @@ import { createHash } from 'crypto';
import fs from 'fs';
import path from 'path';
import createEmotionServer from '@emotion/server/create-instance';
import chalk from 'chalk';
import type { IApi, IRoute } from 'dumi';
import ReactTechStack from 'dumi/dist/techStacks/react';
import sylvanas from 'sylvanas';
@ -126,7 +125,8 @@ class AntdReactTechStack extends ReactTechStack {
const resolve = (p: string): string => require.resolve(p);
const RoutesPlugin = (api: IApi) => {
const RoutesPlugin = async (api: IApi) => {
const chalk = await import('chalk').then((m) => m.default);
// const ssrCssFileName = `ssr-${Date.now()}.css`;
const writeCSSFile = (key: string, hashKey: string, cssString: string) => {

View File

@ -175,7 +175,7 @@ jobs:
CI: 1
- name: check build files
run: node ./tests/dekko/index.test.js
run: bun run test:dekko
# Artifact build files
- uses: actions/upload-artifact@v4

View File

@ -1,6 +1,9 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { render, unmount } from 'rc-util/lib/React/render';
import warning from '../_util/warning';
export type UnmountType = () => Promise<void>;
export type RenderType = (
node: React.ReactElement,
@ -8,6 +11,22 @@ export type RenderType = (
) => UnmountType;
const defaultReactRender: RenderType = (node, container) => {
// TODO: Remove in v6
// Warning for React 19
if (process.env.NODE_ENV !== 'production') {
const majorVersion = parseInt(React.version.split('.')[0], 10);
warning(
majorVersion < 19 ||
!!(
ReactDOM as typeof ReactDOM & {
createRoot: VoidFunction;
}
).createRoot,
'compatible',
'antd v5 support React is 16 ~ 18. see https://u.ant.design/v5-for-19 for compatible.',
);
}
render(node, container);
return () => {
return unmount(container);

View File

@ -0,0 +1,32 @@
import { version } from 'react';
import { waitFakeTimer19 } from '../../../tests/utils';
import Modal from '../../modal';
jest.mock('rc-util/lib/Dom/isVisible', () => () => true);
describe('UnstableContext', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
// TODO: Remove in v6
it('should warning', async () => {
const majorVersion = parseInt(version.split('.')[0], 10);
if (majorVersion >= 19) {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
Modal.info({ title: 'title', content: 'content' });
await waitFakeTimer19();
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: compatible] antd v5 support React is 16 ~ 18. see https://u.ant.design/v5-for-19 for compatible.',
);
}
});
});

View File

@ -27,16 +27,16 @@ export default function useWatermark(
removeWatermark: (container: HTMLElement) => void,
isWatermarkEle: (ele: Node) => boolean,
] {
const [watermarkMap] = React.useState(() => new Map<HTMLElement, HTMLDivElement>());
const watermarkMap = React.useRef(new Map<HTMLElement, HTMLDivElement>());
const appendWatermark = (base64Url: string, markWidth: number, container: HTMLElement) => {
if (container) {
if (!watermarkMap.get(container)) {
if (!watermarkMap.current.get(container)) {
const newWatermarkEle = document.createElement('div');
watermarkMap.set(container, newWatermarkEle);
watermarkMap.current.set(container, newWatermarkEle);
}
const watermarkEle = watermarkMap.get(container)!;
const watermarkEle = watermarkMap.current.get(container)!;
watermarkEle.setAttribute(
'style',
@ -55,20 +55,20 @@ export default function useWatermark(
}
}
return watermarkMap.get(container);
return watermarkMap.current.get(container);
};
const removeWatermark = (container: HTMLElement) => {
const watermarkEle = watermarkMap.get(container);
const watermarkEle = watermarkMap.current.get(container);
if (watermarkEle && container) {
container.removeChild(watermarkEle);
}
watermarkMap.delete(container);
watermarkMap.current.delete(container);
};
const isWatermarkEle = (ele: any) => Array.from(watermarkMap.values()).includes(ele);
const isWatermarkEle = (ele: any) => Array.from(watermarkMap.current.values()).includes(ele);
return [appendWatermark, removeWatermark, isWatermarkEle];
}

View File

@ -4,7 +4,6 @@ group:
order: 2
order: 0
title: 从 v4 到 v5
tag: Updated
---
本文档将帮助你从 antd `4.x` 版本升级到 antd `5.x` 版本,如果你是 `3.x` 或者更老的版本,请先参考之前的[升级文档](https://4x.ant.design/docs/react/migration-v4-cn)升级到 4.x。

View File

@ -0,0 +1,58 @@
---
group:
title: Advanced
order: 9
title: React 19 Compatibility
tag: New
---
<!-- prettier-ignore -->
:::info{title="Compatibility Interface"}
antd v5 compatibility with React 16 ~ 18 by default. For React 19, you can use the following compatibility methods to adapt.
:::
### React 19 Compatibility Issues
Since React 19 adjusted the export method of `react-dom`, antd cannot directly use the `ReactDOM.render` method. Therefore, using antd will encounter the following problems:
- Wave effect does not show
- Static methods of `Modal`, `Notification`, `Message` not working
Therefore, you need to use a compatibility configuration to make antd work properly in React 19.
### Compatibility Methods
You can choose one of the following methods, and it is recommended to use the compatibility package first.
#### Compatibility Package
Install the compatibility package
```bash
npm install --save-dev @ant-design/v5-patch-for-react-19
```
Import the compatibility package at the application entry
```tsx
import '@ant-design/v5-patch-for-react-19';
```
#### unstableSetRender
Once again, please use the compatibility package first. Only for special scenarios such as umd, micro-applications, etc., use the `unstableSetRender` method. `unstableSetRender` is a low-level registration method that allows developers to modify the rendering method of ReactDOM. Write the following code at the entry of your application:
```js
import { unstableSetRender } from 'antd';
import { createRoot } from 'react-dom/client';
unstableSetRender((node, container) => {
container._reactRoot ||= createRoot(container);
const root = container._reactRoot;
root.render(node);
return async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
root.unmount();
};
});
```

View File

@ -0,0 +1,58 @@
---
group:
title: 进阶使用
order: 9
title: React 19 兼容
tag: New
---
<!-- prettier-ignore -->
:::info{title="兼容接口"}
antd v5 默认兼容 React 16 ~ 18 版本,对于 React 19 版本,可以使用以下兼容方法进行适配。该兼容方式以及接口将在 v6 被移除。
:::
### React 19 兼容问题
由于 React 19 调整了 `react-dom` 的导出方式,导致 antd 无法直接使用 `ReactDOM.render` 方法。因而使用 antd 会遇到以下问题:
- 波纹特效无法正常工作
- `Modal`、`Notification`、`Message` 等组件的静态方法无效
因而需要通过兼容配置,使 antd 在 React 19 中正常工作。
### 兼容方式
以下方法任选其一,其中优先推荐使用兼容包。
#### 兼容包
安装兼容包
```bash
npm install --save-dev @ant-design/v5-patch-for-react-19
```
在应用入口处引入兼容包
```tsx
import '@ant-design/v5-patch-for-react-19';
```
#### unstableSetRender
再次提醒,默认情况下,请优先使用兼容包。除非对于 umd、微应用等特殊场景才使用 `unstableSetRender` 方法。`unstableSetRender` 为底层注册方法,允许开发者修改 ReactDOM 的渲染方法。在你的应用入口处写入:
```js
import { unstableSetRender } from 'antd';
import { createRoot } from 'react-dom/client';
unstableSetRender((node, container) => {
container._reactRoot ||= createRoot(container);
const root = container._reactRoot;
root.render(node);
return async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
root.unmount();
};
});
```

View File

@ -83,7 +83,7 @@
"pretest": "npm run version",
"test": "jest --config .jest.js --no-cache",
"test:all": "sh -e ./scripts/test-all.sh",
"test:dekko": "node ./tests/dekko/index.test.js",
"test:dekko": "tsx ./tests/dekko/index.test.ts",
"test:image": "jest --config .jest.image.js --no-cache -i -u --forceExit",
"test:node": "npm run version && jest --config .jest.node.js --no-cache",
"test:package-diff": "antd-tools run package-diff",
@ -151,7 +151,7 @@
"rc-tree": "~5.10.1",
"rc-tree-select": "~5.24.5",
"rc-upload": "~4.8.1",
"rc-util": "^5.44.2",
"rc-util": "^5.44.3",
"scroll-into-view-if-needed": "^3.1.0",
"throttle-debounce": "^5.0.2"
},
@ -159,6 +159,7 @@
"@ant-design/compatible": "^5.1.3",
"@ant-design/happy-work-theme": "^1.0.0",
"@ant-design/tools": "^18.0.2",
"@ant-design/v5-patch-for-react-19": "^1.0.2",
"@antfu/eslint-config": "^3.11.2",
"@antv/g6": "^4.8.24",
"@biomejs/biome": "^1.9.4",
@ -223,7 +224,7 @@
"antd-style": "^3.7.1",
"antd-token-previewer": "^2.0.8",
"axios": "^1.7.7",
"chalk": "^4.1.2",
"chalk": "^5.0.0",
"cheerio": "^1.0.0",
"circular-dependency-plugin": "^5.2.2",
"cli-progress": "^3.12.0",
@ -237,7 +238,7 @@
"eslint-plugin-compat": "^6.0.1",
"eslint-plugin-jest": "^28.9.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-react-hooks": "^5.2.0-canary-6907aa2a-20241220",
"eslint-plugin-react-refresh": "^0.4.14",
"fast-glob": "^3.3.2",
"fetch-jsonp": "^1.3.0",
@ -315,12 +316,12 @@
"tar": "^7.4.3",
"tar-fs": "^3.0.6",
"terser": "^5.36.0",
"tsx": "4.11.2",
"typedoc": "~0.26.11",
"tsx": "^4.19.2",
"typedoc": "^0.27.0",
"typescript": "~5.7.2",
"vanilla-jsoneditor": "^2.3.1",
"vanilla-tilt": "^1.8.1",
"webpack": "^5.96.1",
"webpack": "^5.97.1",
"webpack-bundle-analyzer": "^4.10.2",
"xhr-mock": "^2.5.1"
},

View File

@ -58,11 +58,11 @@ fi
if ! has_arg '--skip-dekko' "$@"; then
echo "[TEST ALL] dekko dist"
echo "[TEST ALL] dekko dist" > ~test-all.txt
node ./tests/dekko/dist.test.js
tsx ./tests/dekko/dist.test.ts
echo "[TEST ALL] dekko lib and es"
echo "[TEST ALL] dekko lib and es" > ~test-all.txt
node ./tests/dekko/lib-es.test.js
tsx ./tests/dekko/lib-es.test.ts
else
echo "[TEST ALL] dekko test...skip"
fi

View File

@ -1,5 +1,5 @@
const $ = require('dekko');
const chalk = require('chalk');
import $ from 'dekko';
import chalk from 'chalk';
$('dist')
.isDirectory()

View File

@ -1,3 +0,0 @@
require('./dist.test');
require('./lib-es.test');
require('./use-client.test');

View File

@ -0,0 +1,3 @@
import './dist.test';
import './lib-es.test';
import './use-client.test';

View File

@ -1,24 +0,0 @@
const $ = require('dekko');
const chalk = require('chalk');
$('lib').isDirectory().hasFile('index.js').hasFile('index.d.ts');
$('lib/*')
.filter((filename) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)))
.isDirectory()
.filter((filename) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext)))
.hasFile('index.js')
.hasFile('index.d.ts');
console.log(chalk.green('✨ `lib` directory is valid.'));
$('es').isDirectory().hasFile('index.js').hasFile('index.d.ts');
$('es/*')
.filter((filename) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)))
.isDirectory()
.filter((filename) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext)))
.hasFile('index.js')
.hasFile('index.d.ts');
console.log(chalk.green('✨ `es` directory is valid.'));

View File

@ -0,0 +1,28 @@
import $ from 'dekko';
import chalk from 'chalk';
$('lib').isDirectory().hasFile('index.js').hasFile('index.d.ts');
$('lib/*')
.filter(
(filename: string) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)),
)
.isDirectory()
.filter((filename: string) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext)))
.hasFile('index.js')
.hasFile('index.d.ts');
console.log(chalk.green('✨ `lib` directory is valid.'));
$('es').isDirectory().hasFile('index.js').hasFile('index.d.ts');
$('es/*')
.filter(
(filename: string) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)),
)
.isDirectory()
.filter((filename: string) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext)))
.hasFile('index.js')
.hasFile('index.d.ts');
console.log(chalk.green('✨ `es` directory is valid.'));

View File

@ -1,28 +0,0 @@
const $ = require('dekko');
const chalk = require('chalk');
const fs = require('fs');
const includeUseClient = (filename) =>
fs.readFileSync(filename).toString().includes('"use client"');
$('dist/*')
.isFile()
.assert("doesn't contain use client", (filename) => !includeUseClient(filename));
$('{es,lib}/index.js')
.isFile()
.assert('contain use client', (filename) => includeUseClient(filename));
$('{es,lib}/*/index.js')
.isFile()
.assert('contain use client', (filename) => includeUseClient(filename));
// check tsx files
$('{es,lib}/typography/*.js')
.isFile()
.assert('contain use client', (filename) => includeUseClient(filename));
$('{es,lib}/typography/Base/*.js')
.isFile()
.filter((filename) => !filename.endsWith('/util.js'))
.assert('contain use client', (filename) => includeUseClient(filename));
console.log(chalk.green('✨ use client passed!'));

View File

@ -0,0 +1,28 @@
import $ from 'dekko';
import chalk from 'chalk';
import fs from 'node:fs';
const includeUseClient = (filename: string) =>
fs.readFileSync(filename).toString().includes('"use client"');
$('dist/*')
.isFile()
.assert("doesn't contain use client", (filename: string) => !includeUseClient(filename));
$('{es,lib}/index.js')
.isFile()
.assert('contain use client', (filename: string) => includeUseClient(filename));
$('{es,lib}/*/index.js')
.isFile()
.assert('contain use client', (filename: string) => includeUseClient(filename));
// check tsx files
$('{es,lib}/typography/*.js')
.isFile()
.assert('contain use client', (filename: string) => includeUseClient(filename));
$('{es,lib}/typography/Base/*.js')
.isFile()
.filter((filename: string) => !filename.endsWith('/util.js'))
.assert('contain use client', (filename: string) => includeUseClient(filename));
console.log(chalk.green('✨ use client passed!'));

View File

@ -27,3 +27,5 @@ declare module '@npmcli/run-script' {
}
declare module '@microflash/rehype-figure';
declare module 'dekko';