Dynamically Sassy

Dynamically Sassy

Generating dynamic CSS in Ruby on Rails

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

April 22, 2015
Tweet

Transcript

  1. Dynamically Sassy Generating Dynamic CSS in Rails Jeremy Fairbank

  2. What are we going to talk about?

  3. Generate dynamic CSS

  4. Write Sass functions in Ruby

  5. Identify performance issues

  6. Caching and background processing

  7. Hi, I'm Jeremy ...or @elpapapollo

  8. pushagency.io

  9. simplybuilt.com

  10. Our Problem

  11. Implementation Issues

  12. Same Sass style sheet for default and custom color palettes

  13. Rendering custom color style sheets from a controller

  14. Slow Sass render times

  15. Caching generated CSS

  16. Rendering in a worker

  17. Syntactically Awesome Style Sheets sass-lang.com

  18. / / V a r i a b l e

    s $ f o n t - s i z e : 1 2 p x ; / / N e s t e d r u l e s # f o o { . b a r { f o n t - s i z e : $ f o n t - s i z e ; } } / / I n c l u d e o t h e r S a s s s t y l e s h e e t s @ i m p o r t ' b a r ' ; / / M i x i n s @ m i x i n h a s - c a t s { & : : b e f o r e { c o n t e n t : ' m e o w ' ; d i s p l a y : b l o c k ; } } # i n t e r n e t { @ i n c l u d e h a s - c a t s ; }
  19. So What?

  20. Modularity

  21. DRY

  22. Data Structures

  23. Scripting

  24. Color Functions

  25. That's Sass

  26. Reusing style sheets

  27. It's all in the variables

  28. Static Context

  29. / / _ m y - u i . s

    c s s # f o o { b a c k g r o u n d : n t h ( $ p a l e t t e , 1 ) ; c o l o r : n t h ( $ p a l e t t e , 2 ) ; } / / p a l e t t e 1 . s c s s $ p a l e t t e : ( r e d g r e e n ) ; @ i m p o r t ' m y - u i ' ; / / p a l e t t e 2 . s c s s $ p a l e t t e : ( b l u e y e l l o w ) ; @ i m p o r t ' m y - u i ' ;
  30. Dynamic Context

  31. Sass Functions

  32. / / d y n a m i c -

    p a l e t t e . s c s s $ p a l e t t e : g e t - d y n a m i c - p a l e t t e ( ) ; @ i m p o r t ' m y - u i ' ; # c o n f i g / i n i t i a l i z e r s / s a s s . r b m o d u l e S a s s : : S c r i p t : : F u n c t i o n s d e f g e t _ d y n a m i c _ p a l e t t e p a l e t t e = 2 . t i m e s . m a p d o c o l o r = ' # % 0 6 x ' % ( r a n d * 0 x f f f f f f ) S a s s : : S c r i p t : : V a l u e : : C o l o r . f r o m _ h e x ( c o l o r ) e n d S a s s : : S c r i p t : : V a l u e : : L i s t . n e w ( p a l e t t e , : s p a c e ) e n d e n d
  33. Injecting data from a user?

  34. / / a p p / a s s e

    t s / s t y l e s h e e t s / d y n a m i c - p a l e t t e - r a i l s . c s s . s c s s $ p a l e t t e : g e t - d y n a m i c - p a l e t t e - f r o m - u s e r - s o m e h o w - m a g i c a l l y ( ) ; @ i m p o r t ' m y - u i ' ;
  35. Use the asset pipeline?

  36. "Static" context in the tilt template

  37. # s a s s - r a i l

    s / l i b / s a s s / r a i l s / t e m p l a t e . r b m o d u l e S a s s m o d u l e R a i l s c l a s s S a s s T e m p l a t e < T i l t : : T e m p l a t e # . . . d e f e v a l u a t e ( c o n t e x t , l o c a l s , & b l o c k ) c a c h e _ s t o r e = C a c h e S t o r e . n e w ( c o n t e x t . e n v i r o n m e n t ) o p t i o n s = { : f i l e n a m e = > e v a l _ f i l e , : l i n e = > l i n e , : s y n t a x = > s y n t a x , : c a c h e _ s t o r e = > c a c h e _ s t o r e , : i m p o r t e r = > i m p o r t e r _ c l a s s . n e w ( c o n t e x t . p a t h n a m e . t o _ s ) , : l o a d _ p a t h s = > c o n t e x t . e n v i r o n m e n t . p a t h s . m a p { | p a t h | i m p o r t e r _ c l a s s . n e w ( p a t h . t o _ s ) } , : s p r o c k e t s = > { : c o n t e x t = > c o n t e x t , : e n v i r o n m e n t = > c o n t e x t . e n v i r o n m e n t } } s a s s _ c o n f i g = c o n t e x t . s a s s _ c o n f i g . m e r g e ( o p t i o n s ) e n g i n e = : : S a s s : : E n g i n e . n e w ( d a t a , s a s s _ c o n f i g ) c s s = e n g i n e . r e n d e r e n g i n e . d e p e n d e n c i e s . m a p d o | d e p e n d e n c y | c o n t e x t . d e p e n d _ o n ( d e p e n d e n c y . o p t i o n s [ : f i l e n a m e ] ) e n d c s s r e s c u e : : S a s s : : S y n t a x E r r o r = > e c o n t e x t . _ _ L I N E _ _ = e . s a s s _ b a c k t r a c e . f i r s t [ : l i n e ] r a i s e e e n d # . . . e n d e n d e n d
  38. Oh, and assets are precompiled for production

  39. We need our own render class for dynamic content

  40. S a s s : : E n g i

    n e to the rescue
  41. # l i b / s a s s _

    c u s t o m _ p a l e t t e . r b c l a s s S a s s C u s t o m P a l e t t e T E M P L A T E = < < - E O S . f r e e z e $ p a l e t t e : g e t - c u s t o m - p a l e t t e ( ) ; @ i m p o r t ' m y - u i ' ; E O S d e f i n i t i a l i z e ( c o l o r ) @ c o l o r = c o l o r e n d d e f r e n d e r S a s s : : E n g i n e . n e w ( T E M P L A T E , s a s s _ c u s t o m _ o p t i o n s ) . r e n d e r e n d p r i v a t e d e f s a s s _ c u s t o m _ o p t i o n s { s y n t a x : : s c s s , s t y l e : : e x p a n d e d , c u s t o m : { c o l o r : @ c o l o r } } e n d e n d
  42. # c o n f i g / i n

    i t i a l i z e r s / s a s s . r b m o d u l e S a s s : : S c r i p t : : F u n c t i o n s d e f g e t _ c u s t o m _ p a l e t t e c o l o r = S a s s : : S c r i p t : : V a l u e : : C o l o r . f r o m _ h e x ( o p t i o n s [ : c u s t o m ] [ : c o l o r ] ) f a c t o r = S a s s : : S c r i p t : : V a l u e : : N u m b e r . n e w ( 2 0 , ' % ' ) p a l e t t e = [ l i g h t e n ( c o l o r , f a c t o r ) , d a r k e n ( c o l o r , f a c t o r ) ] S a s s : : S c r i p t : : V a l u e : : L i s t . n e w ( p a l e t t e , : s p a c e ) e n d e n d
  43. # a p p / c o n t r

    o l l e r s / p a l e t t e s _ c o n t r o l l e r . r b c l a s s P a l e t t e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f c u s t o m _ p a l e t t e c u s t o m _ r e n d e r e r = S a s s C u s t o m P a l e t t e . n e w ( p a r a m s [ : c u s t o m _ c o l o r ] ) @ c s s = c u s t o m _ r e n d e r e r . r e n d e r e n d e n d - # a p p / v i e w s / p a l e t t e s / c u s t o m _ p a l e t t e . c s s . h a m l = @ c s s . h t m l _ s a f e
  44. Try it out...

  45. S a s s : : S y n t

    a x E r r o r - F i l e t o i m p o r t n o t f o u n d o r u n r e a d a b l e : m y - u i . - _ _ -
  46. There's always a load path

  47. c l a s s S a s s C

    u s t o m P a l e t t e p r i v a t e d e f l o a d _ p a t h s r o o t = R a i l s . r o o t . j o i n ( ' a p p ' , ' a s s e t s ' , ' s t y l e s h e e t s ' ) D i r [ r o o t . j o i n ( ' i n c l u d e s ' ) ] e n d d e f s a s s _ c u s t o m _ o p t i o n s { s y n t a x : : s c s s , s t y l e : : e x p a n d e d , l o a d _ p a t h s : l o a d _ p a t h s , c u s t o m : { c o l o r : @ c o l o r } } e n d e n d / / a p p / a s s e t s / s t y l e s h e e t s / i n c l u d e s / _ m y - u i . s c s s # f o o { b a c k g r o u n d : n t h ( $ p a l e t t e , 1 ) ; c o l o r : n t h ( $ p a l e t t e , 2 ) ; }
  48. Dynamic Sass Demo sassy-demos.jeremyfairbank.com/palettes

  49. Implementing in SimplyBuilt

  50. GET /custom_palette.css? custom_color=%23f00

  51. ...waiting ...waiting ...waiting

  52. Done!

  53. None
  54. What's the problem?

  55. Slow render times (1-1.5 seconds!)

  56. Complex Sass rules

  57. Many file dependencies

  58. Loops

  59. Data structure lookups

  60. Compass functions

  61. Can impact the whole server

  62. Web Server

  63. Web Server

  64. Web Server

  65. Web Server

  66. Web Server

  67. Yup, we need caching

  68. Memcached / memcached.org github.com/mperham/dalli

  69. # c o n f i g / e n

    v i r o n m e n t s / p r o d u c t i o n . r b c o n f i g . c a c h e _ s t o r e = : m e m _ c a c h e _ s t o r e , \ M E M _ C A C H E _ S E R V E R , M E M _ C A C H E _ O P T I O N S R a i l s . c a c h e . w r i t e ( ' f o o ' , ' b a r ' ) R a i l s . c a c h e . f e t c h ( ' f o o ' ) # = > ' b a r '
  70. Introduce caching into our Sass rendering

  71. c l a s s S a s s R

    e n d e r e r d e f i n i t i a l i z e ( t e m p l a t e , c a c h e _ k e y , o p t i o n s ) @ c a c h e _ k e y = c a c h e _ k e y @ e n g i n e = S a s s : : E n g i n e . n e w ( t e m p l a t e , o p t i o n s ) e n d d e f r e n d e r f r o m _ c a c h e { @ e n g i n e . r e n d e r } e n d d e f c a c h e d ? R a i l s . c a c h e . e x i s t ? ( @ c a c h e _ k e y ) e n d d e f g e t _ c a c h e d _ c s s R a i l s . c a c h e . f e t c h ( @ c a c h e _ k e y ) e n d p r i v a t e d e f s e t _ c a c h e d _ c s s ( c s s ) R a i l s . c a c h e . w r i t e ( @ c a c h e _ k e y , c s s ) e n d d e f f r o m _ c a c h e ( & w r i t e _ b l o c k ) g e t _ c a c h e d _ c s s | | w r i t e _ b l o c k . c a l l . t a p { | c s s | s e t _ c a c h e d _ c s s ( c s s ) } e n d e n d
  72. Incorporate into S a s s C u s t

    o m P a l e t t e
  73. c l a s s S a s s C

    u s t o m P a l e t t e d e f i n i t i a l i z e ( c o l o r ) @ c o l o r = c o l o r @ c a c h e _ k e y = " c u s t o m _ p a l e t t e / # { @ c o l o r } " @ e n g i n e = S a s s R e n d e r e r . n e w ( T E M P L A T E , @ c a c h e _ k e y , s a s s _ c u s t o m _ o p t i o n s ) e n d d e l e g a t e : r e n d e r , t o : : @ e n g i n e e n d
  74. And?

  75. None
  76. What about the first render?

  77. Process in the background

  78. sidekiq.org

  79. Why Bother?

  80. Free up server thread quickly to handle new request

  81. No thread concurrency guarantees with MRI

  82. But...

  83. Code complexity

  84. Polling client

  85. Websockets

  86. Failing jobs

  87. Waiting on an available worker

  88. Network traffic

  89. Setting up a Sidekiq worker

  90. # a p p / w o r k e

    r s / s a s s _ c u s t o m _ p a l e t t e _ w o r k e r . r b c l a s s S a s s C u s t o m P a l e t t e W o r k e r i n c l u d e S i d e k i q : : W o r k e r d e f p e r f o r m ( c o l o r ) S a s s C u s t o m P a l e t t e . n e w ( c o l o r ) . r e n d e r e n d e n d c l a s s S a s s C u s t o m P a l e t t e d e f r e n d e r _ a s y n c u n l e s s @ e n g i n e . c a c h e d ? S a s s C u s t o m P a l e t t e W o r k e r . p e f o r m _ a s y n c ( @ c o l o r ) e n d @ c a c h e _ k e y e n d e n d
  91. c l a s s S a s s R

    e n d e r e r d e f s e l f . g e t _ b y _ k e y ( k e y ) R a i l s . c a c h e . f e t c h ( k e y ) e n d d e f s e l f . c a c h e d ? ( k e y ) R a i l s . c a c h e . e x i s t ? ( k e y ) e n d p r i v a t e d e f g e t _ c a c h e d _ c s s s e l f . c l a s s . g e t _ b y _ k e y ( @ c a c h e _ k e y ) e n d e n d
  92. What would polling look like?

  93. Polling

  94. Polling

  95. Polling

  96. Polling

  97. Polling

  98. Polling

  99. Polling

  100. Polling

  101. c l a s s P a l e t

    t e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f r e q u e s t _ c u s t o m _ p a l e t t e c u s t o m _ r e n d e r e r = S a s s C u s t o m P a l e t t e . n e w ( p a r a m s [ : c u s t o m _ c o l o r ] ) @ k e y = c u s t o m _ r e n d e r e r . r e n d e r _ a s y n c e n d d e f c h e c k _ c u s t o m _ p a l e t t e @ r e a d y = S a s s R e n d e r e r . c a c h e d ? ( p a r a m s [ : k e y ] ) e n d d e f c u s t o m _ p a l e t t e @ c s s = S a s s R e n d e r e r . g e t _ b y _ k e y ( p a r a m s [ : k e y ] ) e n d e n d
  102. Is this the best answer?

  103. Can refactoring simplify our Sass?

  104. YES!

  105. Identified areas for code improvement

  106. Acceptable render times

  107. Eliminate need for background processing

  108. Recap

  109. Reuse Sass style sheets to render static and dynamic content

  110. Sass rendering isn't always fast

  111. Web server performance

  112. Importance of caching

  113. Background processing

  114. Sometimes refactoring is all you need

  115. So what does this mean?

  116. Things aren't always simple

  117. We always have to consider performance

  118. Thanks! Slides: Demo Source: Blog: sassy-talk.jeremyfairbank.com github.com/jfairbank/dynamically-sassy-demos blog.jeremyfairbank.com jeremy@pushagency.io @elpapapollo

    github.com/jfairbank