Mixins with parameters #621
Unanswered
megheaiulian
asked this question in
Q&A
Replies: 1 comment
-
I was wondering the same thing and my initial solution ended up taking a while to come up with. Would be great to have a simpler and more thorough solution. Although I don't think it is part of the goals of LightningCSS to be able to do stuff like this. I've got something here that was working a few months ago but I haven't tested it recently. export function lightningCssVisitor() {
const mixins = new Map();
const variables = new Map();
const errorText = '\x1b[31m%s\x1b[0m';
return {
Function: {
useVar({ arguments: tokens }) {
const name = tokens.map(getTokenRawValue).join('');
if (!name) {
console.warn(errorText, `useVar(${name}) is unknown.`);
}
return {
raw: `var(--noop,${variables.get(name) ?? ''})`, // TODO: Remove this var wrapper if https://github.com/parcel-bundler/lightningcss/issues/579 is fixed
};
},
},
Rule: {
unknown: {
mixin(rule) {
const firstToken = rule.prelude[0];
const token = firstToken.type === 'token' ? firstToken.value : firstToken;
const isFunction = token.type === 'function';
const isName = token.type === 'ident';
if (!isFunction && !isName) {
throw new Error('[LightningCSS]: Invalid mixin.');
}
const name = token.type === 'function' ? token.value.name : token.value;
const declarations = resolveDeclarations(rule.block || []);
mixins.set(name, {
arguments: isFunction ? resolveFunctionParams(token.value.arguments) : [],
declarations,
});
return [];
},
include(rule) {
const firstToken = rule.prelude[0];
const token = firstToken.type === 'token' ? firstToken.value : firstToken;
const isFunction = token.type === 'function';
const isName = token.type === 'ident';
if (!isFunction && !isName) {
throw new Error('[LightningCSS]: Unknown mixin.');
}
const name = token.type === 'function' ? token.value.name : token.value;
const functionValues = isFunction ? resolveFunctionParams(token.value.arguments) : [];
const mixin = mixins.get(name);
if (!mixin) {
console.warn(errorText, `Mixin '${name}' has not been registered yet.`);
return [];
}
const {
declarations,
arguments: argumentDefinitions,
} = mixin;
argumentDefinitions.forEach(({ name, defaultValue }, index) => {
variables.set(name, functionValues[index]?.name ?? defaultValue);
});
return {
type: 'nesting',
value: {
loc: rule.loc,
style: {
loc: rule.loc,
selectors: [[{ type: 'nesting' }]],
declarations,
},
},
};
},
},
},
};
}
function resolveDeclarations(block) {
let tokens = [];
const declarations = [];
const importantDeclarations = [];
let declaration = {};
for (const token of block) {
const isToken = token.type === 'token';
if (isToken && token.value.type === 'colon') {
declaration = {
property: tokens.map(getTokenRawValue).join('').trim(),
};
tokens = [];
continue;
}
if (isToken && token.value.type === 'semicolon') {
const rawValue = tokens.map(getTokenRawValue).join('').trim();
declaration = {
property: declaration.property,
raw: rawValue,
};
rawValue.endsWith('!important') ? importantDeclarations.push(declaration) : declarations.push(declaration);
tokens = [];
continue;
}
tokens.push(token);
}
return {
importantDeclarations,
declarations,
};
}
function resolveFunctionParams(params) {
const functionArguments = [];
let tokens = [];
let argumentName = false;
for (const token of params) {
const isToken = token.type === 'token';
if (isToken && token.value.type === 'colon') {
argumentName = tokens.map(getTokenRawValue).join('').trim();
tokens = [];
continue;
}
if (isToken && token.value.type === 'comma') {
const rawValue = tokens.map(getTokenRawValue).join('').trim();
const argument = argumentName ? {
name: argumentName,
defaultValue: rawValue,
} : {
name: rawValue,
defaultValue: '',
};
argumentName = '';
functionArguments.push(argument);
tokens = [];
continue;
}
tokens.push(token);
}
if (tokens.length > 0) {
const rawValue = tokens.map(getTokenRawValue).join('').trim();
const argument = argumentName ? {
name: argumentName,
defaultValue: rawValue,
} : {
name: rawValue,
defaultValue: '',
};
functionArguments.push(argument);
}
return functionArguments;
}
function getTokenRawValue(token) {
if (token.type === 'token') {
return getTokenRawValue(token.value);
}
if (
token.type === 'ident'
|| token.type === 'dashed-ident'
|| token.type === 'white-space'
|| token.type === 'number'
|| token.type === 'delim'
) {
return '' + token.value;
}
if (token.type === 'string') {
return `"${token.value}"`;
}
if (token.type === 'var') {
const fallback = token.value.fallback
? ', ' + token.value.fallback.map(getTokenRawValue).join('')
: '';
return `var(${token.value.name.ident}${fallback})`;
}
if (token.type === 'function') {
return `${token.value.name}(${token.value.arguments.map(getTokenRawValue).join('')})`;
}
if (token.type === 'color') {
return getRawColour(token.value);
}
if (token.type === 'percentage') {
return `${token.value * 100}%`;
}
if (token.type === 'comma') {
return ',';
}
if (token.type === 'colon') {
return ':';
}
if (token.type === 'length') {
return `${token.value.value}${token.value.unit}`;
}
if (token.type === 'angle') {
return `${token.value.value}${token.value.type}`;
}
const errorText = '\x1b[31m%s\x1b[0m';
console.error(errorText, `[LightningCSS]: Token type '${token.type}' is not handled. Make sure you handle it's raw values.`);
return '';
}
function getRawColour(color) {
if (color.type === 'currentcolor') {
return 'currentcolor';
}
const values = [...color.type].flatMap(char => {
return char in color ? color[char] : [];
});
return `${color.type}a(${values.join(', ')}, ${color.alpha})`;
} Can't get it to work with your example but you would use it something like this: @mixin color-green {
color: green;
}
@mixin contain(containWidth) {
width: 100%;
max-width: useVar(containWidth);
margin-left: auto;
margin-right: auto;
}
/* with a default value */
@mixin contain(containWidth: 1080px) {
width: 100%;
max-width: useVar(containWidth);
margin-left: auto;
margin-right: auto;
}
.contained {
@include color-green;
@include contain;
/* or with variables */
@include contain(420px);
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
The documentation at https://lightningcss.dev/transforms.html provides some good explanation about creating mixins but is it possible to create a custom at-rule for a mixin that takes some parameter?
With postcss i'd use something like this:
Beta Was this translation helpful? Give feedback.
All reactions