Skip to content

Commit

Permalink
Handle pre-transformed tagged-template literal by swc
Browse files Browse the repository at this point in the history
  • Loading branch information
Brijesh Bittu committed Feb 8, 2025
1 parent 35f443e commit 155b9ef
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 82 deletions.
196 changes: 117 additions & 79 deletions packages/pigment-css-core/src/processors/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* CssProcessor.
*/

import { SourceLocation } from '@babel/types';
import { SourceLocation, TemplateElement } from '@babel/types';
import {
type TransformedInternalConfig,
type StyleObjectReturn,
Expand All @@ -21,7 +21,7 @@ import {
serializeStyles,
valueToLiteral,
evaluateClassNameArg,
getCSSVar,
transformProbableCssVar,
} from '@pigment-css/utils';
import {
CallParam,
Expand Down Expand Up @@ -119,6 +119,99 @@ export type CssTailProcessorParams = BaseCssProcessorConstructorParams extends [
? T
: never;

function handleTemplateElementOrSimilar(
templateParams: (TemplateElement | ExpressionValue)[],
values: ValueCache,
processor: BaseCssProcessor,
) {
const { themeArgs = {}, pigmentFeatures: { useLayer = true } = {} } =
processor.options as TransformedInternalConfig;
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
let paramsToIterate = templateParams;
const [firstArg, ...restArgs] = templateParams;
if ('kind' in firstArg && firstArg.kind === ValueType.LAZY) {
const value = values.get(firstArg.ex.name) as string[];
templateStrs.push(...value);
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw.push(...value);
paramsToIterate = restArgs;
}
paramsToIterate.forEach((param) => {
if ('kind' in param) {
switch (param.kind) {
case ValueType.FUNCTION: {
const value = values.get(param.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST: {
if (typeof param.value === 'string') {
templateExpressions.push(transformProbableCssVar(param.value));
} else {
templateExpressions.push(param.value);
}
break;
}
case ValueType.LAZY: {
const evaluatedValue = values.get(param.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else if (typeof evaluatedValue === 'string') {
templateExpressions.push(transformProbableCssVar(evaluatedValue));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
} else if ('type' in param && param.type === 'TemplateElement') {
templateStrs.push(param.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(param.value.raw);
}
});
const { styles } = serializeStyles(
templateExpressions.length > 0 ? [templateStrs, ...templateExpressions] : [templateStrs],
);

const cssText = useLayer
? `@layer pigment.base{${processor.wrapStyle(styles, '')}}`
: processor.wrapStyle(styles, '');
const className = processor.getClassName();
const rules: Rules = {
[`.${className}`]: {
className,
cssText,
displayName: processor.displayName,
start: processor.location?.start ?? null,
},
};
const location = processor.location;
const sourceMapReplacements: Replacements = [
{
length: cssText.length,
original: {
start: {
column: location?.start.column ?? 0,
line: location?.start.line ?? 0,
},
end: {
column: location?.end.column ?? 0,
line: location?.end.line ?? 0,
},
},
},
];
processor.classNames.push(className);
processor.artifacts.push(['css', [rules, sourceMapReplacements]]);
}

/**
* Only deals with css`` or css(metadata)`` calls.
*/
Expand All @@ -138,84 +231,8 @@ export class CssTaggedTemplateProcessor extends BaseCssProcessor {
}

build(values: ValueCache): void {
const { themeArgs, pigmentFeatures: { useLayer = true } = {} } = this
.options as TransformedInternalConfig;
const [, templateParams] = this.templateParam;
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
const templateStrs: string[] = [];
// @ts-ignore @TODO - Fix this. No idea how to initialize a Tagged String array.
templateStrs.raw = [];
const templateExpressions: Primitive[] = [];
templateParams.forEach((param) => {
if ('kind' in param) {
switch (param.kind) {
case ValueType.FUNCTION: {
const value = values.get(param.ex.name) as TemplateCallback;
templateExpressions.push(value(themeArgs));
break;
}
case ValueType.CONST: {
templateExpressions.push(param.value);
break;
}
case ValueType.LAZY: {
const evaluatedValue = values.get(param.ex.name);
if (typeof evaluatedValue === 'function') {
templateExpressions.push(evaluatedValue(themeArgs));
} else if (
typeof evaluatedValue === 'object' &&
evaluatedValue &&
(evaluatedValue as unknown as Record<string, boolean>).isThemeVar
) {
templateExpressions.push(getCSSVar(evaluatedValue.toString(), true));
} else {
templateExpressions.push(evaluatedValue as Primitive);
}
break;
}
default:
break;
}
} else if ('type' in param && param.type === 'TemplateElement') {
templateStrs.push(param.value.cooked as string);
// @ts-ignore
templateStrs.raw.push(param.value.raw);
}
});
const { styles } = serializeStyles(
templateExpressions.length > 0 ? [templateStrs, ...templateExpressions] : [templateStrs],
);

const cssText = useLayer
? `@layer pigment.base{${this.wrapStyle(styles, '')}}`
: this.wrapStyle(styles, '');
const className = this.getClassName();
const rules: Rules = {
[`.${className}`]: {
className,
cssText,
displayName: this.displayName,
start: this.location?.start ?? null,
},
};
const location = this.location;
const sourceMapReplacements: Replacements = [
{
length: cssText.length,
original: {
start: {
column: location?.start.column ?? 0,
line: location?.start.line ?? 0,
},
end: {
column: location?.end.column ?? 0,
line: location?.end.line ?? 0,
},
},
},
];
this.classNames.push(className);
this.artifacts.push(['css', [rules, sourceMapReplacements]]);
handleTemplateElementOrSimilar(templateParams, values, this);
}
}

Expand All @@ -235,7 +252,28 @@ export class CssObjectProcessor extends BaseCssProcessor {
return params.flat().filter((param) => 'kind' in param);
}

isMaybeTransformedTemplateLiteral(values: ValueCache): boolean {
const [, firstArg, ...restArgs] = this.callParam;
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;
}

private buildForTransformedTemplateTag(values: ValueCache) {
const [, ...templateParams] = this.callParam;
handleTemplateElementOrSimilar(templateParams, values, this);
}

build(values: ValueCache): void {
if (this.isMaybeTransformedTemplateLiteral(values)) {
this.buildForTransformedTemplateTag(values);
return;
}
const [, ...callParams] = this.callParam;
const { themeArgs, pigmentFeatures: { useLayer = true } = {} } = this
.options as TransformedInternalConfig;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* 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-new';

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;
},
);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 155b9ef

Please sign in to comment.