abishekatp / stylers

Fully compile time scoped CSS for Leptos components
MIT License
139 stars 13 forks source link

[discussion] Nightly build dependency... #57

Closed nigeleke closed 9 months ago

nigeleke commented 12 months ago

I love the work being done for this crate. I wanted to understand the reason for its dependency on the nightly build though, so I decided to take a fork with the potential to remove it.

I can see that the underlying reason for the dependency is to track line / column numbers of Spans. This are used:

  1. In error messages (to show line numbers)
  2. When re-inserting whitespace (when necessary) that gets eaten by the TokenStream (the TokenStream being the macro's first point of contact with the enclosed tokens).

My fork (see the feature/no_nightly branch on my repo):

  1. Removed the nightly dependency
  2. Amended the error message not to provide line numbers (I fully admit this is not user friendly - especially if the embedded css is lengthy).
  3. Fails to re-insert whitespace as it's unable to determine the line number based logic... :( Forcing inserts, if required by the original Spans, cause an error for the 1st properties on the newline.

Given the above (and just because I can & have time) I decided to "roll my own", just to see where it would take me... This has made me appreciate the considerable work that's involved in the original to a much larger extent.

The key differences between approaches are:

  1. A deterministic class name is generated, rather than a random one (it's based on a hash of the tokens). This has meant that the compilation macro processing returns the class name and does the css syntax checking, but does not need to generate intermediate files.

  2. The build.rs phase scans all source and creates the main.css output. It doesn't require the intermediate files to "pick-up" the random class name that gets generated during compilation. (This is the stage where I realise how much work as gone into to knowing where to insert the ".random-class-name" appropriately :) :) :) )

  3. Source / target and output folders are currently by "convention" and cannot be changed during build - I'm yet to determine if that is workable.

The key point of this is discussion though is to understand the nightly build dependency, and whether, at some future date that's likely to no longer be necessary....

abishekatp commented 12 months ago

Hi @nigeleke, Thank you for spending your time on this issue. As you have said one of the core logic of this repo is parsing the selector and inserting random classes in it. First I will try to answer why do we need nightly version and then try to give some solutions to that problem.

The problem

Note: Since the style_str macro executes the 'from_ts()` method inside procedural macro context it requires nightly version of rust.

Possible solutions

nigeleke commented 11 months ago

I've got around the nightly build dependency with the following:

/// Recover the original source css embedded in the tokenstream.
/// Note: this can only be called from a macro invocation. The source
///       needs to be recovered directly from the source file otherwise.
///
pub fn source_from(tokens: &TokenStream) -> String {
    let group = Group::new(Delimiter::None, tokens.clone());
    let source = group.span().source_text().unwrap();

    let tokens = Vec::from_iter(tokens.clone().into_iter());

    let group_range = byte_range(&group.span());
    let (first_range, last_range) = 
        if tokens.len() > 0 {
            let len = tokens.len();
            (byte_range(&tokens[0].span()), byte_range(&tokens[len-1].span()))
        } else {
            (group_range.clone(), group_range.clone())
        };

    let start_offset = first_range.start - group_range.start;
    let length = last_range.end - first_range.start;
    let end_offset = start_offset + length;

    source[start_offset..end_offset].to_string()
}

and

/// Emualate the `proc_macro::Span::byte_range` method.
/// Rely on the debug format of `#n bytes(from..to))`.
/// TODO: Remove this when `byte_range` becomes available on the stable build...
///
pub fn byte_range(span: &Span) -> Range<usize> {
    let span = &format!("{:?}", span);
    let re = Regex::new(r"^(#\d+ )?bytes\((\d+)\.\.(\d+)\)$").unwrap();
    let captures = re.captures(span).unwrap();
    let start = captures[2].parse::<usize>().unwrap();
    let end = captures[3].parse::<usize>().unwrap();
    start..end
}

These are used in the "macro handling"; the "build" phase however reads directly from the file. The above is currently used in the roll my own version (style4rs), but could possibly get integrated into stylers. I'm happy to try and put this approach in stylers if you see any benefit. (I think it depends if / when .start() / .end() become available in stable.

abishekatp commented 10 months ago

Hi, Thank you for your patience, I have held upon something. This weekend I will explore your repo, Then we will decide the further steps. I hope this will help us improve the stylers.

nigeleke commented 10 months ago

Hi, Thank you for your patience, I have held upon something. This weekend I will explore your repo, Then we will decide the further steps. I hope this will help us improve the stylers.

Sorry - I have only just spotted your reply too; happy to chat further on the difference. My solution "works for me", though the error handling in stylers is much better. The alternatives table in the README shows a summary - https://nigeleke.github.io/style4rs/#alternatives.