DEV Community

Cleo Buenaventura
Cleo Buenaventura

Posted on

A Final Challenge: Release

The final week is here, which brings my issue to a conclusion. I think I did a pretty good job with achieving the goals I aimed for in the beginning of this challenge. This task focused on improving how Onlook detects and handles Tailwind class names, particularly in cases involving dynamic expressions or template literals. The goal was to provide better error detection, ensure static classes are correctly extracted, and allow users to navigate directly to the relevant code sections in their editor. Along the way, I tackled challenges, refined my technical skills, and interacted with the Onlook maintainer to ensure my contributions aligned with the project's goals.

GitHub logo onlook-dev / onlook

The open source, local-first Figma for React. Design directly in your live React app and publish your changes to code.

Figma for your React App

Onlook

The first browser-powered visual editor
Explore the docs »

View Demo · Report Bug · Request Feature

Discord LinkedIn Twitter

Table of Contents
  1. Installation
  2. Usage
  3. Roadmap
  4. Contributing
  5. Contact
  6. Acknowledgments
  7. License

The open-source visual editor for your React Apps

Seamlessly integrate with any website or web app running on React + TailwindCSS, and make live edits directly in the browser DOM. Customize your design, control your codebase, and push changes your changes without compromise.

Onlook.Studio.Component.Demo.for.GitHub.mp4

Export-1724891449817

Built With

  • React
  • Electron
  • Tailwind
  • Vite

Stay up-to-date

Onlook officially launched our first version of Onlook on July 08, 2024 and we've shipped a ton since then. Watch releases of this repository to be notified of future updates, and you can follow along with us on LinkedIn or Substack where we write a weekly newsletter.

Getting Started

image

Installation

  1. Visit onlook.dev to download the app.
  2. Run locally following this guide

Usage

Onlook will run on any React project, bring your own React project…

Achieving My Goals

The primary goal was to improve how Onlook detects and handles Tailwind class names within React components, particularly when dynamic expressions or template literals are used. I believe I achieved this effectively through:

  1. Error Detection for Dynamic Classes: Implemented logic to detect and return errors when dynamic expressions are present in class names. This ensures users are informed and can take appropriate action.

  2. Static Class Extraction: Ensured that static classes wrapped in TemplateLiteral constructs are parsed and returned when no dynamic variables are present.

  3. Source Navigation: Added a button in the UI that lets users navigate directly to the relevant code when dynamic variables are detected.

By the end of this task, the feature was functional, user-friendly, and aligned with Onlook's architecture.

Methods

Pull Request: https://github.com/onlook-dev/onlook/pull/849

Class Parsing Logic

The main function at the core of this feature is getNodeClasses, which is responsible for traversing a JSX element's AST (Abstract Syntax Tree) and extracting the className attribute. The function handles multiple scenarios:

  • Missing className Attribute: If no className attribute exists in the JSX element, the function immediately returns an error, allowing the system to handle this case gracefully.

  • Static Classes: If the className is a simple string or part of a TemplateLiteral without any dynamic variables, the function extracts the classes and returns them as a list. For TemplateLiteral, the function parses its quasis (static portions) to ensure all classes are properly extracted.

  • Dynamic Classes: If a TemplateLiteral contains dynamic expressions (e.g., ${variable}), the function detects this and returns an error with a specific reason. This ensures users are notified of unsupported dynamic constructs in className.

Here’s the relevant portion of the function that handles TemplateLiteral cases which I was responsible for:

if (
    t.isJSXExpressionContainer(classNameAttr.value) &&
    t.isTemplateLiteral(classNameAttr.value.expression)
) {
    const templateLiteral = classNameAttr.value.expression;

    if (templateLiteral.expressions.length > 0) {
        return {
            type: 'error',
            reason: 'Dynamic classes detected. Dynamic variables in the className prevent extraction of Tailwind classes.',
        };
    }

    const quasis = templateLiteral.quasis.map((quasi) => quasi.value.raw.split(/\s+/));
    return {
        type: 'classes',
        value: quasis.flat().filter(Boolean),
    };
}

Enter fullscreen mode Exit fullscreen mode

This logic ensures the function is robust enough to handle both valid and invalid class declarations while giving clear feedback for edge cases. This modular design not only simplifies testing but also allows for easy extensions if new requirements arise.

UI Integration

To integrate this functionality into the UI:

  • I updated the TailwindInput component where the textarea is implemented to handle and display warnings when dynamic classes were detected.

  • Added a button labeled "Go to source" that appears alongside the warning. Clicking this button navigates users to the exact location in their code where the dynamic class is defined.

What I learned

One of the most valuable lessons I gained from this experience was the importance of understanding code deeply and learning how to dig through layers of files in a codebase. Open-source projects can be intricate, with logic spread across multiple files and modules. Successfully implementing this feature required me to navigate the project's structure, understand its existing logic, and integrate my changes seamlessly. It was a constant process of reading, analyzing, and piecing together different parts of the system.

A key breakthrough for me was discovering the capabilities of @babel/types. Specifically, the isTemplateLiteral method allowed me to identify template literals within the JSX AST (Abstract Syntax Tree). This discovery greatly simplified my logic for handling className attributes. Without understanding how to leverage this utility, implementing a robust solution would have been significantly more challenging.

Another critical step was learning how to extract static classes from a TemplateLiteral. Using the quasis property of the TemplateLiteral node, I was able to parse the static portions of the string and filter out dynamic variables. This approach ensured that I could reliably return static classes while gracefully handling dynamic constructs.

More about Babel's AST documentation.

Conclusion

This task was a rewarding experience. I successfully improved Onlook's handling of dynamic Tailwind classes, providing users with better error detection and navigation tools. Along the way, I honed my skills in TypeScript and JSX parsing, while contributing meaningful enhancements to the project.

I’m proud of the work I’ve done and look forward to further contributions to Onlook and other open-source projects. This experience reaffirms my belief in the power of community-driven development to produce high-quality, user-friendly software.

Top comments (0)