lightningcss icon indicating copy to clipboard operation
lightningcss copied to clipboard

UnexpectedToken(Function("--spacing")) with .\[--anchor-gap\:--spacing\(3\)\] { css

Open qknight opened this issue 1 month ago • 2 comments

I want to use tailwind for CSS with cargo-leptos and i hit an issue with the generated CSS.

I first thought it is a cargo-leptos issue so I filed this ticket: https://github.com/leptos-rs/cargo-leptos/issues/599

Anyway, I will repeat the important things here again, so you don't have to read two tickets!

setup

  1. Started with https://github.com/leptos-rs/cargo-leptos?tab=readme-ov-file#features from a https://github.com/leptos-rs/start-axum-workspace
  2. by default this uses sass and i updated the Cargo.toml with:
# The tailwind input file.
#
# Optional, Activates the tailwind build
tailwind-input-file = "style/style.css"

# The tailwind config file.
#
# Optional, defaults to "tailwind.config.js" which if is not present
# is generated for you
tailwind-config-file = "tailwind.config.js"

The files like style/style.css and tailwind.config.js are in place and valid and I can run the tailwindcss command by hand without any issues.

style/style.css

@tailwind base;
@tailwind components;
@tailwind utilities;

/* https://stackoverflow.com/questions/5110833/css-how-to-print-a-table-with-background-color-without-print-settings-changes */
body{
  -webkit-print-color-adjust:exact !important;
  print-color-adjust:exact !important;
}

tailwind.config.js

const colors = require("tailwindcss/colors");
const homedir = require("os").homedir();
const path = require("path");

module.exports = {
  content: ["./frontend/src/**/*.rs"],
  plugins: [
    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
    require("@tailwindplus/elements"),
  ],
  theme: {
  },
};

The command is:

tailwindcss --input style/style.css --config tailwind.config.js --output /home/nixos/planicus/target/tmp/tailwind.css

This is usually executed by cargo-leptos automatically.

Image

using cargo leptos serve

now i want to develop with this setup and so i call a trunk like software:

  1. cargo leptos serve
  2. it calls various things and then tailwindcss --input style/style.css --config tailwind.config.js --output /home/nixos/planicus/target/tmp/tailwind.css
  3. after tailwindcss has finished, cargo-leptos reads /home/nixos/planicus/target/tmp/tailwind.css and puts it into the StyleSheet::parse(&source, ParserOptions::default()).unwrap(); to later minify it.

But it turns out that lightningcss fails with parsing the css in test_tailwind_css, I've extended tests/test_custom_parser.rs, see below:

I assume this is valid CSS as it is produced by the tailwind binary I also used with trunk before I tried to convert my code-base to cargo-leptos with SSR.

generated tailwind.css

Here is some lines from the generated tailwind.css file:


.filter {
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.transition {
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.\[--anchor-gap\:--spacing\(3\)\] {
  --anchor-gap: var(--spacing(3));
}

.\[appearance\:textfield\] {
  -webkit-appearance: textfield;
     -moz-appearance: textfield;
          appearance: textfield;
}

/* https://stackoverflow.com/questions/5110833/css-how-to-print-a-table-with-background-color-without-print-settings-changes */

body{
  -webkit-print-color-adjust:exact !important;
  print-color-adjust:exact !important;
}

.time-grid {
  display: grid;
  grid-template-columns: 60px repeat(7, 1fr);
  grid-template-rows: 40px repeat(15, 60px);
  /* header + 15 hours */
}

test to reproduce it

fn test_parse(source: &str) {
  let mut stylesheet = StyleSheet::parse(&source, ParserOptions::default()).unwrap();
  assert_eq!(1,2);
}

#[test]
fn test_tailwind_css() {
  test_parse(
    r#"
    .\[--anchor-gap\:--spacing\(3\)\] {
      --anchor-gap: var(--spacing(3));
    }
  "#
  )
}
test test_tailwind_css ... FAILED

failures:

---- test_tailwind_css stdout ----
thread 'test_tailwind_css' panicked at tests/test_custom_parser.rs:12:77:
called `Result::unwrap()` on an `Err` value: Error { kind: UnexpectedToken(Function("--spacing")), loc: Some(ErrorLocation { filename: "", line: 2, column: 25 }) }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test_tailwind_css

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

related

https://github.com/parcel-bundler/lightningcss/issues/1018

cargo-leptos vs. trunk

it seems that both projects are using lightningcss:

cargo-leptos: lightningcss = { version = "1.0.0-alpha.67", features = ["browserslist"] } trunk: lightningcss = "=1.0.0-alpha.65" (this is used by minify-html = "0.15.0")

after some trunk source code reading this was discovered:

  1. trunk src/pipelines/mod.rs calls src/processing/minify.rs for minimizing the css in
  2. minify_css(bytes: Vec<u8>) -> Vec<u8> returns the bytes uncompressed if compression failed or compressed. there is a warning and i have to check if there was a warning printed somewhere.

seems trunk executed the minify_css successfully as https://planicus.eu/style-3798f67b665eb67.css is minified and it contains the gap\:--spacing\(3\)\] sequence.

a plausible explanation is this:

Running trunk serve -v --release in the old setup would let tailwindcss do the:

tailwindcss args args=["--input", "/home/nixos/planicus_wip/src/style.css", "--output", "/home/nixos/planicus_wip/dist/.stage/style.css", "--minify"]

Image

gpt4 says

This is valid in Tailwind CSS (for arbitrary properties/selectors), but not normal/valid CSS syntax. Here, you're encoding the actual property name and its value (--anchor-gap: --spacing(3)) into the selector using backslash escapes.

Why the Error Occurs lightningcss's parser expects a valid CSS selector, where class names are "normal" identifiers, possibly allowing some escaping. Tailwind's arbitrary selectors use a syntax (like .\[--anchor-gap\:--spacing\(3\)\]) that pushes the boundaries of CSS parsing. When parsing, lightningcss encounters --spacing(3) inside the class selector, and interprets this as a "function token" instead of part of a (strange) class name.

qknight avatar Dec 07 '25 14:12 qknight

You need to run Tailwind's transformer before passing valid (standard) CSS to Lightning CSS.

devongovett avatar Dec 07 '25 18:12 devongovett

@devongovett you mention a transformer and i think cargo-leptos did that for me already.

  1. cargo leptos serve
  2. it calls various things and then tailwindcss --input style/style.css --config tailwind.config.js --output /home/nixos/planicus/target/tmp/tailwind.css
  3. after tailwindcss has finished, cargo-leptos reads /home/nixos/planicus/target/tmp/tailwind.css and puts it into the StyleSheet::parse(&source, ParserOptions::default()).unwrap(); to later minify it.

in step (3) is where my error occures.

i'll update the issue above to make this clear.

qknight avatar Dec 07 '25 19:12 qknight