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

perf: refactor devWarning for production code size #35411

Merged
merged 14 commits into from May 10, 2022
65 changes: 65 additions & 0 deletions components/_util/__tests__/warning.test.js
@@ -0,0 +1,65 @@
describe('Test warning', () => {
let spy: jest.SpyInstance;

beforeAll(() => {
spy = jest.spyOn(console, 'error');
});

afterAll(() => {
spy.mockRestore();
});

beforeEach(() => {
jest.resetModules();
});

afterEach(() => {
spy.mockReset();
});

it('Test noop', async () => {
const { noop } = await import('../warning');
const value = noop();

expect(value).toBe(undefined);
expect(spy).not.toHaveBeenCalled();
expect(() => {
noop();
}).not.toThrow();
});

describe('process.env.NODE_ENV !== "production"', () => {
it('If `false`, exec `console.error`', async () => {
const warning = (await import('../warning')).default;
warning(false, 'error');

expect(spy).toHaveBeenCalled();
});

it('If `true`, do not exec `console.error`', async () => {
const warning = (await import('../warning')).default;
warning(true, 'error message');

expect(spy).not.toHaveBeenCalled();
});
});

describe('process.env.NODE_ENV === "production"', () => {
it('Whether `true` or `false`, do not exec `console.error`', async () => {
const prevEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';

const { default: warning, noop } = await import('../warning');

expect(warning).toEqual(noop);

warning(false, 'error message');
expect(spy).not.toHaveBeenCalled();

warning(true, 'error message');
expect(spy).not.toHaveBeenCalled();

process.env.NODE_ENV = prevEnv;
});
});
});
12 changes: 0 additions & 12 deletions components/_util/devWarning.ts

This file was deleted.

21 changes: 21 additions & 0 deletions components/_util/warning.ts
@@ -0,0 +1,21 @@
import rcWarning, { resetWarned } from 'rc-util/lib/warning';

export { resetWarned };
export function noop() {}

type Warning = (valid: boolean, component: string, message: string) => void;

// eslint-disable-next-line import/no-mutable-exports
let warning: Warning = noop;
if (process.env.NODE_ENV !== 'production') {
warning = (valid, component, message) => {
rcWarning(valid, `[antd: ${component}] ${message}`);

// StrictMode will inject console which will not throw warning in React 17.
if (process.env.NODE_ENV === 'test') {
resetWarned();
}
};
}

export default warning;
19 changes: 9 additions & 10 deletions components/auto-complete/__tests__/index.test.js
Expand Up @@ -42,16 +42,15 @@ describe('AutoComplete', () => {
});

it('AutoComplete throws error when contains invalid dataSource', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
expect(() => {
mount(
<AutoComplete dataSource={[() => {}]}>
<textarea />
</AutoComplete>,
);
}).toThrow();
// eslint-disable-next-line no-console
console.error.mockRestore();
const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined);

mount(
<AutoComplete dataSource={[() => {}]}>
<textarea />
</AutoComplete>,
);

expect(spy).toHaveBeenCalled();
});

it('legacy dataSource should accept react element option', () => {
Expand Down
34 changes: 18 additions & 16 deletions components/auto-complete/index.tsx
Expand Up @@ -20,7 +20,7 @@ import type {
import Select from '../select';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigConsumer } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';
import { isValidElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';

Expand Down Expand Up @@ -95,26 +95,28 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
);
}
default:
throw new Error('AutoComplete[dataSource] only supports type `string[] | Object[]`.');
warning(
false,
'AutoComplete',
'`dataSource` is only supports type `string[] | Object[]`.',
);
return undefined;
}
})
: [];
}

// ============================ Warning ============================
React.useEffect(() => {
devWarning(
!('dataSource' in props),
'AutoComplete',
'`dataSource` is deprecated, please use `options` instead.',
);

devWarning(
!customizeInput || !('size' in props),
'AutoComplete',
'You need to control style self instead of setting `size` when using customize input.',
);
}, []);
warning(
!('dataSource' in props),
'AutoComplete',
'`dataSource` is deprecated, please use `options` instead.',
);

warning(
!customizeInput || !('size' in props),
'AutoComplete',
'You need to control style self instead of setting `size` when using customize input.',
);

return (
<ConfigConsumer>
Expand Down
4 changes: 2 additions & 2 deletions components/avatar/avatar.tsx
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames';
import ResizeObserver from 'rc-resize-observer';
import { composeRef } from 'rc-util/lib/ref';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';
import type { Breakpoint } from '../_util/responsiveObserve';
import { responsiveArray } from '../_util/responsiveObserve';
import useBreakpoint from '../grid/hooks/useBreakpoint';
Expand Down Expand Up @@ -126,7 +126,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
: {};
}, [screens, size]);

devWarning(
warning(
!(typeof icon === 'string' && icon.length > 2),
'Avatar',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
Expand Down
4 changes: 2 additions & 2 deletions components/breadcrumb/Breadcrumb.tsx
Expand Up @@ -5,7 +5,7 @@ import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
import Menu from '../menu';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';
import { cloneElement } from '../_util/reactNode';

export interface Route {
Expand Down Expand Up @@ -119,7 +119,7 @@ const Breadcrumb: BreadcrumbInterface = ({
return element;
}

devWarning(
warning(
element.type &&
(element.type.__ANT_BREADCRUMB_ITEM === true ||
element.type.__ANT_BREADCRUMB_SEPARATOR === true),
Expand Down
4 changes: 2 additions & 2 deletions components/button/button-group.tsx
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import classNames from 'classnames';
import type { SizeType } from '../config-provider/SizeContext';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';

export interface ButtonGroupProps {
size?: SizeType;
Expand Down Expand Up @@ -34,7 +34,7 @@ const ButtonGroup: React.FC<ButtonGroupProps> = props => {
case undefined:
break;
default:
devWarning(!size, 'Button.Group', 'Invalid prop `size`.');
warning(!size, 'Button.Group', 'Invalid prop `size`.');
}

const classes = classNames(
Expand Down
6 changes: 3 additions & 3 deletions components/button/button.tsx
Expand Up @@ -7,7 +7,7 @@ import Group, { GroupSizeContext } from './button-group';
import { ConfigContext } from '../config-provider';
import Wave from '../_util/wave';
import { tuple } from '../_util/type';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import LoadingIcon from './LoadingIcon';
Expand Down Expand Up @@ -219,13 +219,13 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
(onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)?.(e);
};

devWarning(
warning(
!(typeof icon === 'string' && icon.length > 2),
'Button',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
);

devWarning(
warning(
!(ghost && isUnBorderedButtonType(type)),
'Button',
"`link` or `text` button can't be a `ghost` button.",
Expand Down
20 changes: 12 additions & 8 deletions components/cascader/index.tsx
Expand Up @@ -14,7 +14,7 @@ import RightOutlined from '@ant-design/icons/RightOutlined';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import { useContext } from 'react';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
Expand Down Expand Up @@ -159,13 +159,17 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
const mergedStatus = getMergedStatus(contextStatus, customStatus);

// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
devWarning(
popupClassName === undefined,
'Cascader',
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);
}
warning(
popupClassName === undefined,
'Cascader',
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);

warning(
!multiple || !props.displayRender,
'Cascader',
'`displayRender` not work on `multiple`. Please use `tagRender` instead.',
);

// =================== No Found ====================
const mergedNotFoundContent = notFoundContent || renderEmpty('Cascader');
Expand Down
4 changes: 2 additions & 2 deletions components/checkbox/Checkbox.tsx
Expand Up @@ -5,7 +5,7 @@ import { useContext } from 'react';
import { FormItemInputContext } from '../form/context';
import { GroupContext } from './Group';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';

export interface AbstractCheckboxProps<T> {
prefixCls?: string;
Expand Down Expand Up @@ -67,7 +67,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo

React.useEffect(() => {
checkboxGroup?.registerValue(restProps.value);
devWarning(
warning(
'checked' in restProps || !!checkboxGroup || !('value' in restProps),
'Checkbox',
'`value` is not a valid prop, do you mean `checked`?',
Expand Down
2 changes: 1 addition & 1 deletion components/checkbox/__tests__/checkbox.test.js
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render, fireEvent } from '../../../tests/utils';
import Checkbox from '..';
import focusTest from '../../../tests/shared/focusTest';
import { resetWarned } from '../../_util/devWarning';
import { resetWarned } from '../../_util/warning';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';

Expand Down
4 changes: 2 additions & 2 deletions components/collapse/CollapsePanel.tsx
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import RcCollapse from 'rc-collapse';
import classNames from 'classnames';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';

export type CollapsibleType = 'header' | 'disabled';

Expand All @@ -23,7 +23,7 @@ export interface CollapsePanelProps {
}

const CollapsePanel: React.FC<CollapsePanelProps> = props => {
devWarning(
warning(
!('disabled' in props),
'Collapse.Panel',
'`disabled` is deprecated. Please use `collapsible="disabled"` instead.',
Expand Down
2 changes: 1 addition & 1 deletion components/collapse/__tests__/index.test.js
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { sleep } from '../../../tests/utils';
import { resetWarned } from '../../_util/devWarning';
import { resetWarned } from '../../_util/warning';

describe('Collapse', () => {
// eslint-disable-next-line global-require
Expand Down
2 changes: 1 addition & 1 deletion components/config-provider/__tests__/theme.test.ts
@@ -1,7 +1,7 @@
import { kebabCase } from 'lodash';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import ConfigProvider from '..';
import { resetWarned } from '../../_util/devWarning';
import { resetWarned } from '../../_util/warning';

let mockCanUseDom = true;

Expand Down
4 changes: 2 additions & 2 deletions components/config-provider/cssVariables.tsx
Expand Up @@ -5,7 +5,7 @@ import canUseDom from 'rc-util/lib/Dom/canUseDom';
import { TinyColor } from '@ctrl/tinycolor';
import { generate } from '@ant-design/colors';
import type { Theme } from './context';
import devWarning from '../_util/devWarning';
import warning from '../_util/warning';

const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`;

Expand Down Expand Up @@ -101,6 +101,6 @@ export function registerTheme(globalPrefixCls: string, theme: Theme) {
if (canUseDom()) {
updateCSS(style, `${dynamicStyleMark}-dynamic-theme`);
} else {
devWarning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.');
warning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.');
}
}
2 changes: 1 addition & 1 deletion components/date-picker/__tests__/QuarterPicker.test.js
@@ -1,7 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import DatePicker from '..';
import { resetWarned } from '../../_util/devWarning';
import { resetWarned } from '../../_util/warning';

const { QuarterPicker } = DatePicker;

Expand Down
Expand Up @@ -9,7 +9,7 @@ import type { GenerateConfig } from 'rc-picker/lib/generate/index';
import { forwardRef, useContext } from 'react';
import enUS from '../locale/en_US';
import { getPlaceholder, transPlacement2DropdownAlign } from '../util';
import devWarning from '../../_util/devWarning';
import warning from '../../_util/warning';
import type { ConfigConsumerProps } from '../../config-provider';
import { ConfigContext } from '../../config-provider';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<

constructor(props: InnerPickerProps) {
super(props);
devWarning(
warning(
picker !== 'quarter',
displayName!,
`DatePicker.${displayName} is legacy usage. Please use DatePicker[picker='${picker}'] directly.`,
Expand Down