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.