ant-design-icons icon indicating copy to clipboard operation
ant-design-icons copied to clipboard

Create a reusable alias for each forwardRef'd component

Open DanielRosenwasser opened this issue 3 years ago • 9 comments

This is a slightly experimental PR since I'm not that familiar with the codebase; however, an investigation on compiling ant-design uncovered that a lot of string literal type syntax nodes were being created during parsing. We found that many of them came directly from the .d.ts files of @ant-design/icons.

This is because React.forwardRef creates several anonymous intermediate types and TypeScript's declaration emit ends up having to write those types inline. Those types each contain over 360 string literals which are all the same!

declare const _default: React.ForwardRefExoticComponent<Pick<AntdIconProps, "max" | "required" | "default" | "high" | "low" | "disabled" | "start" | "open" | "media" | "hidden" | "cite" | "data" | "dir" | "form" | "label" | "slot" | "span" | "style" | "summary" | "title" | "pattern" | "async" | "defer" | "manifest" | "color" | "content" | "size" | "wrap" | "multiple" | "height" | "rotate" | "translate" | "width" | "prefix" | "src" | "children" | "key" | "list" | "step" | "value" | "aria-label" | "spin" | "accept" | "acceptCharset" | "action" | "allowFullScreen" | "allowTransparency" | "alt" | "as" | "autoComplete" | "autoFocus" | "autoPlay" | "capture" | "cellPadding" | "cellSpacing" | "charSet" | "challenge" | "checked" | "classID" | "cols" | "colSpan" | "controls" | "coords" | "crossOrigin" | "dateTime" | "download" | "encType" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "frameBorder" | "headers" | "href" | "hrefLang" | "htmlFor" | "httpEquiv" | "integrity" | "keyParams" | "keyType" | "kind" | "loop" | "marginHeight" | "marginWidth" | "maxLength" | "mediaGroup" | "method" | "min" | "minLength" | "muted" | "name" | "nonce" | "noValidate" | "optimum" | "placeholder" | "playsInline" | "poster" | "preload" | "readOnly" | "rel" | "reversed" | "rows" | "rowSpan" | "sandbox" | "scope" | "scoped" | "scrolling" | "seamless" | "selected" | "shape" | "sizes" | "srcDoc" | "srcLang" | "srcSet" | "target" | "type" | "useMap" | "wmode" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "draggable" | "id" | "lang" | "spellCheck" | "tabIndex" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "twoToneColor"> & React.RefAttributes<HTMLSpanElement>>;
export default _default;

However, if we create a type alias (here called ForwardRefComponentBase) and annotate every component with that type, the declaration emit will reuse that type instead.

declare const Comp: ForwardRefComponentBase<AntdIconProps>;
export default Comp;

This brings the size down of each component from 5.98KB to 165 byes. This cumulatively brings down the size of your declaration files from 4.66MB to 189KB.

That's a substantial reduction of the size of your published packages. It will also reduce the amount of code that TypeScript needs to parse and keep in memory for all users, meaning faster builds for all your consumers.

It's not highly scientific, but here's a before/after of just doing type-checking @ant-design/icons:

image

DanielRosenwasser avatar Apr 22 '21 22:04 DanielRosenwasser

@afc163 @zombieJ @John60676 any chance I can get a review on this PR? I expect it'll have a great downstream effect for you and your users!

DanielRosenwasser avatar Apr 29 '21 19:04 DanielRosenwasser

/rebase

afc163 avatar Aug 16 '21 07:08 afc163

CI failed, could you fix it?

src/icons/WalletOutlined.tsx(11,7): error TS2604: JSX element type 'AntdIcon' does not have any construct or call signatures.
src/icons/WalletOutlined.tsx(15,13): error TS2304: Cannot find name 'ForwardRefComponentBase'.
src/icons/WalletOutlined.tsx(15,13): error TS4025: Exported variable 'Comp' has or is using private name 'ForwardRefComponentBase'.
src/icons/WalletTwoTone.tsx(11,7): error TS2604: JSX element type 'AntdIcon' does not have any construct or call signatures.
src/icons/WalletTwoTone.tsx(15,13): error TS2304: Cannot find name 'ForwardRefComponentBase'.
src/icons/WalletTwoTone.tsx(15,13): error TS4025: Exported variable 'Comp' has or is using private name 'ForwardRefComponentBase'.
src/icons/WarningFilled.tsx(11,7): error TS2604: JSX element type 'AntdIcon' does not have any construct or call signatures.
src/icons/WarningFilled.tsx(15,13): error TS2304: Cannot find name 'ForwardRefComponentBase'.
src/icons/WarningFilled.tsx(15,13): error TS4025: Exported variable 'Comp' has or is using private name 'ForwardRefComponentBase'.
src/icons/WarningOutlined.tsx(11,7): error TS2604: JSX element type 'AntdIcon' does not have any construct or call signatures

afc163 avatar Aug 16 '21 07:08 afc163

I've pushed the changes, but I don't seem to be getting a full CI run.

DanielRosenwasser avatar Aug 17 '21 21:08 DanielRosenwasser

@afc163 just checking in - is there an easy way to kick off the CI?

DanielRosenwasser avatar Aug 24 '21 00:08 DanielRosenwasser

Pinging @tangjinzhou @zombieJ @afc163 @hullis, this is an old PR but will likely speed up load time, check time, and memory usage for everyone using ant-design

DanielRosenwasser avatar Jun 03 '22 22:06 DanielRosenwasser

Must fix CI firstly, I'll do that asap

vagusX avatar Jun 07 '22 03:06 vagusX

@DanielRosenwasser could you please rebase master?

vagusX avatar Jun 10 '22 07:06 vagusX

@vagusX thanks for following up - I was on vacation back then. I've rebased the commits.

DanielRosenwasser avatar Sep 14 '22 05:09 DanielRosenwasser