Skip to content

Commit

Permalink
feat(perf): remove large union for looser type but much faster (#711)
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense authored Jul 6, 2021
1 parent 25a13ac commit 8ad3fe0
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 36 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Test
on:
push:
branches: [master, v1]
pull_request:
branches: [master, v1]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 'lts/*'
cache: 'yarn'
- run: yarn install --frozen-lockfile
# - run: yarn test
75 changes: 47 additions & 28 deletions src/runtime/react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as React from 'react';

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type IntrinsicElementsKeys = keyof JSX.IntrinsicElements;

// Any prop that has a default prop becomes optional, but its type is unchanged
// Undeclared default props are augmented into the resulting allowable attributes
Expand All @@ -23,30 +23,58 @@ type ReactDefaultizedProps<C, P> = C extends { defaultProps: infer D }
? Defaultize<P, D>
: P;

type MakeAttrsOptional<
C extends string | React.ComponentType<any>,
O extends object,
A extends keyof any
> = Omit<
ReactDefaultizedProps<
C,
React.ComponentPropsWithRef<
C extends IntrinsicElementsKeys | React.ComponentType<any> ? C : never
>
> &
O,
A
> &
Partial<
Pick<
React.ComponentPropsWithRef<
C extends IntrinsicElementsKeys | React.ComponentType<any> ? C : never
> &
O,
A
>
>;

export type StyledComponentProps<
// The Component from whose props are derived
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
C extends keyof string | React.ComponentType<any>,
// The other props added by the template
O extends object,
// The props that are made optional by .attrs
A extends keyof any
> = Omit<ReactDefaultizedProps<C, React.ComponentPropsWithRef<C>> & O, A> &
Partial<Pick<React.ComponentPropsWithRef<C> & O, A>> &
WithChildrenIfReactComponentClass<C>;
> =
// Distribute O if O is a union type
O extends object
? MakeAttrsOptional<C, O, A> & WithChildrenIfReactComponentClass<C>
: never;

// Because of React typing quirks, when getting props from a React.ComponentClass,
// we need to manually add a `children` field.
// See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/31945
// and https://github.com/DefinitelyTyped/DefinitelyTyped/pull/32843
type WithChildrenIfReactComponentClass<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>
> = C extends React.ComponentClass<any> ? { children?: React.ReactNode } : {};
C extends keyof string | React.ComponentType<any>
> = C extends React.ComponentClass<any>
? { children?: React.ReactNode | undefined }
: {};

type StyledComponentPropsWithAs<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
C extends keyof string | React.ComponentType<any>,
O extends object,
A extends keyof any
> = StyledComponentProps<C, O, A> & { as?: C };
> = StyledComponentProps<C, O, A> & { as?: C | undefined };

// abuse Pick to strip the call signature from ForwardRefExoticComponent
type ForwardRefExoticBase<P> = Pick<
Expand All @@ -60,32 +88,26 @@ export interface StyledComponent<
A extends keyof any = never
> extends ForwardRefExoticBase<StyledComponentProps<C, O, A>> {
// add our own fake call signature to implement the polymorphic 'as' prop
// NOTE: TS <3.2 will refuse to infer the generic and this component becomes impossible to use in JSX
// just the presence of the overload is enough to break JSX
//
// TODO (TypeScript 3.2): actually makes the 'as' prop polymorphic
(props: StyledComponentProps<C, O, A> & { as?: never }): React.ReactElement<
StyledComponentProps<C, O, A>
>;
<AsC extends keyof JSX.IntrinsicElements | React.ComponentType<any> = C>(
<AsC extends string | React.ComponentType<any> = C>(
props: StyledComponentPropsWithAs<AsC, O, A>,
): React.ReactElement<StyledComponentPropsWithAs<AsC, O, A>>;

withComponent<
WithC extends keyof JSX.IntrinsicElements | React.ComponentType<any>
>(
withComponent<WithC extends string | React.ComponentType<any>>(
component: WithC,
): StyledComponent<WithC, O>;
}

export type StyledComponentPropsWithRef<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>
C extends string | React.ComponentType<any>
> = React.ComponentPropsWithRef<C>;

type Attrs<P, A extends Partial<P>> = ((props: P) => A) | A;

export interface StyledFunction<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
C extends string | React.ComponentType<any>,
O extends object = {},
A extends keyof any = never
> {
Expand All @@ -107,28 +129,25 @@ export interface StyledFunction<
}

export type StyledTags = {
readonly [TTag in keyof JSX.IntrinsicElements]: StyledFunction<TTag>;
readonly [TTag in IntrinsicElementsKeys]: StyledFunction<TTag>;
};

export interface StyledOptions {
allowAs?: boolean;
allowAs?: boolean | undefined;
}

export type mapper<TInner, TOuter> = (input: TInner) => TOuter;

export interface CreateStyled extends StyledTags {
<C extends keyof JSX.IntrinsicElements | React.ComponentType<any>>(
<C extends string | React.ComponentType<any>>(
component: C,
options?: StyledOptions,
): StyledFunction<C>;

<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
OtherProps extends object
>(
<C extends string | React.ComponentType<any>, OtherProps extends object>(
component: C,
options?: StyledOptions,
): StyledFunction<C, OtherProps>; // tslint:disable-line:no-unnecessary-generics
options?: StyledOptions | undefined,
): StyledFunction<C, OtherProps>;
}

declare const styled: CreateStyled;
Expand Down
14 changes: 7 additions & 7 deletions types/test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const Button0 = styled('button')`
<Button0 type="button" />;

// href not allowed
<Button0 type="button" href />; // $ExpectError
<Button0 type="button" href='#' />; // $ExpectError

const Input0 = styled('input', {
allowAs: true,
Expand Down Expand Up @@ -92,9 +92,9 @@ const Button2 = styled<'button', PrimaryProps>('button')`
// missing primary
<Button2 type="button" />; // $ExpectError

const Button4 = styled<typeof ReactClassComponent0>(ReactClassComponent0)<
PrimaryProps
>``;
const Button4 = styled<typeof ReactClassComponent0>(
ReactClassComponent0,
)<PrimaryProps>``;

const Button5 = styled<typeof ReactSFC0>(ReactSFC0)<PrimaryProps>``;
<div>
Expand All @@ -116,7 +116,7 @@ const Button5 = styled<typeof ReactSFC0>(ReactSFC0)<PrimaryProps>``;
<Button5 />; // $ExpectError

// - missing primary
<Button5 column />; // $ExpectError
<Button5 />; // $ExpectError

const Container0 = styled(ReactClassComponent0)``;
<Container0 column={false} />;
Expand Down Expand Up @@ -228,7 +228,7 @@ const AsComponent1 = styled('input')``;
border-radius: 3px;
`;

const AttrsWithOnlyNewProps = styled.h2.attrs(p => ({ as: 'h1', ...p }))`
const AttrsWithOnlyNewProps = styled.h2.attrs((p) => ({ as: 'h1', ...p }))`
color: blue;
font-size: 14px;
`;
Expand All @@ -242,7 +242,7 @@ const AsComponent1 = styled('input')``;
// Ideally should complain about foo?
<ButtonAttrs />;

const ButtonAttrs2 = styled(ReactSFC0).attrs<{ other: boolean }>(p => ({
const ButtonAttrs2 = styled(ReactSFC0).attrs<{ other: boolean }>((p) => ({
column: p.column,
other: p.other,
}))``;
Expand Down
2 changes: 1 addition & 1 deletion types/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"astroturf/*": ["../src/runtime/*"]
}
},
"include": [".", "../src"]
"include": [".", "../src/runtime"]
}

0 comments on commit 8ad3fe0

Please sign in to comment.