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.
onlook-dev / onlook
The open source, local-first Figma for React. Design directly in your live React app and publish your changes to code.
Onlook
The first browser-powered visual editor
Explore the docs »
View Demo
·
Report Bug
·
Request Feature
Table of Contents
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
Built With
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
Installation
- Visit onlook.dev to download the app.
- 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:
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.
Static Class Extraction: Ensured that static classes wrapped in
TemplateLiteral
constructs are parsed and returned when no dynamic variables are present.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 noclassName
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 aTemplateLiteral
without any dynamic variables, the function extracts the classes and returns them as a list. ForTemplateLiteral
, the function parses itsquasis
(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 inclassName
.
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),
};
}
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 thetextarea
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)