Skip to content

[css-mixins] Can we drop the "anonymous function" idea for mixin evaluation? #14372

Description

@tabatkins

A while back, in #12927, we defined the evaluation model of mixins to, under the covers, wrap all the property values set by the mixin into anonymous functions. At the time, this let us move away from env()-based parameters/locals and instead use the existing custom-function var()-based parameters/locals. Functions had a nice "hypothetical element" model that let us use the author-chosen parameter names in var() without clashing with real custom properties already on the element.

Since then, we've made changes to mixins for a number of reasons, most recently #14004 to use the new generic @private rule for defining lexically-scoped custom properties, which lets us resolve mixin parameters/locals directly on the applying element and get the desired behavior wrt element-relative values like em.

As far as I can tell, this completely obviates the previous need for the function-based evaluation model. Am I right? Or is there still some significant detail I'm missing that the function-based model gives us?

/cc @andruud, as the originator of the function-based evaluation idea


As an example, take the --colorized-squish() example Anders gives in #14004, which shows off nested mixin application:

@mixin --squish(--left-color <color>,
                --right-color <color>: var(--left-color)) {
  &::before {
    content: "🡆";
    background-color: var(--left-color);
  }
  &::after {
    content: "🡄";
    background-color: var(--right-color);
  }
}

@mixin --colorized-squish(--color <color>) {
  background-color: var(--color);
  border: 2px solid oklch(from var(--color) calc(l - 0.1) c h);
  @apply --squish(oklch(from var(--color) calc(l - 0.3) c h),
                  oklch(from var(--color) calc(l - 0.2) c h));
}

div {
  @apply --colorized-squish(tomato);
}

Under the new spec model, this would desugar to approximately1:

/* First step, desugaring the `@apply --colorized-squish` */

div {
	/* colorized-squish() passed args */
	/* magically chosen unique name, only used once */
	@private { --arg-3e296714 <color>: tomato; }

	@nest { /* colorized-squish() mixin body */
		@private { --color <color>: var(--arg-3e296714); }
		           /* only usage of this name */
		background-color: var(--color);
		border: 2px solid oklch(from var(--color) calc(l - 0.1) c h);
		@apply --squish(oklch(from var(--color) calc(l - 0.3) c h),
		                oklch(from var(--color) calc(l - 0.2) c h);
	}
}


/* Second step, desugaring the nested `@apply squish` */

div {
	@private { --arg-3e296714 <color>: tomato; }

	@nest {
		@private { --color <color>: var(--arg-3e296714); }
		background-color: var(--color);
		border: 2px solid oklch(from var(--color) calc(l - 0.1) c h);

		@private { 
			/* squish() passed args */
			/* more magical unique names */
			--arg-dc8b6cab <color>: oklch(from var(--color) calc(l - 0.3) c h);
			--arg-d482b77e <color>: oklch(from var(--color) calc(l - 0.2) c h);
		}
		@nest { /* squish() mixin body */
			@private { /* again, only usage of the magic names */
				--left-color <color>: var(--arg-dc8b6cab);
				--right-color <color>: var(--arg-d482b77e, var(--left-color));
			}
			&::before {
				content: "🡆";
				background-color: var(--left-color);
			}
			&::after {
				content: "🡄";
				background-color: var(--right-color);
			}
		}
	}
}

I think that just makes all the behavior we want fall right out.

  • Values will resolve against the applying element.
  • Parameter names are only visible within the mixin body.
  • Parameter names are visible to nested mixins if they want, same as normal custom properties.
  • Multiple mixins using the same parameter names will shadow each other rather than accidentally colliding, even when they're applied inside each other.
  • An argument value using a var() will see names in the outer context, a parameter default will see names in the inner context.

Footnotes

  1. Using @nest to represent a nested declarations rule. Using the proposed "inline typed custom property" syntax for argument values.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions