 Good afternoon, everyone. How are we doing after lunch? Feeling all right? Sweet. Thank you for coming out to this. I don't normally see a lot of talks on SAS, so I hope you'll find this one interesting, because we're gonna look at making it a little more dynamic. And just to give you kind of a preview, so really what we're wanting to accomplish is take our SAS style sheets, and in a dynamic way, take in user input from the client and be able to render out dynamic CSS. So it's not so static like the style sheets we might use in the Asset Pipeline. So what are we going to talk about then? Obviously, we're gonna generate dynamic CSS. That's the topic for today. And like I mentioned, this is gonna be based on user input. So we're gonna learn about writing SAS functions in Ruby, actually, because we're gonna have to figure out some way to get some dynamic content into a style sheet context. And along the way, we'll find that this may not be the most performant route, so we're gonna identify performance issues and think of ways that we can resolve and fix that. So that means we are probably gonna look at caching and even maybe some background processing. Before we get too far, I'm Jeremy. I live in Tennessee in a very small town, or I'm El Papapoyo on Twitter. I work for a company called Push Agency. We are a completely distributed team, so I'm a remote web developer. I do a lot of Rails and JavaScript. So I'm back-end guy, front-end guy, full stack, whatever you want to call it. So we do a lot of client work, but we have this product called SimplyBuilt. And SimplyBuilt is a website builder, editor, and you can have as many websites as you want. You can edit your sites directly looking at them. So we have a drag-and-drop interface for different blocks of content, and you can rearrange those blocks and edit them directly via the content-editable attribute that's available in HTML5. So why am I bringing up SimplyBuilt, though? We're here about SAS. Am I just gonna pitch our product to you? Well, maybe. I think it's pretty awesome. But this is related to the talk. So, early in this year, I was working on a task for SimplyBuilt, a new feature. So at the time, SimplyBuilt, for your website, you could select from a few themes, and then within each theme, you could select your color palette. And that would determine what navigation colors you're using, what background colors are being used, text color, et cetera. So we just wanted to offer that in a custom manner to where a user wouldn't be stuck with just whatever palettes we have, and they could customize it. And we wanted the user experience to be pretty nice. So we didn't want to just have a color wheel and you select all your swatches, and more than likely, you're gonna get frustrated. It's not gonna look good, and you're gonna hate us. So we thought, let's let them pick a color, sort of a seed color, and then we'll generate palettes with different color harmonies. And then that way we can let them customize it, but still generate something that's gonna look good for their website. So this was the problem that I was tasked with solving. And so there were some implementation issues that I had to start to consider. First thing, we already have these style sheets, and we're using them in a static context. So we have all these complex rules in SAS that determine what background color gets used, which palette, text color, et cetera. Well, it doesn't make sense to re-implement all this and duplicate code just for a dynamic context. So we want to put this in a manner where I can reuse the style sheet in both our static default context and in the custom color context. And that means we're probably not gonna use the Asset Pipeline, especially not if we're in production. So we're gonna have to somehow get this to work from a controller and render out that way. But we'll find that SAS isn't very quick at rendering, especially when you get pretty complex style sheets. So we'll look at caching generated CSS and maybe even rendering in the worker. And so these were some of the research I did in looking into getting this to work with Simply Bill and some of the implementation issues that I ended up facing. So just as a quick introduction, I'm sure most of you have already used SAS and Rails, but just to let you get a feel for it if you've never seen it before, if you're new to SAS and Rails, it's syntactically awesome style sheets. And SAS is a CSS preprocessor. So it offers you some different syntax that looks like CSS, but ideally is gonna make your job easier to write your CSS in a more manageable, maintainable way. And here's just basic overview of what some SAS looks like. So we have variables. The nice thing about variables is if you have a certain font size or text color you like to use in your style sheets, instead of constantly duplicating that over and over, we'll set it to a variable. And then we can use our variable throughout our styles. And if we need to make a change, all we have to do is change the value of our variable. We also have nested rules. So with nested rules, it allows you to avoid the whole descender selectors, descendant selectors that can be super long, and this way you can kind of name space your SAS rules. We can include other SAS style sheets via the import keyword, and that allows us to make our files more modular. We can devote this style sheet over here to some widget, and this one to this web page, et cetera. Mixins, they're kind of like modules in Ruby. I can create some rules that I know I will want to use in more than one class or selector, and I can just include them, and that helps avoid that sometimes you want to extract out some common class, and then you've got to add more classes to your markup, so this helps you kind of avoid that. So what? I know there's debate sometimes whether CSS preprocessed or neither or not. I really like it. Obviously it gives you modularity. I can separate out different widgets and pages, and I can import them as needed, and it helps me keep my style sheets more maintainable and clean. Obviously with Mixins and variables, it helps enforce the drive principle. We also actually have data structures and scripting in SAS, so it's a language, and you can capitalize that and use loops and whatnot to also help reduce repetitiveness in your CSS. Like a great example is a background sprite image, and you want to set where the positioning is, so you could create a map which has a class you want to use for a position, the actual background position, and then loop over that and generate your rules that way. And there's also color functions, which is really helpful, especially in this case with custom colors where I can take a color, lighten it, darken it, and then use that modified color in some way. And that's SAS. All right, so let's get on to what we're really going to talk about today. So first thing is we want to reuse these style sheets, and we'll find out it's in the variables. So we want to architect our files in a way that we have some global variable that has, in this case, we'll see, our palette of colors, and we'll import another file, which is actually the style sheet we're going to reuse. And so that imported file is going to depend on some global variable that needs to exist, and it's going to be able to use that then in generating these styles. So let's look at the static context first. So I have three SAS files here. The top one, that's going to be our simplified UI SAS file. So we have this foo selector. We have a background and a color. Now notice we're calling this int function. So SAS has lists. I mentioned they have data structures. This palette variable is going to be a list. So like you might guess, if I call int on a list and pass in a number, I'm going to grab that int element from the list. And you'll also notice it starts at 1. So SAS lists are just indexed starting at 1 instead of 0. So we go down to the next two files. We have a palette 1 SCSS and a palette 2 SCSS. And you'll notice I'm setting a list there. The first one, the palette. And I just use space delimiter, rapid and parins just to make it more explicit what I'm doing. You can use commas too for lists. And it's going to be consisting of red and green. And then I import my UI file. Below that I have the second palette. And I'm setting again a palette list, but this time it's blue and yellow. And I'm importing the UI. So basically what that's going to do, I've set this global scope palette is defined. When I import this file it's going to see that global variable and it's going to use that. So already you can see how we can reuse these style sheets in the static context. But we want to start looking at dynamic context. And this is where we're going to look at SAS functions. So here we have another style sheet. This time I'm setting palette equal to the return type of this getDynamicPalette function. And again I'm importing my UI. So where does getDynamicPalette come from? So SAS has this module SAS Script Functions. And any method you add to that module will make that method available as a function in your SAS style sheets. And if you've ever programmed in a host language where you're defining some function for the target language, you'll know you have to work in types that are available in the host language. So if you've written a C extension for Ruby before, you know your C function. It's not going to return an integer. It's not going to return a character pointer. It's going to return a Ruby value type. So the same idea applies here with Ruby and SAS. So walking through this function here we're going to create a palette again. I'm calling two times map. So it's going to be two colors. I'm just generating two random hexadecimal strings. And then I'm calling this function on this very long module SAS Script Value Color. So underneath SAS Script Value exist all these types in SAS. There's colors, numbers, lists, et cetera. So Color has this convenient class level method called from hex. And obviously, like you might guess, it'll take a hexadecimal string and it'll return this color value type. So I'm going to basically create an array of two colors, SAS colors. And there at the bottom I'm going to pass that into a SAS Script Value List. So it expects, when I instantiate it, it expects an array of SAS value types. And then the other parameter there, which is the space symbol, is basically saying this is the delimiter I want to use in my list. Okay, so that's pretty cool. We can get some type of dynamic content from Ruby into SAS. But what about injecting data from a user? Because we want this to work somehow in the Rails ecosystem where the client makes a request and we get back some dynamic CSS to them. So I'm basically saying here I want getDynamicPalette from user somehow magically. And how is that going to work? Can we use the asset pipeline? Not exactly. So we have sort of a quote-unquote static context in the tilt template for SAS Rails. So SAS Rails, it's a gem that you normally have bundled with your Rails app that gets SAS running in the asset pipeline. And it defines this tilt template, which is responsible for rendering out the SAS. And we're kind of locked in whatever it's doing there. We would have to do some hackery to get it to work in a dynamic way like we'd like. So that's not going to work. Oh, and assets, you know, they're pre-combodable for production. So yeah, asset pipeline is not going to work here. So we need some sort of render class for dynamic content that we're going to have to write. So this is where SAS Engine will come to the rescue. So SAS Engine is the actual engine where you render out your CSS based on an CSS or SAS template. So we'll have this SAS custom pallet class we'll define. And the first thing, we're going to define this template string. And we'll do that because it's not going to change. We really don't want to throw another file on the file system to read in just to reuse this in a dynamic context. So we'll just set this template constant. And you'll notice there, it's going to call a getCustomPallet function in SAS. So we're going to take in our color, we'll set it as an instance variable. We'll have a render method which is basically going to delegate to the SAS Engine. Pass in the template, we pass in some SAS custom options, and we call it render. So there at the bottom, what's up with these SAS custom options? So the first couple options, pretty straightforward syntax, SCSS. That's usually the more popular syntax people like. Style expanded, that's just for pretty printing your CSS. And then that last option, notice, it's called custom. And if you look at SAS's documentation, they mentioned this is specifically what we need in this context. If you want to inject some sort of custom data to your SAS style sheets, then you pass it in there. So I'm going to pass in a hash, and it's going to basically have whatever our custom seed color is. Using that custom palette function, remember we need to add a method to SAS script functions. So it's pretty similar to what we did in the last example. This time though, notice that we're going to call this options method. And we're going to pass in the custom key and then the color key. So options is available to you inside these SAS methods based on whatever data you injected. So again, we'll wrap that in a color. Then we're going to create this factor variable. And so basically what I want to do is I just want to create 20%. So I pass in to the number type 20, and then the percent symbol, which is sort of like, here's my unit for this number. And then I'm going to generate a palette where I'm going to lighten the color by 20%. And then the other color will darken it by 20%. And the light and darken functions, those are built into SAS, so that's how they're available there as well. And finally, pass it again into a list and return it. So finally what we need to do is we've got to get this rendering from the controller. So we instantiate our new class, pass in params custom color, and then we render out the CSS. We'll try it out. We'll make an HTTP request. Oh. SAS syntax error, file to import, not found or unreadable. So what happens normally when you're working in a language you have to compile or render? What do you normally need? There's always a load path, and we forgot that. So let's go back and modify this because we need this load path because it needs to think in some sense it's just like a static style sheet in the asset pipeline. So we're going to add in a new option to our SAS custom options called load paths. And if you look at the method for that, we're basically just in this case passing in an array containing one directory which is app, assets, stylesheets, includes. And that's where I'm going to store my UI file. So let's try that out, and I actually have a demo just to kind of highlight this at a low level. So what I have here is a pretty simple app I put together. It's got the Hello World text, and I'm going to pass in color, and then it's going to render that lightened color as the background for Hello World, and it'll make the text color of Hello World that darkened color. So red wasn't working too well with these, so I'm going to go with green. So I'm going to hit submit, and there we go. It may be a little harder to notice with the projector, but if we go over here to DevTools and look at the request we just made, and click on it, you can see there is the background was lightened green by 20%, and the text color was darkened. And just to show that this will work with other colors, we can try blue. And there, the colors have changed. Anyone have a color you want me to try? Tusha? I wonder if I can spell that right. Let's try purple. That's a little simpler spell. So yeah, we'll click on the request down here, and yeah, so it works. And I'll admit I cheated here a little. I'm not using the hexadecimal representations because I hate typing those out, so I'm just using a gem I wrote that lets me translate those color names to their actual hexadecimal representations. So there we go. It worked. That's pretty cool. We got it working in this dynamic context based on what the user sent up from the client. So now I want to actually get this implemented and simply build, try it out. So I'm going to make my get request, see what's going to happen. Okay, I'm waiting. I'm still waiting. All right, still waiting. All right, done. That took a while. What's the damage here? Oh my gosh. That request took two seconds. That's two seconds too long. So what's going on here? So what's the problem? That two seconds is worrying. And in fact, once I benchmark just the SAS itself, we're still talking render times of one to one and a half seconds. So throw in the extra overhead of being on a web server and it adds up to that two seconds. So why is this happening? Well, this is a pretty complex SAS style sheet. It is responsible for lots of different colors, for lots of different pieces of a website editor. There are a lot of file dependencies, so we have this modular structure, and we're importing lots of these files. We're also using loops and data structure lookups. So it's all going to start adding up, especially when you throw compass in the mix, too, to handle a lot of your prefixing and whatnot. So just to really stress how important this is and what a big deal this can be, I want to mention this can impact the whole server. It's not only going to impact that one person making the request, it's obviously going to affect anyone making a request for this custom color. So, and I'm hoping the color is going to work on this. So I have this little hypothetical situation. So let's say on our production server, we have a couple forked processes, like Puma, we've got a couple workers, and each of these has just two threads working in them. So let's say a request comes in and the first process picks it up, the thread grabs the request, it gets scheduled on a core, and this is to render one of our SAS custom style sheets. All right, let's say another request comes in. The second process, thread picks it up, gets scheduled on another core, and it's rendering some more SAS, too. So now we have a third request come in, and my question is what's going to happen here? And just to give a hint, we are using MRI Ruby here. So as you might guess, more than likely we're going to end up waiting on the other request or thread to finish, because if you know what's MRI Ruby, we have a global interpreter lock. And we can't really schedule that for another thread because only one single, only one thread is allowed to run at one time because of the global interpreter lock. So we can't get real true concurrency here. So that request is going to have to wait until something else frees up. And this request could be just to, like, look at your list of websites or view your published website. You're not even interested in the rendered SAS. At best, maybe the CPU scheduler will preempt one of these SAS threads and let this other request happen and then reschedule the other one to finish the SAS rendering, but we don't have guarantees on that. So as you might guess, we're going to need caching because we'll realize if user A, once red is their custom color, it gets rendered out, they got it, they're done. Okay, user two comes. They want red too, actually. So if our UI style sheet's not changing a whole lot, there's really no reason to keep rendering this over and over. So obviously caching is going to be the best answer for this. So we're using memcached. I'm sure maybe a lot of you have tried it out or used it in production. It's a in-memory key value store. So it will allow us to render our SAS, store it in memcached, and then when a new request comes in for that same color, we already have it available, we can pull it out, and we're not wasting time rendering again. So setting up memcached is pretty straightforward in Rails. If you've never done it before, you can configure this cache store option on config. We pass in memcached or symbol, and then just one of our server URLs and some options. And then Rails hides the details of whatever cache you're using underneath via rails.cache right and fetch. So if I want to put something in the cache, I'll call it right. In this case, I'm setting foo equal to bar, and then I can fetch it later with the foo key. So pretty straightforward. It's a nice abstraction, so you can use some other cache store like FileCache on your development, but you're writing it one way. So let's introduce caching into our SAS rendering. So we're going to add in this pretty large class now called the SAS renderer, and it's basically going to wrap over the SAS engine that we were using before and add in this caching layer and extract out those details. So it'll take in a template, a cache key that we'll give it, and the SAS options. So we'll instantiate the engine, and then the next method, render, basically what we do is we call this fromCache method, and we pass in a block which is going to render the CSS. And then down at the bottom what this fromCache method does basically is it first checks the cache, pulls the CSS if it does exist. If not, then it'll invoke that block which renders the CSS, then it can store it in the cache and return it. So now let's incorporate that into our SAS custom pallet class we had earlier. So basically what we'll do is we'll kind of replace calling SAS engine directly with the SAS renderer we just wrote. So pass in the template, cache key, and those same custom options, and we'll just delegate render to this new engine, which is our SAS renderer. And... a lot better. So 212 milliseconds is much better than two seconds. Now this does raise the question, though. What about the first render, though? Some poor soul is going to have to unfortunately deal with that first render of a color that's not been used yet. And there's not a lot we can do for that first person, but maybe we could look into preventing this from impacting our whole web server. So one idea is maybe we can process this render in the background. So a good way to do this is something like sidekick. So we use sidekick. You may have used rescue or even delayed job. So this basically allows us to have some workers in the background and we can pass off jobs to them via a Redis queue. And that way, the benefit is, let's just see why bother, is we can still free up that server thread that initially needs to render. So it can quickly go back to handling new requests and we're not blocking other requests. So ideally this should help keep our throughput on our web server pretty fast. And like I mentioned, we don't have good thread concurrency guarantees with MRI, so this will hopefully help with that. There's always a but. This is going to introduce potentially a lot of code complexity. So on the client, they're going to have to do, introduce some sort of mechanism where they can make a request for this CSS and then they're going to have to get it somehow, but we've kind of introduced this asynchronicity. So we might have to introduce some polling code or we could use web sockets. But there's other issues to think about. Passing off work to a worker now, we've got to think about what happens when jobs fail, how many retries do we do, how long does the client keep trying to pull, what happens if a worker's not available yet? We might be introducing more latency for that initial render for the first user wanting that color. And obviously we're going to introduce some more network traffic, especially with polling. So let's just see what are the possibilities. We'll look at setting up a worker and doing the polling and figure out if this is going to be a good approach. So setting up your worker with Sidekick, it's pretty straightforward. We create this worker class we're calling it SAS CustomPalletWorker. You include a SidekickWorker module and that's going to set everything up to where you're going to need to define this perform method. And it's pretty straightforward. We'll take in our color, we'll instantiate the SAS CustomPallet class which is actually rendering this and we'll call render. And we know in the background it's going to end up getting that CSS cached. So then we'll add a new method to SAS CustomPallet. It's called renderAsync and it's basically going to check first is this cached, if it's not then let's get the worker queued up to actually render it. So we'll call performAsync on the worker and then we'll return the cache key. And then we'll just add a few methods to SAS renderer to make it a little simpler to check if something's cached or not which we'll use in our controller. So let's just say we're going to use polling. What is that going to look like? And this is my lovely diagram, a conversation between the server and client. So the client says I need a pallet for red. The server says sure thing, your key is meow. The client walks off, he's waiting, comes back, the server says nope, not yet. The client has to walk off again, comes back, hey is meow ready now? Yep, here you go. So what does that look like in a controller? So notice we've had to add two new methods now. We have request custom pallet and that's basically what the client's going to first call out. So we'll instantiate the custom pallet and we'll call our new render async method. After that the client's then going to have to poll and call the check custom pallet method which is going to have the sass renderer check, is it cached? And once it is finally ready, the client will know to call our original custom pallet action which will get the CSS from the key. But is this the best answer? That was a lot of code to add and are we over-engineering this? So that was one of the good questions that my co-worker had. So I started on this project. I got a lot of the framework laid out for it and then I had to move over to another team and so one of the other developers on our team he picked it up and was able to kind of give over the finish line and do some of the cleanup that I wasn't able to get finished and so one of the big questions is can refactoring just... can it simplify our sass? And the answer was actually yes. So really working on the CSS or the sass style sheets, reducing the complexity we were able to get initial renders down to around 500 milliseconds which is a lot better than two seconds and that seems like a pretty good sacrifice to make here and obviously once it's been rendered for the first time, the cache response times are pretty good. So what did we find out? In this whole process we were able to identify areas for code improvement that we didn't even realize at the time and through that we were able to still get acceptable render times and eliminate the need for this background processing and extra code complexity. So let's recap then what we've really learned here today. So obviously we learned how we can reuse sass style sheets to render static and dynamic content. We also realized that when you get pretty complex style sheets rendering isn't always fast. We learned that this actually involved some web server performance issues that we needed to look into. So obviously we learned the importance of caching. We saw background processing can help but it may introduce some code complexity. And we realized you know like most of us know sometimes factoring may be all you need. So what does this mean? Things aren't always simple. We were just supposed to render sass dynamically but somehow we ended up looking at web server performance and caching. So we always have to consider performance even in some of the tasks that may seem so easy and so simple. And I think that's something we all realize as programmers and especially web developers. So thank you.