Re: [csswg-drafts] Proposal: Custom CSS Functions & Mixins (#9350)

Yeah, that seems reasonable to me, and I'm generally fine with still needing a `calc()` there.

--------

Two more important topics to decide on: variable scoping, and execution model.

# Variables

Big decision is between dynamic scope, lexical scope, or some mix. 
* "Dynamic scope" here basically means all custom properties on the element are implicitly visible inside of functions, and nested functions as well. 
* "Lexical scope" means you'll only see variables that are explicitly declared. 
* A mix would likely mean that custom props are dynamic, arguments are lexical, so nested func invocations won't see the outer func's arguments but will still see the custom props from the element. (Whether the outer func can override custom props for the inner func is a separate question.) (Given the namespace collision, this might also mean we need two different functions to access values - `var()` for custom props and `arg()` for arguments.)

I think that the strong convention of nearly every programming language in the world suggests that going completely lexical is ideal - the only names visible to the function body are listed in its arg list.

Technically, this means that a function can get access to any custom properties they want, it just requires them to be explicitly passed as arguments. Lea said they'd prefer if this was easy to do, and I think that's handleable. I propose something like:

```css
@function --foo(--arg1, --arg2) using (--var-1, --var-2) { ... }
```

That is, a second arglist giving custom property names that they want to pull from the calling environment; the caller doesn't need to do anything for this to happen. They'd just call `width: --foo(1, 2);` and it would implicitly also fetch the `--var1` and `--var2` properties. This should be a full arglist, with type and defaults; the default is used if the custom property's value is invalid.

This also puts us in a good spot for future expansion into JS-backed custom functions, since JS is strictly lexical as well. The future `CSS.registerFunction(...)` call would just take an arg list and a using list, and function identically.

# Execution

This is the big one: are the functions declarative, or imperative?

For example, if you write:

```css
@function --foo(--arg1: 1) {
  --arg2: sin(var(--arg1));
  --arg1: 2;
  @return calc(var(--arg1) + var(--arg2));
}
```

What does each instance of `var(--arg1)` resolve to? Is it `1` followed by `2`, or both `2`?

I feel moderately strongly that we should go for a declarative model. That is, the body of the function is a declaration list that only allows custom properties, using normal declaration-list rules (last valid instance of a given property wins; the arglist is treated as setting several declarations at the beginning of the function body). This would give the answer "both `2`" in the above, the same that you'd get if you wrote that in a rule:

```css
.foo {
  --arg1: 1;
  --arg2: sin(var(--arg1));
  --arg1: 2;
  --return: calc(var(--arg1) + var(--arg2));
}
```

* Declarative is how CSS already works in every single existing CSS construct.
* Declarative allows for a desugaring into a few unique-named custom properties and then a big expression; imperative would never be desugarable. (I expect impls will act differently behind the scenes, but it gives us a simple spec model to work with.)
* Staying declarative makes it more reasonable to borrow existing CSS syntax, like using declarations to set variables. If we did an imperative design I'd pretty strongly feel that we should change the syntax to look more like other programming languages, to distinguish it; that means a good chunk of brand new Syntax work for a new parsing model.
* Declarative also implicitly sets a reasonably firm boundary on how far we're allowed to push the CSS-backed function model. It disallows loops, for instance. Past that point, we'd offer JS-backed functions instead. An imperative model, on the other hand, is only limited by what we choose to limit ourselves to; it could grow arbitrarily complex (see Sass and other preprocessors for examples). I'd prefer to keep the web platform at least *somewhat* segmented by purpose here, and let JS continue to fill the "real" programming side of things.
* Declarative also makes it simpler to integrate other CSS features. Like, if we did want to allow conditional rules inside the function body, then the "it's just a declaration list" model makes that completely straightforward and obvious to reason about, since it would be identical to how Nesting works. Imperative would mean defining novel semantics.

-- 
GitHub Notification of comment by tabatkins
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9350#issuecomment-1930463996 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 6 February 2024 17:49:09 UTC