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

💩 Spiking dynamic expressions to css vars #81

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/CssProp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,54 @@ storiesOf('CSS Prop', module)
<Button>Base</Button>
</Fragment>
);
})
.add('Foo', () => {
const Button: FC<ButtonProps & { width: any }> = ({
width,
children,
}) => (
<button
css={css`
background-color: #b3cde8;
border-radius: 6px;
border: none;
box-shadow: inset 0 -4px #9fb8d1;
color: white;
display: inline-block;
cursor: pointer;
font-family: sans-serif;
font-weight: 500;
font-size: 20px;
letter-spacing: 1px;
margin: 5px 10px;
padding: 10px 20px 14px 20px;
text-decoration: none;
transition: background-color 300ms, color 300ms;
outline: none;
width: ${width};

:hover {
background-color: #adc6e0;
color: rgba(255, 255, 255, 0.8);
}
:active {
background-color: #9fb8d1;
}
`}
>
{children}
</button>
);

function getWidth() {
return '333px';
}

return (
<Fragment>
<Button width="123px">Base</Button>
<Button width="222px">Base</Button>
<Button width={getWidth}>Base</Button>
</Fragment>
);
});
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
"@babel/core": "^7.7.5",
"@changesets/cli": "^2.6.2",
"@manypkg/cli": "^0.11.1",
"@storybook/addon-storysource": "^5.2.8",
"@storybook/addons": "^5.2.8",
"@storybook/react": "^5.2.8",
"@storybook/source-loader": "^5.2.8",
"@storybook/addon-storysource": "^6.0.21",
"@storybook/addons": "^6.0.21",
"@storybook/react": "^6.0.21",
"@storybook/source-loader": "^6.0.21",
"@storybook/storybook-deployer": "^2.8.1",
"@testing-library/react": "^10.0.0",
"@types/jest": "^25.2.0",
Expand Down
126 changes: 118 additions & 8 deletions packages/core/src/jsx.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
import { createElement, hasOwnProperty, ElementType, ReactNode } from 'react';

import {
createElement,
hasOwnProperty,
ElementType,
ReactNode,
useLayoutEffect,
useMemo,
} from 'react';
import { StyleCollector, CSSProps } from '@trousers/utils';
import useStyles from './useStyles';
import { registry } from '@trousers/registry';
import { parseObject } from '@trousers/parser';
import { useTheme } from '@trousers/theme';
import { toHash } from '@trousers/hash';

import css from './css';

type DynamicExpressions = Record<string, any>;

interface ActiveDefinition {
componentId: string;
styles: string;
dynamicExpressions: DynamicExpressions;
}

function isCollector<Theme>(
collector: StyleCollector<Theme> | CSSProps,
): collector is StyleCollector<Theme> {
return !!(collector as StyleCollector<Theme>).get;
}

import './types';
const interpolateStyles = (
styles: TemplateStringsArray | string[],
expressions: string[],
) =>
expressions.reduce(
(result, expression, index) =>
`${result}var(${expression})${styles[index + 1]}`,
styles[0],
);

const jsx = <
Props extends { css: StyleCollector<Theme> | CSSProps },
Props extends { css: StyleCollector<Theme> | CSSProps; [key: string]: any },
Theme extends {} = {}
>(
type: ElementType<Omit<Props, 'css'>>,
Expand All @@ -17,12 +50,89 @@ const jsx = <
return createElement(type, props, ...children);
}

const { css, ...rest } = props;
// eslint-disable-next-line react-hooks/rules-of-hooks
const themeCtx = useTheme();

const { css: collector, ...rest } = props;
const styleDefinitions = (isCollector<Theme>(collector)
? collector
: css([parseObject(css)])
).get();

const activeDefinitions: ActiveDefinition[] = styleDefinitions
.filter(({ predicate }) => !!predicate)
.map((definition, i) => {
const dynamicExpressions = definition.expressions.reduce<
DynamicExpressions
>((accum, expression, j) => {
accum[`--trousers-${i}-${j}`] = expression;
return accum;
}, {});

const styles = interpolateStyles(
definition.styles,
Object.keys(dynamicExpressions),
);

const componentId =
definition.name +
toHash(styles).toString() +
themeCtx.hash.toString();

return {
styles,
componentId,
dynamicExpressions,
};
});

const hash = activeDefinitions.reduce(
(accum, { componentId }) => accum + componentId,
'',
);

// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
const headElement = document.getElementsByTagName('head')[0];
const clientRegistry = registry(headElement, 'data-trousers');

activeDefinitions.forEach(definition =>
clientRegistry.register(
`.${definition.componentId}`,
definition.styles,
),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash]);

// eslint-disable-next-line react-hooks/rules-of-hooks
const className = useStyles<Theme>(props.css);
const style = useMemo(
() =>
activeDefinitions.reduce<CSSProps>(
(accum, { dynamicExpressions }) => {
Object.keys(dynamicExpressions).forEach(key => {
const expression = dynamicExpressions[key];
// @ts-ignore
accum[key] =
typeof expression === 'function'
? expression(themeCtx.theme as Theme)
: expression;
});

return accum;
},
{},
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[hash],
);

const className = activeDefinitions
.map(definition => definition.componentId)
.join(' ')
.trim();

return createElement(type, { ...rest, className }, ...children);
return createElement(type, { ...rest, className, style }, ...children);
};

export default jsx;
Expand Down
32 changes: 10 additions & 22 deletions packages/core/src/useStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { STYLE_ID, StyleCollector, CSSProps } from '@trousers/utils';
import { toHash } from '@trousers/hash';
import { useTheme } from '@trousers/theme';
import { ServerContext, ServerCtx } from '@trousers/server';
import { registry, Registry } from '@trousers/registry';
import { registry } from '@trousers/registry';
import { parseObject } from '@trousers/parser';

import { interpolateStyles, isBrowser } from './common';
Expand All @@ -14,20 +14,6 @@ interface ActiveDefinition {
styles: string;
}

function getComponentId(
name: string,
stylesHash: string,
themeHash: string = '',
) {
return `${name}${stylesHash}${themeHash}`;
}

function registerStyle(registry: Registry, definition: ActiveDefinition) {
const className = `.${definition.componentId}`;
if (registry.has(className)) return;
registry.register(className, definition.styles);
}

function isCollector<Theme>(
collector: StyleCollector<Theme> | CSSProps,
): collector is StyleCollector<Theme> {
Expand All @@ -51,11 +37,10 @@ export default function useStyles<Theme = {}>(
themeCtx.theme,
);

const componentId = getComponentId(
definition.name,
toHash(styles).toString(),
themeCtx.hash.toString(),
);
const componentId =
definition.name +
toHash(styles).toString() +
themeCtx.hash.toString();

return {
styles,
Expand All @@ -70,7 +55,10 @@ export default function useStyles<Theme = {}>(
}

if (!isBrowser() && !!serverStyleRegistry) {
registerStyle(serverStyleRegistry, activeDefinitions[0]);
serverStyleRegistry.register(
`.${activeDefinitions[0].componentId}`,
activeDefinitions[0].styles,
);
}

const hash = activeDefinitions.reduce(
Expand All @@ -83,7 +71,7 @@ export default function useStyles<Theme = {}>(
const clientRegistry = registry(headElement, STYLE_ID);

activeDefinitions.forEach(definition =>
registerStyle(clientRegistry, definition),
clientRegistry.register(definition.componentId, definition.styles),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash]);
Expand Down
35 changes: 9 additions & 26 deletions packages/registry/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,16 @@ const registry = (
const selector = !isGlobal ? id : ``;
const processedStyles = stylis(selector, styles);

if (process.env.NODE_ENV === 'production') {
try {
splitRules(processedStyles).forEach(styles => {
const sheet = getSheet(styleElement!);
sheet.insertRule(
styles,
isGlobal ? 0 : sheet.cssRules.length,
);
});

return;
} catch (error) {
console.warn(
`Trousers: unable to insert rule: ${styles}`,
error,
);
}
try {
splitRules(processedStyles).forEach(styles => {
const sheet = getSheet(styleElement!);
sheet.insertRule(styles, isGlobal ? 0 : sheet.cssRules.length);
});

return;
} catch (error) {
console.warn(`Trousers: unable to insert rule: ${styles}`, error);
}

const styleNode = document.createTextNode(`${processedStyles}\n`);
const mountedStyles = styleElement!.getAttribute(attributeId);

styleElement!.appendChild(styleNode);
styleElement!.setAttribute(
attributeId,
`${mountedStyles} ${id}`.trim(),
);
};

return {
Expand Down
Loading