Jeremy Fairbank
Dynamically Sassy

Dynamically Sassy

by Jeremy Fairbank

In this video titled "Dynamically Sassy" by Jeremy Fairbank, the focus is on leveraging the Sass CSS preprocessor to create dynamic stylesheets based on user input. Fairbank addresses the challenge of allowing users to customize user interfaces while reusing existing static CSS.

Key points discussed include:

- Introduction to Sass: Fairbank introduces Sass as a CSS preprocessor that enhances the maintainability of stylesheets through features such as variables, nested rules, and mixins.

- Dynamic CSS Generation: The core goal is to generate dynamic CSS by accepting user input while retaining the ability to reuse static stylesheets. Fairbank emphasizes not duplicating code across static and dynamic contexts.

- User Experience Design: He discusses how Simply Built, the website builder he works on, originally limited users with predefined color palettes. The new dynamic customization allows users to select a seed color from which various pleasing palettes can be generated.

- Technical Implementation: The talk details the implementation process, including writing Sass functions in Ruby, managing dynamic content injection, and the limitations of Rails’ asset pipeline for supporting dynamic rendering.

- Performance Concerns: Fairbank identifies potential performance bottlenecks such as slow rendering times when dealing with complex stylesheets. Solutions explored include caching generated CSS and using background processing to improve efficiency.

- Caching Strategies: Integrating caching with tools like memcached is suggested to reduce render times considerably from two seconds to less than 500 milliseconds for repeated requests.

- Background Processing: Fairbank also suggests using Sidekiq for offloading rendering processes, which can manage requests more efficiently without clogging the main server thread.

Fairbank concludes with the importance of balancing simplicity in solutions with the need to consider performance implications, reflecting on how well-structured code practices can significantly enhance overall performance. The session provides valuable insights into optimizing Sass for dynamically generated styles, offering practical solutions to common challenges in web development.

00:00:11.870 Alright, good afternoon everyone. How are we doing after lunch? Y'all alright? Sweet! Thank you for coming out to this talk. We don't normally see a lot of presentations on Sass, so I hope you'll find this one interesting.
00:00:20.550 Today, we’re going to look at making Sass a little more dynamic. To give you an overview, our goal is to take our Sass stylesheets and, in a dynamic way, accept user input from the client to render dynamic CSS. This is in contrast to the static stylesheets we might typically use in the asset pipeline.
00:00:36.870 So, what are we going to discuss today? Obviously, we are going to focus on generating dynamic CSS based on user input. We will learn how to write Sass functions in Ruby as we need to figure out a way to inject dynamic content into a stylesheet context.
00:01:02.850 While doing this, we may discover that it's not the most efficient approach due to performance issues. Therefore, we will identify potential performance bottlenecks and consider solutions, including caching and possibly background processing.
00:01:29.759 Before we dive deeper, let me introduce myself. I'm Jeremy. I live in Tennessee in a very small town and you can find me on Twitter as @el_papa_poyo. I work for a company called Push Agency, where we are a completely distributed team, and I’m a remote web developer engaged in a lot of Rails and JavaScript work, full stack development.
00:01:42.270 We do a lot of client work, but we also have a product called Simply Built. Simply Built is a website builder where users can create multiple websites. They can edit their sites directly within the application using a drag-and-drop interface. This editor allows users to rearrange different content blocks directly via the HTML5 content-editable attribute.
00:02:29.550 So, why am I bringing up Simply Built during a talk about Sass? Am I pitching our product? Well, sort of! Earlier this year, I was tasked with developing a new feature for Simply Built. At the time, Simply Built allowed users to select from a few themes for their website.
00:03:11.820 Within each theme, users could select a color palette that determined the navigation colors, background colors, text colors, etc. However, we wanted to offer a more customizable experience, allowing users not to be limited to the palettes we provided.
00:03:38.459 In working towards a good user experience, we didn't want a simple color wheel where users would select swatches—this often leads to frustration as the final result may not look good. Instead, we decided to allow users to pick a 'seed' color and generate palettes based on various color harmonies.
00:04:00.090 This way, users can customize their palettes while still achieving visually appealing results for their websites. This was the problem I was tasked with solving, and it introduced several implementation considerations.
00:04:26.880 First, we already had existing stylesheets that we were using in a static context. These included complex Sass rules that determined background colors, text colors, and more. It wouldn’t make sense to duplicate all this code solely for dynamic contexts.
00:04:38.760 Therefore, we aimed to set up our project so that we could reuse the stylesheets in both static and custom color contexts. This ruled out using the asset pipeline, especially not in production.
00:05:02.250 As a result, we needed to render styles from a controller instead. We found that Sass is relatively quick to render, even with complex stylesheets. To improve efficiency, we looked at caching the generated CSS and possibly rendering it in a background worker.
00:05:21.420 Now, just as a quick introduction, I assume that many of you are familiar with Sass and Rails, but just in case, if you're new to Sass and Rails, it's important to know that Sass stands for Syntactically Awesome Style Sheets.
00:05:41.610 Sass is a CSS preprocessor, which gives you an improved syntax that looks like CSS but is designed to make it easier to write and maintain your stylesheets. Here’s a basic overview of what Sass looks like: we have variables, which allow you to avoid duplicating common values across your stylesheets. For instance, if you have a specific font size or text color, you can set that as a variable.
00:06:13.500 This way, if you need to make changes, you only have to change the variable value instead of searching through your stylesheets to modify every instance.
00:06:39.310 We also have nested rules in Sass, which help avoid long descendant selectors, allowing you to namespace your Sass rules. You can include other Sass stylesheets using the @import keyword for modularity, which means you can divide up your stylesheets for different widgets or pages.
00:07:09.080 Additionally, mixins in Sass allow you to create reusable styles that can be included in multiple selectors. This saves you from having to repeatedly add classes in your markup.
00:07:36.590 There are debates on whether CSS preprocessors are worth it, but I personally find them beneficial because they provide modularity, keeping my stylesheets maintainable and clean, especially with mixins and variables that enforce the DRY principle.
00:08:00.430 Sass even supports data structures and allows scripting, reducing repetition in your CSS. For example, with background images, you could create a map of positions and generate rules efficiently using loops.
00:08:23.880 Regarding our topic today, we want to reuse stylesheets effectively, especially focusing on variables. Our architecture will include a global variable representing our color palette. We will then import another stylesheet that will depend on this global variable, hence being able to generate styles accordingly.
00:08:41.700 Let’s look at the static context as our first example. I have three Sass files here. The top one is the simplified UI Sass file, where we define a selector with background and color properties. Notice we're calling the 'nth' function to access our palette variable.
00:09:03.180 In the first stylesheet, we define a list for our color palette. By using a space-delimited list, I can create a more explicit and clear palette for usage, consisting of red and green. Below that, I import my UI file.
00:09:31.170 For a second palette, we define another list again; this time it includes blue and yellow. By importing the UI file again, we can reuse the styles defined above while introducing new color sets.
00:09:52.560 Using a global palette makes it clear how stylesheets can reuse color definitions. Now, let's move on to the dynamic context where we'll analyze Sass functions.
00:10:02.790 Here, we set the palette equal to a return value from our 'get_dynamic_palette' function. This demonstrates how to incorporate dynamic elements. Where does this function come from? Sass has a module for script functions, which allows us to add methods available to Sass stylesheets.
00:10:21.660 When defining a function in Sass, it’s vital to work with data types available in the host language. For example, when creating a Ruby extension for Sass, specifications that return a specific value type become necessary.
00:10:53.700 In this instance, we'll create a palette by generating two random hexadecimal strings. We can leverage the 'Sass::Script::Value::Color' module from Sass to manipulate color values.
00:11:18.540 From hexadecimal strings, we will create an array using ‘Sass::Script::Value::List’. It expects an array of Sass value types, which we use for instantiation. So, we’ve managed to get some dynamic content from Ruby, but what about injecting user data? We want this to work in a Rails context where the client makes a request to receive dynamic CSS.
00:12:00.660 However, we have another challenge with Rails' asset pipeline, which traditionally supports static content rendering. In Rails applications, the Sass gem gets bundled with your app, allowing for Sass processing through the asset pipeline.
00:12:21.780 This creates a limitation—we would have to do some hackery to achieve dynamic rendering. Additionally, assets are precompiled for production, further complicating the task.
00:12:44.460 Thus, we need to have a render class that can manage dynamic content dynamically during requests, and this is where the Sass engine becomes useful.
00:13:02.940 The Sass engine processes CSS based on Sass templates, and we can create a 'SassCustomPalette' class. The first step is to define a template string that won’t change, to avoid adding extra files for reuse.
00:13:22.140 In this class, we will receive colors as instance variables. We will then set up a render method that delegates calls to the Sass engine while supplying our custom options.
00:13:44.640 Some of these options include using SCSS syntax, which is more popular, and an option for pretty-printing the CSS. Most notably, we have a key for custom data; this is critical for injecting data into Sass templates.
00:14:06.900 We need to find the function responsible for creating that custom palette. Similar to our previous example, we'll use the options argument to access injected data. Upon wrapping the color, we'll use a factor variable to lighten and darken colors by 20%.
00:14:30.270 These built-in lighten and darken functions in Sass help us reshape our colors. Finally, we’ll render CSS from the controller by instantiating our custom palette class while passing parameters like custom color.
00:15:00.150 When we execute the render, we may encounter an error indicating that the Sass syntax file cannot be found. In typical programming work, errors often stem from typos or missing components.
00:15:23.280 Since we began with a language that requires compilation or rendering, there’s always a loading path that we might need to define. Let's add a load path to our options to ensure it recognizes the UI file location.
00:15:43.800 With the load path established, we’ll reference the directory holding the UI file. I have a simple application setup to demonstrate this process. The app displays 'Hello, World!' and takes a color input that generates lightened colors for the background and darkened text.
00:16:05.220 After submitting, the colors render appropriately based on my specified color. If you check the developer tools, you can see what the requests are generating.
00:16:29.640 We can test it with another color and verify the request. While I initially intended to use red, it didn’t quite fit; instead, I opted for green! The demo showcases how changing colors reflects dynamically within the application.
00:17:01.050 Now we are ready to implement this with Simply Built and see how it performs with the GET requests.
00:17:33.660 Upon performing the request, we’re confronted with a latency issue. Two seconds for a request is quite sluggish. Investigating why this happens reveals several factors.
00:18:02.610 The slow performance stems from a combination of the complexity of our stylesheets, interdependencies, and any usage of external libraries like Compass for prefix management.
00:18:32.520 This increased processing time affects not only individual requests but can also influence the rest of the server. Imagine a situation where multiple requests are being handled simultaneously, potentially monopolizing server resources.
00:19:01.350 Given that we're dealing with MRI Ruby, we need to keep in mind the Global Interpreter Lock, which hampers true concurrency.
00:19:26.940 As such, we must consider implementing a caching strategy: once a user retrieves their custom color response, we can save it for subsequent requests.
00:19:49.920 Using memcached is ideal since it allows us to store rendered Sass so that when new requests are made for the same colors, we avoid redundant processing.
00:20:07.680 Integrating memcached into a Rails environment is straightforward. Simply configure the caching option in the application setting, linking it to your server URL.
00:20:28.560 Rails abstracts the caching details, making it easy to interact with regardless of the caching store being utilized.
00:20:51.540 Incorporating Caching into our Sass rendering involves implementing a 'SassRenderer' class to manage the caching logic, including methods for rendering cached content.
00:21:10.800 This design saves us substantial rendering time. The objective is to check the cache, returning preexisting rendered CSS if available. If it's not cached, we will invoke the rendering process.
00:21:32.820 The result? A response time significantly reduced from two seconds down to just a fraction of that—212 milliseconds.
00:22:03.060 However, we need to address the potential performance concerns regarding those first requests. Users requesting an unseen color will experience the initial latency.
00:22:18.780 To mitigate this, we can consider rendering the stylesheets in the background using Sidekiq. This allows us to offload processing while keeping our server thread available for handling new incoming requests.
00:22:54.780 In this dynamic context, managing requests with Sidekiq involves setting up background workers. We will create a 'SassCustomPaletteWorker' that will instantiate the appropriate classes, generating the CSS behind the scenes.
00:23:31.620 Additionally, we must have an event signaling to the client when processing is complete. This introduces some complexity, as the client will need to check the status of the request, possibly implementing polling mechanisms or web sockets.
00:24:07.200 The controller will require new methods—one for the initial request and a second to check if the rendering process is done. This approach sounds verbose, and one might wonder if we are overengineering this solution.
00:24:33.660 Collaboration within our team allowed us to refine this codebase, enhancing our Sass rendering methods. We simplified the logic, reducing initial render times to around 500 milliseconds.
00:25:05.730 The performance for cached responses is effective, allowing us to serve stylesheets quickly after they’ve been accessed once.
00:25:31.050 In conclusion, we've explored how Sass can be used to render both static and dynamic content. We realized that complex stylesheets could lead to slower render times.
00:26:05.220 Caching emerged as a key technique for performance improvement, while background processing, despite its benefits, could introduce complexity.
00:26:37.020 Furthermore, refactoring was pivotal in simplifying the logic, showcasing how good coding practices can enhance performance without adding unnecessary burden.
00:27:03.060 Lastly, these lessons remind us that while we aim for simple solutions, performance considerations can escalate complexities we don’t initially expect.
00:27:36.150 Thank you for your kind attention, and I hope you take away valuable insights from this discussion.
00:27:55.380 Thank you.