Stylance

repository·main·Indexed 40 days ago

https://github.com/basro/stylance-rs

A Rust-based toolchain for managing scoped CSS. It provides proc macros to import CSS class names as type-safe Rust constants and a CLI to bundle and hash those classes for production, ensuring compile-time safety for styles in Rust projects.

Tokens
6.7K
Snippets
28
Records
34
Agent score
99%

What's inside Stylance

  1. What is Stylance-cli

    main

    Stylance-cli is the dedicated build tool for the Stylance ecosystem. It processes CSS module files to enable scoped styling by performing two primary transformations:

    1. Classname Hashing: Appends a unique hash as a suffix to every classname found (e.g., .class is transformed into .class-63gi2cY).
    2. Global Content Cleanup: Removes :global(contents) syntax while preserving the actual CSS content inside the block.

    For detailed usage instructions on how to integrate this into your workflow, refer to the main Stylance documentation.

  2. How Stylance works

    main

    Stylance is a tool for working with scoped CSS in Rust. It consists of two main components:

    1. Rust Proc Macros: These import scoped class names from CSS/SCSS files as string constants into your Rust code. This provides compile-time safety: using a non-existent class name results in a compiler error, and unused classes trigger warnings.
    2. Stylance CLI: A tool that scans your crate for CSS modules and generates a single output CSS file where all class names are transformed to include a deterministic hash.

    Class name hashes are based on the relative path between the CSS file and your crate's manifest directory (Cargo.toml). This separation allows you to modify CSS styles and rebuild the CSS bundle independently of the Rust compilation process.

  3. Inherit configuration from a Workspace

    main

    By setting workspace = true in a crate's [package.metadata.stylance] section, Stylance will look for configuration in the workspace's [workspace.metadata.stylance] section.

    Settings from the package take priority over workspace settings. When workspace = true is used, the hash_root_path defaults to the workspace directory.

    # In /my_project/crate1/Cargo.toml
    [package.metadata.stylance]
    workspace = true
    extensions = [".module.css"]
    
    # In /my_project/Cargo.toml
    [workspace.metadata.stylance]
    extensions = [".module.scss", ".module.css"]
    output_file = "./out.css"
    hash_len = 8
  4. Target global classnames with :global()

    main

    To target classnames defined globally (outside of your CSS module), wrap them in the :global() pseudo-selector within your CSS/SCSS file. This prevents the class name from being hashed while allowing the parent scoped class to remain hashed.

    Example CSS:

    .my_scoped_class :global(.paragraph) {
      color: red;
    }

    This transforms into:

    .my_scoped_class-f45126d .paragraph {
      color: red;
    }
  5. Use output-dir for SASS compatibility

    main

    If you are using the output in a SASS project and want to @use individual modules, use the --output-dir option instead of --output-file.

    Instead of a single bundle, this creates a stylance/ folder inside your specified directory containing individual transformed CSS modules and an _index.scss file that imports them all.

    stylance ./path/to/crate/dir/ --output-dir ./styles/

    In your SASS files, you can then use: @use "path/to/the/folder/stylance";

  6. Build multiple crates at once

    main

    You can pass multiple crate paths to the CLI to build them in a single command. This is more efficient and allows Stylance to properly concatenate outputs into a single file if they share the same --output-file.

    Single command for multiple crates:

    stylance . ./components/crate1 ./components/crate2

    Watch mode for multiple crates:

    stylance --watch . ./components/crate1 ./components/crate2

    Note: If you call stylance separately for each crate using the same --output-file, the second call will overwrite the first. Always pass them as multiple arguments to ensure concatenation.

    stylance ./crate1 ./crate2 --output-file out.css
  7. Use Stylance proc macros in Rust

    main

    First, add stylance to your dependencies:

    cargo add stylance

    Then, use the provided macros to import classes. There are two primary macros:

    • import_crate_style!(module_name, "path"): The path is relative to the crate's Manifest directory (where Cargo.toml resides).
    • import_style!(module_name, "path"): The path is relative to the parent directory of the file where the macro is called.

    All classes found in the file are included as constants within the module named by the first argument.

    // Import relative to Cargo.toml
    stylance::import_crate_style!(my_style, "src/component/card/card.module.scss");
    
    // Import relative to current file
    stylance::import_style!(my_style2, "card.module.scss");
    
    fn use_style() {
        // Accessing classes as constants
        println!("{} {}", my_style::header, my_style2::content);
    }
  8. Handle unused classname warnings

    main

    The import macros create constants that will trigger compiler warnings if they are unused. You can manage these using standard Rust attributes applied directly to the macro call:

    • Silence warnings: Use #[allow(dead_code)].
    • Turn warnings into errors: Use #[deny(dead_code)].
    // Silence warnings for unused classes
    stylance::import_crate_style!(#[allow(dead_code)] my_style, "src/component/card/card.module.scss");
    
    // Deny unused classes
    stylance::import_crate_style!(#[deny(dead_code)] my_style, "src/component/card/card.module.scss");
  9. How Stylance configuration inheritance works

    main

    Stylance uses a hierarchical configuration model based on Cargo's structure:

    1. Package Config: Settings defined in [package.metadata.stylance] apply to the specific crate.
    2. Workspace Config: If workspace = true is set in a package, Stylance will look for [workspace.metadata.stylance] in the workspace root Cargo.toml.
    3. Precedence: Package-level settings override workspace-level settings. If a setting is not provided at the package level, it falls back to the workspace level, and finally to the project defaults.

    Default Values:

    • extensions: [".module.css", ".module.scss"]
    • folders: ["./src/"]
    • hash_len: 7
  10. How to use global class names in .module.css

    main

    When writing .module.css or .module.scss files, you can reference external, non-scoped global class names by wrapping them in the :global() pseudo-selector. This prevents Stylance from applying a unique hash to that specific class name.

    Example

    If you have this in your CSS:

    .contents :global(.paragraph) {
        color: blue;
    }

    Stylance will expand it to something like:

    .contents-539306b .paragraph {
        color: blue;
    }

    This allows you to style standard HTML elements or third-party library classes within your scoped module.

  11. Configure Stylance in Cargo.toml

    main

    Stylance configuration is managed within your Cargo.toml under the [package.metadata.stylance] section. All settings are optional.

    KeyDescription
    output_filePath to the concatenated bundle file.
    output_dirDirectory where individual transformed modules and an _index.scss will be created.
    foldersDirectories to search for modules (defaults to ["./src/"]).
    extensionsFile extensions to treat as modules (defaults to [".module.scss", ".module.css"]).
    scss_preludeString prepended to generated SCSS files (e.g., @use "...";).
    hash_lenLength of the class name hash (defaults to 7).
    class_name_patternShape of transformed names. Supports [name] and [hash]. Defaults to "[name]-[hash]".
    hash_root_pathBase path for computing relative paths used in hashing (defaults to ".").
    workspaceIf true, inherits configuration from [workspace.metadata.stylance] and uses the workspace directory as the hash_root_path.
    [package.metadata.stylance]
    output_file = "./styles/bundle.scss"
    output_dir = "./styles/"
    folders = ["./src/", "./styles/"]
    extensions = [".module.scss", ".module.css"]
    scss_prelude = '@use "../path/to/prelude" as *;'
    hash_len = 7
    class_name_pattern = "my-project-[name]-[hash]"
    hash_root_path = "../../"
    workspace = true