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

Add :global() to inline css classes automatically #68

Closed
wants to merge 4 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
36 changes: 36 additions & 0 deletions packages/next-yak/loaders/__tests__/cssloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,4 +649,40 @@ const headline = css\`
}"
`);
});

it("should add :global automatically to inline css classes", async () => {
expect(
await cssloader.call(
loaderContext,
`
import { css } from "next-yak";

const headline = css\`
color: red;
.dark-mode {
color: black;
}
.my-fancy-class,
.my-button {
color: blue;
}
transition: all 0.3s ease-in-out;
letter-spacing: 0.05em;
\``
)
).toMatchInlineSnapshot(`
".headline_0 {
color: red;
:global(dark-mode) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be :global(.dark-mode)

color: black;
}
:global(my-fancy-class),
:global(my-button) {
color: blue;
}
transition: all 0.3s ease-in-out;
letter-spacing: 0.05em;
}"
`);
});
});
58 changes: 34 additions & 24 deletions packages/next-yak/loaders/cssloader.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const localIdent = require("./lib/localIdent.cjs");
const replaceQuasiExpressionTokens = require("./lib/replaceQuasiExpressionTokens.cjs");
const getStyledComponentName = require("./lib/getStyledComponentName.cjs");
const getCssName = require("./lib/getCssName.cjs");
const wrapClassesWithGlobal = require("./lib/wrapClassesWithGlobal.cjs");
const murmurhash2_32_gc = require("./lib/hash.cjs");
const { relative } = require("path");
const {
Expand Down Expand Up @@ -93,7 +94,6 @@ module.exports = async function cssLoader(source) {
/** @type {Map<string, number | string | null>} */
let topLevelConstBindings = new Map();


babel.traverse(ast, {
Program(path) {
topLevelConstBindings = getConstantValues(path, t);
Expand Down Expand Up @@ -204,8 +204,9 @@ module.exports = async function cssLoader(source) {
const variableName =
isStyledLiteral || isStyledCall || isAttrsCall || isKeyFrameLiteral
? getStyledComponentName(path)
: isCssLiteral ? getCssName(path)
: null
: isCssLiteral
? getCssName(path)
: null;

const literalSelector = localIdent(
variableName || "_yak",
Expand All @@ -217,14 +218,19 @@ module.exports = async function cssLoader(source) {
const currentCssParts = {
quasiCode: [],
cssPartExpressions: [],
selector: !parentLocation ? literalSelector : `&:where(${literalSelector})`,
selector: !parentLocation
? literalSelector
: `&:where(${literalSelector})`,
hasParent: Boolean(parentLocation),
};
const parentCssParts = parentLocation && cssParts.get(parentLocation.parent);
const parentCssParts =
parentLocation && cssParts.get(parentLocation.parent);
cssParts.set(path, currentCssParts);
if (parentCssParts) {
parentCssParts.cssPartExpressions[parentLocation.currentIndex] ||= [];
parentCssParts.cssPartExpressions[parentLocation.currentIndex].push(currentCssParts);
parentCssParts.cssPartExpressions[parentLocation.currentIndex].push(
currentCssParts
);
}

// Replace the tagged template expression with a call to the 'styled' function
Expand All @@ -251,8 +257,10 @@ module.exports = async function cssLoader(source) {
const relativePath = relative(rootContext, resourcePath);
hashedFile = murmurhash2_32_gc(relativePath);
}
const variableName = t.isExpression(expression) && getConstantName(expression, t);
const variableConstValue = variableName && topLevelConstBindings.get(variableName);
const variableName =
t.isExpression(expression) && getConstantName(expression, t);
const variableConstValue =
variableName && topLevelConstBindings.get(variableName);
if (variableConstValue === null || variableConstValue === undefined) {
currentCssParts.quasiCode.push({
insideCssValue: true,
Expand All @@ -264,7 +272,8 @@ module.exports = async function cssLoader(source) {
} else {
currentCssParts.quasiCode.push({
insideCssValue: true,
code: unEscapeCssCode(quasi.value.raw) + String(variableConstValue),
code:
unEscapeCssCode(quasi.value.raw) + String(variableConstValue),
});
}
} else {
Expand All @@ -273,7 +282,7 @@ module.exports = async function cssLoader(source) {
// empty quasis are also added to keep spacings
// e.g. `transition: color ${duration} ${easing};`
currentCssParts.quasiCode.push({
code: unEscapeCssCode(quasi.value.raw),
code: unEscapeCssCode(wrapClassesWithGlobal(quasi.value.raw)),
insideCssValue: false,
});
}
Expand All @@ -285,10 +294,7 @@ module.exports = async function cssLoader(source) {
// const Bar = styled.div` ${MyStyledDiv} { color: blue }`
// "${MyStyledDiv} {" -> ".selector-0 {"
if (variableName && (isStyledLiteral || isStyledCall || isAttrsCall)) {
variableNameToStyledClassName.set(
variableName,
literalSelector
);
variableNameToStyledClassName.set(variableName, literalSelector);
}
},
});
Expand All @@ -310,7 +316,7 @@ const unEscapeCssCode = (code) => code.replace(/\\\\/gi, "\\");
/**
* Searches the closest parent TaggedTemplateExpression using a name from localNames
* Returns the location inside this parent
*
*
* @param {import("@babel/core").NodePath<import("@babel/types").TaggedTemplateExpression>} path
* @param {{ css?: string , styled?: string }} localNames
*/
Expand All @@ -325,7 +331,6 @@ const getClosestTemplateLiteralExpressionParentPath = (
let parent = path.parentPath;
const t = babel.types;
while (parent) {

if (t.isTaggedTemplateExpression(parent.node)) {
const tag = parent.node.tag;
const isCssLiteral =
Expand Down Expand Up @@ -353,16 +358,20 @@ const getClosestTemplateLiteralExpressionParentPath = (
/** @type {babel.types.Identifier} */ (tag.callee.property).name ===
"attrs";
if (isCssLiteral || isStyledLiteral || isStyledCall || isAttrsCall) {
if (!t.isTemplateLiteral(child.node) || !t.isExpression(grandChild.node)) {
if (
!t.isTemplateLiteral(child.node) ||
!t.isExpression(grandChild.node)
) {
throw new Error("Broken AST");
}
const currentIndex = child.node.expressions.indexOf(grandChild.node);
return (
{ parent:
/** @type {import("@babel/core").NodePath<import("@babel/types").TaggedTemplateExpression>} */(parent),
currentIndex
}
);
return {
parent:
/** @type {import("@babel/core").NodePath<import("@babel/types").TaggedTemplateExpression>} */ (
parent
),
currentIndex,
};
}
}
if (!parent.parentPath) {
Expand Down Expand Up @@ -397,7 +406,8 @@ const mergeCssPartExpression = (cssPartExpression, level = 0) => {
}
}
// Try to keep the same indentation as the original code
const indent = quasiCode[0]?.code.match(/^\n( |\t)(\s*)/)?.[2] ?? " ".repeat(level);
const indent =
quasiCode[0]?.code.match(/^\n( |\t)(\s*)/)?.[2] ?? " ".repeat(level);
const hasCss = Boolean(cssPart.trim());
css += !hasCss
? ""
Expand Down
28 changes: 28 additions & 0 deletions packages/next-yak/loaders/lib/wrapClassesWithGlobal.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Searches for all css class names in the given css string and wraps them with :global()
*
* e.g. `
* color: red;
* .dark-mode {
* color: black;
* }
* ` -> `
* color: red;
* :global(.dark-mode) {
* color: black;
* }
* `
*
* @param {string} cssString
*/
const wrapClassesWithGlobal = (cssString) => {
// Replace each matched class name with :global(className)
return cssString.replace(
/(?:^|\s)\.([a-zA-Z0-9-_]+)(?=[,\s{])/g,
(_match, className) => {
return `:global(${className})`;
}
);
};

module.exports = wrapClassesWithGlobal;
Loading