Skip to content

Commit

Permalink
[react] Fix css extraction for trasformed styled tagged-template call
Browse files Browse the repository at this point in the history
  • Loading branch information
Brijesh Bittu committed Sep 12, 2024
1 parent 2a45a3a commit c54a7d6
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 30 deletions.
143 changes: 114 additions & 29 deletions packages/pigment-css-react/src/processors/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IOptions as IBaseOptions,
} from '@wyw-in-js/processor-utils';
import {
type FunctionValue,
ValueType,
type ConstValue,
type ExpressionValue,
Expand Down Expand Up @@ -238,7 +239,35 @@ export class StyledProcessor extends BaseProcessor {
});
}

private buildForTemplateTag(values: ValueCache): void {
private buildTemplateTag(
templateStrs: TemplateStringsArray,
templateExpressions: Primitive[],
values: ValueCache,
) {
const cssClassName = css(templateStrs, ...templateExpressions);
const cssText = cache.registered[cssClassName] as string;

const baseClass = this.getClassName();
this.baseClasses.push(baseClass);
this.collectedStyles.push([baseClass, cssText, null]);
const variantsAccumulator: VariantData[] = [];
this.processOverrides(values, variantsAccumulator);
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
this.generateArtifacts();
}

/**
* This handles transformation for direct tagged-template literal styled calls.
*
* @example
* ```js
* const Component = style('a')`
* color: red;
* `;
*/
private buildForTemplateTag(values: ValueCache) {
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
Expand Down Expand Up @@ -274,14 +303,74 @@ export class StyledProcessor extends BaseProcessor {
templateStrs.raw.push(item.value.raw);
}
});
const cssClassName = css(templateStrs, ...templateExpressions);
const cssText = cache.registered[cssClassName] as string;
this.buildTemplateTag(
templateStrs as unknown as TemplateStringsArray,
templateExpressions,
values,
);
}

const baseClass = this.getClassName();
this.baseClasses.push(baseClass);
this.collectedStyles.push([baseClass, cssText, null]);
const variantsAccumulator: VariantData[] = [];
/**
* This handles transformation for tagged-template literal styled calls that have already been
* transformed through swc. See [styled-swc-transformed-tagged-string.input.js](../../tests/styled/fixtures/styled-swc-transformed-tagged-string.input.js)
* for sample code.
*/
private buildForTransformedTemplateTag(values: ValueCache) {
// the below types are already validated in isMaybeTransformedTemplateLiteral check
const [templateStrArg, ...restArgs] = this.styleArgs as (LazyValue | FunctionValue)[];
const templateStrings = values.get(templateStrArg.ex.name) as string[];

const templateStrs: string[] = [...templateStrings];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [...templateStrings];
const templateExpressions: Primitive[] = [];
const { themeArgs } = this.options as IOptions;

restArgs.forEach((item) => {
switch (item.kind) {
case ValueType.FUNCTION: {
const value = values.get(item.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.LAZY: {
const evaluatedValue = values.get(item.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
});
this.buildTemplateTag(
templateStrings as unknown as TemplateStringsArray,
templateExpressions,
values,
);
}

private buildForStyledCall(values: ValueCache) {
const themeImportIdentifier = this.astService.addDefaultImport(
`${this.getImportPath()}/theme`,
'theme',
);
// all the variant definitions are collected here so that we can
// apply variant styles after base styles for more specific targetting.
let variantsAccumulator: VariantData[] = [];
(this.styleArgs as ExpressionValue[]).forEach((styleArg) => {
this.processStyle(values, styleArg, variantsAccumulator, themeImportIdentifier.name);
});
// Generate CSS for default variants first
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
variantsAccumulator = [];
this.processOverrides(values, variantsAccumulator);
// Generate CSS for variants declared in `styleOverrides`, if any
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
Expand All @@ -304,6 +393,18 @@ export class StyledProcessor extends BaseProcessor {
this.replacer(this.astService.stringLiteral(this.asSelector), false);
}

isMaybeTransformedTemplateLiteral(values: ValueCache): boolean {
const [firstArg, ...restArgs] = this.styleArgs;
if (!('kind' in firstArg) || firstArg.kind === ValueType.CONST) {
return false;
}
const firstArgVal = values.get(firstArg.ex.name);
if (Array.isArray(firstArgVal) && restArgs.length === firstArgVal.length - 1) {
return true;
}
return false;
}

/**
* This is called by Wyw-in-JS after evaluating the code. Here, we
* get access to the actual values of the `styled` arguments
Expand All @@ -314,32 +415,16 @@ export class StyledProcessor extends BaseProcessor {
* 2. CSS declared in theme object's styledOverrides
* 3. Variants declared in theme object
*/
build(values: ValueCache): void {
build(values: ValueCache) {
if (this.isTemplateTag) {
this.buildForTemplateTag(values);
return;
}
const themeImportIdentifier = this.astService.addDefaultImport(
`${this.getImportPath()}/theme`,
'theme',
);
// all the variant definitions are collected here so that we can
// apply variant styles after base styles for more specific targetting.
let variantsAccumulator: VariantData[] = [];
(this.styleArgs as ExpressionValue[]).forEach((styleArg) => {
this.processStyle(values, styleArg, variantsAccumulator, themeImportIdentifier.name);
});
// Generate CSS for default variants first
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
variantsAccumulator = [];
this.processOverrides(values, variantsAccumulator);
// Generate CSS for variants declared in `styleOverrides`, if any
variantsAccumulator.forEach((variant) => {
this.processVariant(variant);
});
this.generateArtifacts();
if (this.isMaybeTransformedTemplateLiteral(values)) {
this.buildForTransformedTemplateTag(values);
return;
}
this.buildForStyledCall(values);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* This is a pre-transformed file for testing.
*/
import { _ as _tagged_template_literal } from "@swc/helpers/_/_tagged_template_literal";
import { styled, keyframes } from '@pigment-css/react';

function _templateObject() {
const data = _tagged_template_literal(["\n 0% {\n transform: scale(0);\n opacity: 0.1;\n }\n\n 100% {\n transform: scale(1);\n opacity: 0.3;\n }\n"]);
_templateObject = function () {
return data;
};
return data;
}
function _templateObject1() {
const data = _tagged_template_literal(["\n 0% {\n opacity: 1;\n }\n\n 100% {\n opacity: 0;\n }\n"]);
_templateObject1 = function () {
return data;
};
return data;
}
function _templateObject2() {
const data = _tagged_template_literal(["\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(0.92);\n }\n\n 100% {\n transform: scale(1);\n }\n"]);
_templateObject2 = function () {
return data;
};
return data;
}
function _templateObject3() {
const data = _tagged_template_literal(["\n opacity: 0;\n position: absolute;\n\n &.", " {\n opacity: 0.3;\n transform: scale(1);\n animation-name: ", ";\n animation-duration: ", "ms;\n animation-timing-function: ", ";\n }\n\n &.", " {\n animation-duration: ", "ms;\n }\n\n & .", " {\n opacity: 1;\n display: block;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: currentColor;\n }\n\n & .", " {\n opacity: 0;\n animation-name: ", ";\n animation-duration: ", "ms;\n animation-timing-function: ", ";\n }\n\n & .", " {\n position: absolute;\n /* @noflip */\n left: 0px;\n top: 0;\n animation-name: ", ";\n animation-duration: 2500ms;\n animation-timing-function: ", ";\n animation-iteration-count: infinite;\n animation-delay: 200ms;\n }\n"]);
_templateObject3 = function () {
return data;
};
return data;
}

const touchRippleClasses = {
rippleVisible: 'MuiTouchRipple.rippleVisible',
ripplePulsate: 'MuiTouchRipple.ripplePulsate',
child: 'MuiTouchRipple.child',
childLeaving: 'MuiTouchRipple.childLeaving',
childPulsate: 'MuiTouchRipple.childPulsate',
};

const enterKeyframe = keyframes(_templateObject());
const exitKeyframe = keyframes(_templateObject1());
const pulsateKeyframe = keyframes(_templateObject2());

export const TouchRippleRoot = styled('span', {
name: 'MuiTouchRipple',
slot: 'Root'
})({
overflow: 'hidden',
pointerEvents: 'none',
position: 'absolute',
zIndex: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
borderRadius: 'inherit'
});

// This `styled()` function invokes keyframes. `styled-components` only supports keyframes
// in string templates. Do not convert these styles in JS object as it will break.
export const TouchRippleRipple = styled(Ripple, {
name: 'MuiTouchRipple',
slot: 'Ripple'
})(_templateObject3(), touchRippleClasses.rippleVisible, enterKeyframe, DURATION, param => {
let {
theme
} = param;
return theme.transitions.easing.easeInOut;
}, touchRippleClasses.ripplePulsate, param => {
let {
theme
} = param;
return theme.transitions.duration.shorter;
}, touchRippleClasses.child, touchRippleClasses.childLeaving, exitKeyframe, DURATION, param => {
let {
theme
} = param;
return theme.transitions.easing.easeInOut;
}, touchRippleClasses.childPulsate, pulsateKeyframe, param => {
let {
theme
} = param;
return theme.transitions.easing.easeInOut;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@keyframes e19ejdhp {
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
}
@keyframes e1rvu9kp {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes p1yhz964 {
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
}
.ttz155n {
overflow: hidden;
pointer-events: none;
position: absolute;
z-index: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
}
.tm7990f {
opacity: 0;
position: absolute;
}
.tm7990f.MuiTouchRipple.rippleVisible {
opacity: 0.3;
transform: scale(1);
animation-name: e19ejdhp;
animation-duration: ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.tm7990f.MuiTouchRipple.ripplePulsate {
animation-duration: 200ms;
}
.tm7990f .MuiTouchRipple.child {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
.tm7990f .MuiTouchRipple.childLeaving {
opacity: 0;
animation-name: e1rvu9kp;
animation-duration: ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.tm7990f .MuiTouchRipple.childPulsate {
position: absolute;
left: 0px;
top: 0;
animation-name: p1yhz964;
animation-duration: 2500ms;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
animation-iteration-count: infinite;
animation-delay: 200ms;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { styled as _styled, styled as _styled2 } from '@pigment-css/react';
import _theme from '@pigment-css/react/theme';
/**
* This is a pre-transformed file for testing.
*/
export const TouchRippleRoot = /*#__PURE__*/ _styled('span', {
name: 'MuiTouchRipple',
slot: 'Root',
})({
classes: ['ttz155n'],
});

// This `styled()` function invokes keyframes. `styled-components` only supports keyframes
// in string templates. Do not convert these styles in JS object as it will break.
const _exp6 = /*#__PURE__*/ () => Ripple;
export const TouchRippleRipple = /*#__PURE__*/ _styled2(_exp6(), {
name: 'MuiTouchRipple',
slot: 'Ripple',
})({
classes: ['tm7990f'],
});
Loading

0 comments on commit c54a7d6

Please sign in to comment.