Skip to content

Commit

Permalink
perf: refactor devWarning for production code size (ant-design#35411)
Browse files Browse the repository at this point in the history
* pref: better code style for production

* refactor `devWarning`

* don't use `useEffect` only wrap `devWarning`

* chore: add 'noop' to coverage

* chore: add test cases for devWarning

* chore: add test case

* chore: update test cases for devWarning

* chore: restore test script command

* fix: remove 'throw new Error'

* should not use `throw` for browser

* chore: update test case for AutoComplete

* perf: add prefix for `devWarning`

* update RegExp for UMD

* add prefix for ES and CJS

* chore: better code style

* perf:

* upgrade antd-tools

* remove `injectWarningCondition`

* rename `devWarning` to `warning`

* chore: better code style

* chore: better code style

* chore: restore hasValidName
  • Loading branch information
KAROTT7 authored May 10, 2022
1 parent 17cd13f commit 338ec7d
Show file tree
Hide file tree
Showing 59 changed files with 253 additions and 211 deletions.
65 changes: 65 additions & 0 deletions components/_util/__tests__/warning.test.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 338ec7d

Please sign in to comment.